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  (neutral shell; accents = player class via ApplyGuiClassTheme)
-- ============================================================
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 (fallback until CreateMainPanel → class color)
    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 },
    sectionHdr = { 1.00,  0.47,  0.00,  1.00 },
    -- tabs (tabIdle stays neutral)
    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 },
}

-- Replaces old fixed dark red on reset/delete buttons (class-tinted accent).
local function AccentDangerBtnRGB()
    return C.accent[1] * 0.58, C.accent[2] * 0.58, C.accent[3] * 0.58
end

local function AccentWarnBtnRGB()
    return C.accent[1] * 0.46, C.accent[2] * 0.46, C.accent[3] * 0.46
end

local function SetTextureAccentDanger(tex, alpha)
    tex:SetColorTexture(C.accent[1] * 0.58, C.accent[2] * 0.58, C.accent[3] * 0.58, alpha or 1)
end

local PANEL_W_DEFAULT = 1100
local PANEL_H_DEFAULT = 800
-- Runtime size (set from SavedVariables when the panel is created)
local PANEL_W  = PANEL_W_DEFAULT
local PANEL_H  = PANEL_H_DEFAULT
local SUBNAV_W = 155   -- middle sub-navigation column
local BOT_H    = 40
local TAB_H    = 40
local PAD      = 18
local SLIDER_W = 240

-- ============================================================
--  CONTENT PANE FADE-IN  (HorizonSuite-Style beim Tab-Wechsel)
-- ============================================================
local function FadePane(frame, dur)
    if not frame then return end
    dur = dur or 0.14
    frame:SetAlpha(0)
    frame:Show()
    local e = 0
    -- Dezentes Hochschieben kombiniert mit Einblenden (sanfter Slide-In).
    local SHIFT = 6
    frame:SetScript("OnUpdate", function(self, dt)
        e = e + dt
        local p = math.min(e / dur, 1)
        -- Ease-out quad
        local eased = 1 - (1 - p) * (1 - p)
        self:SetAlpha(eased)
        if p >= 1 then
            self:SetScript("OnUpdate", nil)
            self:SetAlpha(1)
        end
    end)
    -- Hinweis: kein Reparent/Positions-Eingriff – nur Alpha-Animation,
    -- damit bestehende Scroll-Anker/Layouts unverändert bleiben.
    local _ = SHIFT
end

-- ============================================================
--  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
    local fw, fh = frame:GetSize()
    fw = fw or PANEL_W_DEFAULT
    fh = fh or PANEL_H_DEFAULT
    Edge("TOPLEFT",    "TOPLEFT",    fw, 1)
    Edge("BOTTOMLEFT", "BOTTOMLEFT", fw, 1)
    Edge("TOPLEFT",    "TOPLEFT",    1, fh)
    Edge("TOPRIGHT",   "TOPRIGHT",   1, fh)

    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 AddOnVersionFromToc(folder)
    local gm = C_AddOns and C_AddOns.GetAddOnMetadata or GetAddOnMetadata
    if type(gm) ~= "function" then return nil end
    local v = gm(folder, "Version")
    if type(v) == "string" and strtrim(v) ~= "" then return strtrim(v) end
    return nil
end

local function AttachGuiTitleVersion(parentFrame, anchorTitle, folder)
    local ver = AddOnVersionFromToc(folder)
    if not ver then return end
    local fs = parentFrame:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    fs:SetPoint("LEFT", anchorTitle, "RIGHT", 8, -4)
    fs:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 0.92)
    fs:SetText("v" .. ver)
end

-- ── Section Card Infrastructure (direkt nach HorizonSuite's CreateAccordionCard) ─
-- Jede Section ist ein eigenes `card`-Frame mit `SetClipsChildren(true)`.
-- Widgets werden in `card.settingsContainer` umgehängt (statt an die Pane),
-- daher verschwinden sie sauber, sobald die Card-Höhe auf Header-Höhe
-- schrumpft.  Cards sind per SetPoint-Kette an die vorherige Card gehängt –
-- shrinkt eine Card, rutschen alle folgenden automatisch nach oben.
local SECTION_HEADER_H = 44    -- matches existing `yo -= 44` after header
local SECTION_CARD_GAP = 8     -- vertical gap between cards

-- Forward declarations so helpers/hooks can reference these before the actual
-- definitions.
local _SectionRedirect
local CloseDropdownPopup = function() end

-- Translates a (parent, y) pair destined for the pane into (container, y_cc)
-- inside the currently active section. Callers that pass non-pane parents
-- stay untouched.
_SectionRedirect = function(parent, y)
    local sec = parent and parent._activeSection
    if sec and sec.settingsContainer and not parent._suspendSectionTracking then
        return sec.settingsContainer, (y or 0) - sec.startY + SECTION_HEADER_H, sec
    end
    return parent, y, nil
end

-- Shadows CreateFrame so ANY CreateFrame(..., pane, ...) automatically has its
-- parent redirected into the active section's settingsContainer. This covers
-- helper-created widgets (MakeCheckbox, MakeSlider, …) AND inline CreateFrame
-- calls in the pane-builder code.
local _origCreateFrame = _G.CreateFrame
local function CreateFrame(frameType, name, parent, template, id)
    if parent and parent._activeSection and not parent._suspendSectionTracking
       and parent._activeSection.settingsContainer then
        parent = parent._activeSection.settingsContainer
    end
    return _origCreateFrame(frameType, name, parent, template, id)
end

-- Wraps SetPoint on a newly created region so that anchors that target the
-- pane are silently rewritten to target the section's settingsContainer with
-- the y-offset adjusted by the section's header height.  Supports the 5-arg
-- ("pt, rel, relPt, x, y") and 4-arg ("pt, rel, x, y") forms used throughout
-- rOrbs.
local function _WrapRegionSetPoint(region, pane, sec)
    local origSP = region.SetPoint
    region.SetPoint = function(self, pt, a, b, c, d)
        if a == pane then
            a = sec.settingsContainer
            if type(b) == "string" then
                -- 5-arg form: (pt, rel, relPt, x, y)
                d = (d or 0) - sec.startY + SECTION_HEADER_H
            elseif type(b) == "number" then
                -- 4-arg form: (pt, rel, x, y) — relPt defaults to pt
                c = (c or 0) - sec.startY + SECTION_HEADER_H
            end
        end
        return origSP(self, pt, a, b, c, d)
    end
end

-- Kept as a no-op alias for backwards compatibility with the old code that
-- explicitly registered widgets.  All tracking is now handled by parenting
-- the widget to the correct container, so explicit registration is no longer
-- necessary.
local _Section_Track = function(_, _) end

local function MakeSep(parent, yOffset)
    parent, yOffset = _SectionRedirect(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

-- ── HorizonSuite-style Section Accordion Cards ─────────────────────────────
-- Jede Section ist ein eigenes Card-Frame (kachel-förmig mit Backdrop),
-- verankert per SetPoint-Kette an der vorherigen Card.  Widgets werden beim
-- Erzeugen in `card.settingsContainer` umgehängt (siehe `_SectionRedirect`
-- oben + die SetPoint-/CreateFontString-Hooks).  Beim Einklappen setzen wir
-- `card:SetHeight(SECTION_HEADER_H)` → dank `SetClipsChildren(true)` wird der
-- Inhalt sauber ausgeblendet und alle folgenden Cards rutschen automatisch
-- nach oben, weil sie per Anchor-Kette hängen. Genau das, was HorizonSuite
-- in `DashboardDetailView.CreateAccordionCard` macht.

-- Easing: smooth in-out (matches HorizonSuite's SetSmoothing("IN_OUT"))
local function _SectionEase(t)
    if t < 0.5 then return 2 * t * t end
    return 1 - ((-2 * t + 2) ^ 2) / 2
end

-- Animated height transition à la HorizonSuite's CreateAccordionCard.
-- The card height is interpolated over SECTION_ANIM_DUR seconds; while that
-- runs, the scroll-thumb is also refreshed every frame so the scrollbar
-- tracks the growing/shrinking pane cleanly.
local SECTION_ANIM_DUR = 0.18
local function _Section_AnimateCard(pane, sec, targetH, onDone)
    local card = sec.card
    if not card then return end
    card._animFrame = card._animFrame or _origCreateFrame("Frame", nil, card)
    local af     = card._animFrame
    local startH = card:GetHeight()
    local elapsed = 0
    if math.abs(targetH - startH) < 0.5 then
        card:SetHeight(targetH)
        if onDone then onDone() end
        return
    end
    af:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + (dt or 0)
        local t = math.min(1, elapsed / SECTION_ANIM_DUR)
        local p = _SectionEase(t)
        local cur = startH + (targetH - startH) * p
        card:SetHeight(cur)
        if sec.settingsContainer then
            sec.settingsContainer:SetAlpha(sec.collapsed and (1 - p) or p)
        end
        if pane._RecomputeContentH then pane._RecomputeContentH() end
        if pane._updateThumb then pane._updateThumb() end
        if t >= 1 then
            self:SetScript("OnUpdate", nil)
            card:SetHeight(targetH)
            if sec.settingsContainer then
                sec.settingsContainer:SetAlpha(sec.collapsed and 0 or 1)
            end
            if onDone then onDone() end
        end
    end)
end

-- Set card height based on collapse state and refresh chevron + scrollbar.
local function _Section_SetCollapsed(pane, sec, collapsed, skipPersist)
    if sec.collapsed == collapsed then return end
    sec.collapsed = collapsed

    if sec.chevron and sec.chevron.SetAtlas then
        sec.chevron:SetAtlas(collapsed
            and "friendslist-categorybutton-arrow-right"
            or  "friendslist-categorybutton-arrow-down")
    end

    local targetH = collapsed and SECTION_HEADER_H or (sec.fullHeight or SECTION_HEADER_H + 120)

    if skipPersist then
        -- Initial apply on pane-show: snap without animation so the user
        -- doesn't see every pane "roll up" on every tab switch.
        if sec.card then sec.card:SetHeight(targetH) end
        if sec.settingsContainer then sec.settingsContainer:SetAlpha(collapsed and 0 or 1) end
        if pane._RecomputeContentH then pane._RecomputeContentH() end
        if pane._updateThumb then
            if C_Timer and C_Timer.After then C_Timer.After(0, pane._updateThumb) else pane._updateThumb() end
        end
    else
        _Section_AnimateCard(pane, sec, targetH)
    end

    -- Intentionally not persisted:
    -- section collapse state should reset to defaults after /reload.
end

-- Computes each section's content height from the distance between section
-- starts (in caller-yo units).  Runs once per pane on first OnShow.
local function _Section_FinalizePane(pane)
    if not pane._sections or #pane._sections == 0 then return end
    if pane._sectionsFinalized then return end
    pane._sectionsFinalized = true

    local paneEndY = -(math.max(120, (pane._contentH or 120) - (PAD + 40)))

    for i, sec in ipairs(pane._sections) do
        local nextStart = pane._sections[i + 1] and pane._sections[i + 1].startY or paneEndY
        local contentH  = math.max(32, math.abs(sec.startY - nextStart) - SECTION_HEADER_H - SECTION_CARD_GAP)
        sec.contentH    = contentH
        sec.fullHeight  = SECTION_HEADER_H + contentH
        if sec.settingsContainer then sec.settingsContainer:SetHeight(contentH) end
        if sec.card then sec.card:SetHeight(sec.collapsed and SECTION_HEADER_H or sec.fullHeight) end
    end

    -- Helper used by _Section_SetCollapsed to refresh the scroll thumb.
    pane._RecomputeContentH = function()
        local total = pane._sections[1] and math.abs(pane._sections[1].startY) or 0
        for i, sec in ipairs(pane._sections) do
            total = total + (sec.collapsed and SECTION_HEADER_H or (sec.fullHeight or SECTION_HEADER_H))
            if i < #pane._sections then total = total + SECTION_CARD_GAP end
        end
        pane._contentH = total + PAD + 40
    end
    pane._RecomputeContentH()
end

-- Apply persisted collapse states once a pane is fully built and first shown.
-- Sections mit Reset-Icon sind per Default zugeklappt (HorizonSuite-Stil),
-- außer der Nutzer hat sie bereits einmal aufgeklappt.
local function _Section_ApplyPersisted(pane)
    if not pane._sections or not pane._paneKey then return end
    -- After /reload: only sections with reset icon default to collapsed
    -- (sec._defaultCollapsed). Others stay expanded so controls stay visible.
    -- Section states are intentionally not persisted.
    for _, sec in ipairs(pane._sections) do
        _Section_SetCollapsed(pane, sec, sec._defaultCollapsed == true, true)
    end
end

-- Creates a card frame directly anchored to the pane (first card) or chained
-- below the previous card.  All subsequent widgets built for this section
-- will be automatically reparented into `card.settingsContainer` (via the
-- CreateFrame-shadow / CreateFontString-hook), so collapsing the card by
-- shrinking its height + SetClipsChildren(true) makes them disappear
-- reliably – the exact technique used by HorizonSuite.
local function MakeSectionHeader(parent, text, x, y, noTopLine, resetFn)
    local secKey = tostring(text) .. "@" .. tostring(y)

    -- Tracking/redirect muss aus sein, solange wir Header-Widgets erzeugen –
    -- sonst wandern Label, Chevron & Reset-Icon auch in den settingsContainer
    -- und werden beim Einklappen versteckt.
    local _savedSuspend = parent._suspendSectionTracking
    parent._suspendSectionTracking = true

    -- ── The card itself ────────────────────────────────────────────────
    local card = _origCreateFrame("Frame", nil, parent, "BackdropTemplate")
    card:SetClipsChildren(true)
    if parent._lastCard then
        card:SetPoint("TOPLEFT",  parent._lastCard, "BOTTOMLEFT",  0, -SECTION_CARD_GAP)
        card:SetPoint("TOPRIGHT", parent._lastCard, "BOTTOMRIGHT", 0, -SECTION_CARD_GAP)
    else
        card:SetPoint("TOPLEFT",  parent, "TOPLEFT",  0, y)
        card:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 0, y)
    end
    card:SetHeight(SECTION_HEADER_H + 120)          -- placeholder, recomputed in finalize

    -- Backdrop (dark tile, same palette HorizonSuite uses for its section cards)
    local cBg = card:CreateTexture(nil, "BACKGROUND")
    cBg:SetAllPoints()
    cBg:SetColorTexture(0.09, 0.09, 0.11, 0.55)

    local cBdr = card:CreateTexture(nil, "BORDER")
    cBdr:SetPoint("TOPLEFT",     card, "TOPLEFT",      0, 0)
    cBdr:SetPoint("BOTTOMRIGHT", card, "TOPRIGHT",     0, -1)
    cBdr:SetColorTexture(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.18)

    -- ── Header (always visible, sits at the top of the card) ───────────
    local header = _origCreateFrame("Button", nil, card)
    header:SetPoint("TOPLEFT",  card, "TOPLEFT",  0, 0)
    header:SetPoint("TOPRIGHT", card, "TOPRIGHT", 0, 0)
    header:SetHeight(SECTION_HEADER_H - 6)
    header:SetFrameLevel(card:GetFrameLevel() + 5)

    -- Optional reset icon (only for sections passed a resetFn)
    local ICON_SIZE = 14
    local labelX    = x
    if resetFn then
        local hdrBtn = _origCreateFrame("Button", nil, card)
        hdrBtn:SetSize(ICON_SIZE, ICON_SIZE)
        hdrBtn:SetPoint("TOPLEFT", card, "TOPLEFT", x, -13)
        hdrBtn:SetFrameLevel(header:GetFrameLevel() + 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"], C.accentHi[1], C.accentHi[2], C.accentHi[3], 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"],
                button1 = L["RESET_SECTION_WARN_BTN"],
                button2 = CANCEL,
                OnAccept = function() resetFn() end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show(dialogKey)
        end)

        labelX = x + ICON_SIZE + 4
    end

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

    local CHEV_SIZE = 11
    local chev = card:CreateTexture(nil, "OVERLAY")
    chev:SetSize(CHEV_SIZE, CHEV_SIZE)
    chev:SetPoint("TOPRIGHT", card, "TOPRIGHT", -PAD - 2, -15)
    if chev.SetAtlas then chev:SetAtlas("friendslist-categorybutton-arrow-down") end
    chev:SetVertexColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.85)

    -- Gradient divider between header and content
    local divider = card:CreateTexture(nil, "ARTWORK")
    divider:SetHeight(1)
    divider:SetPoint("TOPLEFT",  card, "TOPLEFT",  PAD, -(SECTION_HEADER_H - 6))
    divider:SetPoint("TOPRIGHT", card, "TOPRIGHT", -PAD, -(SECTION_HEADER_H - 6))
    divider:SetGradient("HORIZONTAL",
        CreateColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.6),
        CreateColor(0, 0, 0, 0))

    -- ── Content container (everything collapsable lives here) ──────────
    local sc = _origCreateFrame("Frame", nil, card)
    sc:SetPoint("TOPLEFT",  card, "TOPLEFT",  0, -SECTION_HEADER_H)
    sc:SetPoint("TOPRIGHT", card, "TOPRIGHT", 0, -SECTION_HEADER_H)
    sc:SetHeight(120)

    parent._sections = parent._sections or {}
    local sec = {
        key               = secKey,
        label             = lbl,
        chevron           = chev,
        card              = card,
        settingsContainer = sc,
        startY            = y,
        y                 = y,            -- kept for backwards-compat
        collapsed         = false,
        _defaultCollapsed = (resetFn ~= nil),
    }
    tinsert(parent._sections, sec)
    parent._lastCard      = card
    parent._activeSection = sec
    parent._suspendSectionTracking = _savedSuspend

    header:SetScript("OnEnter", function()
        lbl:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        chev:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        cBg:SetColorTexture(0.12, 0.12, 0.14, 0.7)
    end)
    header:SetScript("OnLeave", function()
        lbl:SetTextColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 1)
        chev:SetVertexColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.85)
        cBg:SetColorTexture(0.09, 0.09, 0.11, 0.55)
    end)
    header:SetScript("OnClick", function()
        if CloseDropdownPopup then CloseDropdownPopup() end
        _Section_FinalizePane(parent)
        _Section_SetCollapsed(parent, sec, not sec.collapsed)
    end)

    return lbl
end

local function MakeInfoText(parent, text, x, y, w)
    parent, y = _SectionRedirect(parent, y)
    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

-- ---- Checkbox (plain frame, fixed gray border, checkmark overlay) -----------------------
-- desc (3rd arg = tooltip string) is shown as small static text below the label.
-- No GameTooltip on the box; border color does not change when checked;
-- only the red check texture toggles. Border goes red on hover only.
local function MakeCheckbox(parent, labelText, desc, x, y)
    parent, y = _SectionRedirect(parent, y)
    local con = _origCreateFrame("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 (red on hover only)
    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 below label
    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: toggle check only; border stays gray.
    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

-- ---- Slider (full width, track with border) -----------------
-- Returns: slider, label
local function MakeSlider(parent, labelText, tipText, minV, maxV, step, x, y, width)
    parent, y = _SectionRedirect(parent, y)
    -- 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 full width (6 px 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,
            previewTexture     = info.previewTexture,
            previewOrbTemplate = info.previewOrbTemplate,
        })
    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

-- Hover preview panel (textures + orb template 3D)
local DropdownPreview = {}
do
    local root, titleFs, texPrev, modelPrev, texBg
    local PV_W, PV_H = 216, 228
    local TEX_SZ = 196

    local function PreviewApplyOrbParams(m, tmpl)
        if ns.OrbTemplateEngine and ns.OrbTemplateEngine.ApplyModelPreview then
            ns.OrbTemplateEngine:ApplyModelPreview(m, tmpl)
            return
        end
        if ns.ApplyOrbModelPlacement then
            ns.ApplyOrbModelPlacement(nil, m, tmpl, "HEALTH", true)
        end
    end

    function DropdownPreview:Hide()
        if root then root:Hide() end
    end

    local function Ensure()
        if root then return end
        root = CreateFrame("Frame", "rOrbsDropdownPreview", UIParent)
        root:SetFrameStrata("TOOLTIP")
        root:SetFrameLevel(250)
        root:SetSize(PV_W, PV_H)
        root:EnableMouse(false)
        root:Hide()

        local bg = root:CreateTexture(nil, "BACKGROUND")
        bg:SetAllPoints()
        bg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.98)

        local function Edge(pt, rpt, w, h)
            local e = root:CreateTexture(nil, "BORDER")
            e:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.9)
            e:SetSize(w, h)
            e:SetPoint(pt, root, rpt, 0, 0)
        end
        Edge("TOPLEFT", "TOPLEFT", PV_W, 1)
        Edge("BOTTOMLEFT", "BOTTOMLEFT", PV_W, 1)
        Edge("TOPLEFT", "TOPLEFT", 1, PV_H)
        Edge("TOPRIGHT", "TOPRIGHT", 1, PV_H)

        titleFs = root:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        titleFs:SetPoint("TOP", root, "TOP", 0, -6)
        titleFs:SetWidth(PV_W - 8)
        titleFs:SetJustifyH("CENTER")
        titleFs:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3])

        texBg = root:CreateTexture(nil, "BACKGROUND")
        texBg:SetSize(TEX_SZ, TEX_SZ)
        texBg:SetPoint("TOP", root, "TOP", 0, -26)
        texBg:SetColorTexture(0.12, 0.12, 0.14, 1)

        texPrev = root:CreateTexture(nil, "ARTWORK")
        texPrev:SetSize(TEX_SZ - 2, TEX_SZ - 2)
        texPrev:SetPoint("CENTER", texBg, "CENTER", 0, 0)

        modelPrev = CreateFrame("PlayerModel", nil, root)
        modelPrev:SetSize(TEX_SZ, TEX_SZ)
        modelPrev:SetPoint("TOP", root, "TOP", 0, -26)
        modelPrev:EnableMouse(false)
        modelPrev:SetPortraitZoom(0)
    end

    function DropdownPreview:ShowTexture(path, anchorRow, nameText)
        if not path or path == "" then self:Hide() return end
        Ensure()
        titleFs:SetText(L["DROPDOWN_PREVIEW_FMT"]:format(nameText or ""))
        texBg:Show()
        texPrev:Show()
        texPrev:SetTexture(path)
        modelPrev:Hide()
        modelPrev:ClearModel()
        root:SetPoint("TOPLEFT", anchorRow, "TOPRIGHT", 10, 0)
        root:Show()
    end

    function DropdownPreview:ShowOrbTemplate(tmpl, anchorRow, nameText)
        if not tmpl or (tmpl.displayInfoID or 0) <= 0 then self:Hide() return end
        Ensure()
        titleFs:SetText(L["DROPDOWN_PREVIEW_FMT"]:format(nameText or ""))
        texBg:Hide()
        texPrev:Hide()
        modelPrev:Show()

        local mid = tmpl.displayInfoID
        modelPrev._pvTmplId = mid
        modelPrev:ClearModel()
        modelPrev:SetAlpha(0)
        modelPrev:SetDisplayInfo(mid)

        modelPrev:SetScript("OnModelLoaded", function(self)
            if self._pvTmplId ~= mid then return end
            if (self:GetDisplayInfo() or 0) == 0 then return end
            local nx, ny, nz = 0, 0, 0
            if self.GetCameraTarget then nx, ny, nz = self:GetCameraTarget() end
            self._natX, self._natY, self._natZ = nx, ny, nz
            PreviewApplyOrbParams(self, tmpl)
            self:SetScript("OnModelLoaded", nil)
        end)

        C_Timer.After(0.55, function()
            if not modelPrev or not modelPrev:IsShown() then return end
            if modelPrev._pvTmplId ~= mid then return end
            if (modelPrev:GetDisplayInfo() or 0) == 0 then return end
            local nx, ny, nz = 0, 0, 0
            if modelPrev.GetCameraTarget then nx, ny, nz = modelPrev:GetCameraTarget() end
            modelPrev._natX, modelPrev._natY, modelPrev._natZ = nx, ny, nz
            PreviewApplyOrbParams(modelPrev, tmpl)
        end)

        root:SetPoint("TOPLEFT", anchorRow, "TOPRIGHT", 10, 0)
        root:Show()
    end
end

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

-- Simple context menu anchored at the cursor (items: {text, func, disabled})
local function ShowCursorPopupMenu(items, width)
    CloseDropdownPopup()
    if not items or #items == 0 then return end

    width = width or 200
    local itemH = 22
    local listH = #items * itemH + 4
    local popH  = listH

    local cx, cy = GetCursorPosition()
    local scale  = UIParent:GetEffectiveScale()
    cx, cy = cx / scale, cy / scale

    local pop = CreateFrame("Frame", nil, UIParent)
    pop:SetFrameStrata("TOOLTIP")
    pop:SetFrameLevel(200)
    pop:SetSize(width, popH)
    pop:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", cx, cy)
    pop:SetClipsChildren(true)

    local bg = pop:CreateTexture(nil, "BACKGROUND")
    bg:SetAllPoints()
    bg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.98)

    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",     width, 1)
    PopEdge("BOTTOMLEFT",  "BOTTOMLEFT",  width, 1)

    local content = CreateFrame("Frame", nil, pop)
    content:SetSize(width - 2, listH)
    content:SetPoint("TOPLEFT", pop, "TOPLEFT", 1, -1)

    for idx, item in ipairs(items) do
        local row = CreateFrame("Button", nil, content)
        row:SetSize(width - 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

        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()
                rowBg:SetColorTexture(0, 0, 0, 0)
                rowTxt:SetTextColor(C.text[1], C.text[2], C.text[3])
            end)
            row:SetScript("OnClick", function()
                if item.func then item.func() end
                CloseDropdownPopup()
            end)
        end
    end

    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

    C_Timer.After(0, function()
        if not pop or not pop:IsShown() then return end
        local right = pop:GetRight()
        if right and right > UIParent:GetWidth() then
            pop:ClearAllPoints()
            pop:SetPoint("TOPRIGHT", UIParent, "BOTTOMLEFT", cx, cy)
        end
        local top = pop:GetTop()
        if top and top > UIParent:GetHeight() then
            pop:ClearAllPoints()
            pop:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", cx, cy)
        end
    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
    -- HorizonSuite-style live filter at the top of longer lists.
    local needsSearch = #items >= 8
    local SEARCH_H   = needsSearch and 24 or 0
    local SEARCH_PAD = needsSearch and 2  or 0
    local listH = math.min(#items, visMax) * itemH + 4
    local needsScroll = #items > visMax
    local scrollBarW = needsScroll and 10 or 0
    local popH   = SEARCH_H + SEARCH_PAD + listH

    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)

    -- ── Sticky search field at the top (HorizonSuite-style) ────────────────
    local ddSearchBox
    if needsSearch then
        local sBg = pop:CreateTexture(nil, "BACKGROUND", nil, 1)
        sBg:SetHeight(SEARCH_H)
        sBg:SetPoint("TOPLEFT",  pop, "TOPLEFT",  1, -1)
        sBg:SetPoint("TOPRIGHT", pop, "TOPRIGHT", -1, -1)
        sBg:SetColorTexture(0.04, 0.04, 0.05, 1)

        local sBottom = pop:CreateTexture(nil, "BORDER")
        sBottom:SetHeight(1)
        sBottom:SetPoint("TOPLEFT",  pop, "TOPLEFT",  1, -(SEARCH_H + 1))
        sBottom:SetPoint("TOPRIGHT", pop, "TOPRIGHT", -1, -(SEARCH_H + 1))
        sBottom:SetColorTexture(C.accent[1] * 0.4, C.accent[2] * 0.4, C.accent[3] * 0.4, 1)

        local sIcon = pop:CreateTexture(nil, "OVERLAY")
        sIcon:SetSize(12, 12)
        sIcon:SetPoint("TOPLEFT", pop, "TOPLEFT", 6, -((SEARCH_H - 12) / 2) - 1)
        if sIcon.SetAtlas then sIcon:SetAtlas("common-search-magnifyingglass") end
        sIcon:SetVertexColor(C.textDim[1], C.textDim[2], C.textDim[3], 0.85)

        ddSearchBox = CreateFrame("EditBox", nil, pop)
        ddSearchBox:SetFontObject(GameFontHighlightSmall)
        ddSearchBox:SetHeight(SEARCH_H - 4)
        ddSearchBox:SetPoint("TOPLEFT",  pop, "TOPLEFT",  22,  -3)
        ddSearchBox:SetPoint("TOPRIGHT", pop, "TOPRIGHT", -6,  -3)
        ddSearchBox:SetAutoFocus(true)
        ddSearchBox:SetMaxLetters(40)
        ddSearchBox:SetTextInsets(0, 0, 0, 0)
        ddSearchBox:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
        ddSearchBox:SetScript("OnEscapePressed", function() CloseDropdownPopup() end)
        ddSearchBox:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
    end

    -- Scroll frame for many items (sits below the search field)
    local sf   = CreateFrame("ScrollFrame", nil, pop)
    sf:SetPoint("TOPLEFT",     pop, "TOPLEFT",     1, -(SEARCH_H + SEARCH_PAD))
    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)


    -- ── Draggable scrollbar (only when list exceeds visMax) ────────────────
    local thumbBtn = nil
    local thumbH   = 0
    local TRACK_PAD_TOP    = SEARCH_H + SEARCH_PAD + 2
    local TRACK_PAD_BOTTOM = 2
    local trackH = popH - TRACK_PAD_TOP - TRACK_PAD_BOTTOM
    -- Visible content length in px (used to compute max scroll and thumb size).
    -- Starts at full item count; shrinks when the filter hides rows.
    local visibleItems = #items
    local function CurrentContentH() return visibleItems * itemH end
    local function CurrentMaxScroll() return math.max(0, CurrentContentH() - listH + 4) end

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

    local function UpdateThumbPos()
        if not thumbBtn then return end
        local ms = CurrentMaxScroll()
        if ms <= 0 then
            thumbBtn:Hide()
            return
        end
        thumbBtn:Show()
        local contentH = CurrentContentH()
        thumbH = math.max(16, math.floor((listH / math.max(contentH, listH)) * trackH))
        thumbBtn:SetHeight(thumbH)
        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((listH / (#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()
            local ms = CurrentMaxScroll()
            if scaledMouseY > tCY then
                -- click above thumb → scroll up
                local newScroll = math.max(0, sf:GetVerticalScroll() - (listH - itemH))
                sf:SetVerticalScroll(newScroll)
            else
                -- click below thumb → scroll down
                local newScroll = math.min(ms, sf:GetVerticalScroll() + (listH - 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 ms = CurrentMaxScroll()
            local scrollDelta = (delta / travelRange) * ms
            local newScroll = math.max(0, math.min(ms, _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 ms = CurrentMaxScroll()
        local newScroll = math.max(0, math.min(ms, 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

    -- Build all rows once, keep references for live filter.
    local rows = {}
    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])
                if item.previewTexture and item.previewTexture ~= "" then
                    DropdownPreview:ShowTexture(item.previewTexture, row, item.text)
                elseif item.previewOrbTemplate then
                    DropdownPreview:ShowOrbTemplate(item.previewOrbTemplate, row, item.text)
                end
            end)
            row:SetScript("OnLeave", function()
                DropdownPreview:Hide()
                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

        rows[#rows + 1] = { row = row, item = item, textLower = (item.text or ""):lower() }
    end

    -- Live filter: rebuild layout from the visible rows on each key stroke.
    local function ApplyFilter(query)
        query = query and query:lower() or ""
        local visIdx = 0
        for _, r in ipairs(rows) do
            local match = query == "" or r.textLower:find(query, 1, true) ~= nil
            if match then
                r.row:Show()
                r.row:ClearAllPoints()
                r.row:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -visIdx * itemH)
                visIdx = visIdx + 1
            else
                r.row:Hide()
            end
        end
        visibleItems = visIdx
        content:SetHeight(math.max(visIdx * itemH, 1))
        sf:SetVerticalScroll(0)
        UpdateThumbPos()
    end

    if ddSearchBox then
        ddSearchBox:SetScript("OnTextChanged", function(self)
            ApplyFilter(self:GetText())
        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 = listH - 4
        -- Center the selected row in the visible area
        local targetScroll = selTopY - math.floor((visHeight - itemH) / 2)
        local ms = CurrentMaxScroll()
        local initScroll = math.max(0, math.min(ms, 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
    parent, y = _SectionRedirect(parent, y)

    -- 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)
    parent, y = _SectionRedirect(parent, y)
    local btn = _origCreateFrame("Button", nil, parent)
    btn:SetSize(22, 22)
    btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
    local _swatchLbl

    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])
        _swatchLbl = lbl
    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]
    parent, y = _SectionRedirect(parent, y)
    local btn = _origCreateFrame("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 width from text (C_Timer for StringWidth)
    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"])
            SetTextureAccentDanger(self.innerTex)
            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 (GUI accent colors)
-- ============================================================
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, C.accent[2]*0.6, C.accent[3]*0.6, 1)

        -- Title bar
        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.22, C.accent[2]*0.22, C.accent[3]*0.22, 1)
        tBg:SetBackdropBorderColor(C.accent[1]*0.48, C.accent[2]*0.48, C.accent[3]*0.48, 1)
        -- Left red accent stripe
        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)
        -- Title text
        local tLbl = tBg:CreateFontString(nil, "OVERLAY", "GameFontNormal")
        tLbl:SetTextColor(1, 1, 1, 1)
        -- Logo in title bar
        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"])
        -- Description text
        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.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
        reloadBtn:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 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.42, C.accent[2]*0.42, C.accent[3]*0.42, 1)
            s:SetBackdropBorderColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
            rLbl:SetTextColor(1, 1, 1, 1)
        end)
        reloadBtn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(C.accent[1]*0.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
            s:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 1)
            rLbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        end)
        reloadBtn:SetScript("OnClick", function() dlg:Hide(); ReloadUI() end)
        -- Cancel 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], C.accentHi[2], C.accentHi[3], 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 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

local function EnsureOrbCharCfg(cfgKey)
    local db2 = ns.db
    if db2 and cfgKey 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

local function CreateMainPanel()
    if Panel then return end

    ns.ApplyGuiClassTheme(C)

    -- Saved window size (defaults = current design size)
    do
        local rwMin, rhMin, rwMax, rhMax = 800, 520, 1900, 1100
        PANEL_W = math.max(rwMin, math.min(rwMax, math.floor(((rOrbsDB and rOrbsDB.guiFrameW) or PANEL_W_DEFAULT) + 0.5)))
        PANEL_H = math.max(rhMin, math.min(rhMax, math.floor(((rOrbsDB and rOrbsDB.guiFrameH) or PANEL_H_DEFAULT) + 0.5)))
    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 selbst ist draggable (HorizonSuite-Style – keine Titelleiste mehr).
    -- Interaktive Kinder (Buttons/Checkboxen/…) absorbieren Klicks vor Panel.
    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
            local spellPick = _G["rOrbsClickCastSpellPicker"]
            if spellPick and spellPick:IsShown() then
                spellPick:Hide()
            elseif _ddOpenFrame then
                CloseDropdownPopup()   -- dropdown open → close dropdown only, keep GUI
            else
                ns.GUI:Close()         -- no dropdown → close GUI
            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(
        math.min(1, C.accent[1] * 0.52 + 0.06),
        math.min(1, C.accent[2] * 0.52 + 0.06),
        math.min(1, C.accent[3] * 0.52 + 0.06), 1)
    Panel:SetAlpha(0)
    Panel:Hide()
    tinsert(UISpecialFrames, "rOrbsSettingsPanel")

    -- ── CLOSE: floating "x" oben rechts (HorizonSuite-Style) ────────────────
    local closeBtn = CreateFrame("Button", nil, Panel)
    closeBtn:SetSize(26, 26)
    closeBtn:SetPoint("TOPRIGHT", Panel, "TOPRIGHT", -14, -6)
    closeBtn:SetFrameLevel(Panel:GetFrameLevel() + 20)

    local closeBg = closeBtn:CreateTexture(nil, "BACKGROUND")
    closeBg:SetAllPoints()
    closeBg:SetColorTexture(0, 0, 0, 0)

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

    closeBtn:SetScript("OnEnter", function()
        closeBg:SetColorTexture(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        closeLbl:SetTextColor(1, 1, 1, 1)
    end)
    closeBtn:SetScript("OnLeave", function()
        closeBg:SetColorTexture(0, 0, 0, 0)
        closeLbl:SetTextColor(0.85, 0.85, 0.88, 1)
    end)
    closeBtn:SetScript("OnClick", function() ns.GUI:Close() end)

    -- ── SEARCH (HorizonSuite-style; jumps to tab/sub-tab) ───────────────────
    local searchShell = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    searchShell:SetHeight(22)
    searchShell:SetPoint("RIGHT",  closeBtn,  "LEFT",   -10, 0)
    searchShell:SetWidth(280)
    searchShell:SetBackdrop({
        bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Buttons\\WHITE8X8",
        edgeSize=1, insets={left=0,right=0,top=0,bottom=0},
    })
    searchShell:SetBackdropColor(0.06, 0.06, 0.072, 0.92)
    searchShell:SetBackdropBorderColor(C.accent[1]*0.45, C.accent[2]*0.45, C.accent[3]*0.45, 1)

    local searchIcon = searchShell:CreateTexture(nil, "OVERLAY")
    searchIcon:SetSize(12, 12)
    searchIcon:SetPoint("LEFT", searchShell, "LEFT", 6, 0)
    -- Blizzard atlas magnifier; silently empty if not present on client
    if searchIcon.SetAtlas then searchIcon:SetAtlas("common-search-magnifyingglass") end
    searchIcon:SetVertexColor(C.accent[1]*0.9, C.accent[2]*0.9, C.accent[3]*0.9, 1)

    local searchBox = CreateFrame("EditBox", nil, searchShell)
    searchBox:SetAutoFocus(false)
    searchBox:SetFontObject(GameFontHighlightSmall)
    searchBox:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
    searchBox:SetPoint("LEFT",  searchIcon, "RIGHT",   6, 0)
    searchBox:SetPoint("RIGHT", searchShell, "RIGHT", -22, 0)
    searchBox:SetHeight(20)
    searchBox:SetMaxLetters(64)

    local searchPlaceholder = searchShell:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    searchPlaceholder:SetPoint("LEFT", searchBox, "LEFT", 0, 0)
    searchPlaceholder:SetText(L["SEARCH_PLACEHOLDER"] or "Search options...")
    searchPlaceholder:SetTextColor(0.5, 0.5, 0.55, 0.85)

    local searchClear = CreateFrame("Button", nil, searchShell)
    searchClear:SetSize(16, 16)
    searchClear:SetPoint("RIGHT", searchShell, "RIGHT", -4, 0)
    searchClear:Hide()
    local scTex = searchClear:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    scTex:SetAllPoints()
    scTex:SetText("x")
    scTex:SetTextColor(0.75, 0.45, 0.45, 1)
    searchClear:SetScript("OnEnter", function() scTex:SetTextColor(1, 0.55, 0.55, 1) end)
    searchClear:SetScript("OnLeave", function() scTex:SetTextColor(0.75, 0.45, 0.45, 1) end)

    Panel.searchShell = searchShell
    Panel.searchBox   = searchBox

    -- Dropdown öffnet nach oben (Suchfeld sitzt in der Bottom-Bar)
    local searchDrop = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    searchDrop:SetFrameStrata(Panel:GetFrameStrata())
    searchDrop:SetFrameLevel(Panel:GetFrameLevel() + 30)
    searchDrop:SetWidth(340)
    searchDrop:SetPoint("BOTTOMLEFT",  searchShell, "TOPLEFT",  0, 6)
    searchDrop:SetBackdrop({
        bgFile="Interface\\Buttons\\WHITE8X8",
        edgeFile="Interface\\Buttons\\WHITE8X8",
        edgeSize=1, insets={left=0,right=0,top=0,bottom=0},
    })
    searchDrop:SetBackdropColor(0.045, 0.045, 0.055, 0.96)
    searchDrop:SetBackdropBorderColor(C.accent[1]*0.55, C.accent[2]*0.55, C.accent[3]*0.55, 1)
    searchDrop:Hide()

    local dropRows = {}
    local DROP_ROW_H = 38
    local DROP_MAX_ROWS = 8

    local emptyHint = searchDrop:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
    emptyHint:SetPoint("CENTER", searchDrop, "CENTER", 0, 0)
    emptyHint:SetText(L["SEARCH_NO_RESULTS"] or "No matches")
    emptyHint:Hide()

    local function MakeDropRow(i)
        local r = CreateFrame("Button", nil, searchDrop)
        r:SetHeight(DROP_ROW_H)
        r:SetPoint("LEFT",  searchDrop, "LEFT",   1, 0)
        r:SetPoint("RIGHT", searchDrop, "RIGHT", -1, 0)

        local rbg = r:CreateTexture(nil, "BACKGROUND"); rbg:SetAllPoints()
        rbg:SetColorTexture(0, 0, 0, 0)

        local bar = r:CreateTexture(nil, "ARTWORK")
        bar:SetWidth(2)
        bar:SetPoint("TOPLEFT",    r, "TOPLEFT",    1, -4)
        bar:SetPoint("BOTTOMLEFT", r, "BOTTOMLEFT", 1,  4)
        bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)

        local lbl = r:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        lbl:SetPoint("TOPLEFT", r, "TOPLEFT", 10, -6)
        lbl:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
        lbl:SetJustifyH("LEFT")

        local sub = r:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
        sub:SetPoint("TOPLEFT", lbl, "BOTTOMLEFT", 0, -1)
        sub:SetPoint("RIGHT",   r,   "RIGHT",     -8, 0)
        sub:SetTextColor(0.55, 0.55, 0.6, 1)
        sub:SetJustifyH("LEFT")

        r.bg = rbg; r.bar = bar; r.lbl = lbl; r.sub = sub
        r:SetScript("OnEnter", function(s)
            s.bg:SetColorTexture(C.accent[1]*0.18, C.accent[2]*0.18, C.accent[3]*0.18, 1)
            s.bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1)
            s.lbl:SetTextColor(1, 1, 1, 1)
        end)
        r:SetScript("OnLeave", function(s)
            s.bg:SetColorTexture(0, 0, 0, 0)
            s.bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0)
            s.lbl:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
        end)
        return r
    end

    local function HideDropdown()
        searchDrop:Hide()
        emptyHint:Hide()
    end
    Panel.HideSearchDropdown = HideDropdown

    local function NavigateToResult(entry)
        if not entry or not entry.tabKey then return end
        ns.GUI:SetTab(entry.tabKey)
        if entry.subKey and Panel.SetSubTab then
            Panel.SetSubTab(entry.tabKey, entry.subKey)
        end
        searchBox:SetText("")
        searchBox:ClearFocus()
        searchPlaceholder:Show()
        searchClear:Hide()
        HideDropdown()
    end

    --- Lazy-build search index from sidebarStructure + subNavData (available after sub-nav setup).
    --- Each entry: { tabKey, subKey?, label, tabLabel, haystack (lowercase combined) }
    local function BuildSearchIndex()
        local idx = {}
        local function tabLabelFor(tabKey)
            local t = Panel.tabs and Panel.tabs[tabKey]
            return t and t.label or tabKey
        end
        for _, entry in ipairs(Panel.sidebarStructure or {}) do
            if entry.type == "tab" then
                local lbl = entry.label or entry.key
                tinsert(idx, {
                    tabKey = entry.key, subKey = nil,
                    label = lbl, tabLabel = lbl,
                    haystack = string.lower(lbl .. " " .. entry.key),
                })
            elseif entry.type == "group" and entry.children then
                for _, ch in ipairs(entry.children) do
                    tinsert(idx, {
                        tabKey = ch.key, subKey = nil,
                        label = ch.label or ch.key, tabLabel = ch.label or ch.key,
                        haystack = string.lower((ch.label or "") .. " " .. ch.key .. " " .. (entry.label or "")),
                    })
                end
            end
        end
        local subNavData = Panel.subNavData
        if subNavData then
            for tabKey, list in pairs(subNavData) do
                local parentLabel = tabLabelFor(tabKey)
                for _, sd in ipairs(list) do
                    if sd.key ~= "reset" then
                        tinsert(idx, {
                            tabKey = tabKey, subKey = sd.key,
                            label = sd.label or sd.key, tabLabel = parentLabel,
                            haystack = string.lower((sd.label or "") .. " " .. sd.key .. " " .. parentLabel),
                        })
                    end
                end
            end
        end
        return idx
    end

    local searchIndexCache = nil
    local function GetSearchIndex()
        searchIndexCache = searchIndexCache or BuildSearchIndex()
        return searchIndexCache
    end
    Panel.InvalidateSearchIndex = function() searchIndexCache = nil end

    local function RunSearch(query)
        query = (query or ""):lower()
        query = query:gsub("^%s+", ""):gsub("%s+$", "")
        if query == "" then HideDropdown() return end
        local idx = GetSearchIndex()
        local matches = {}
        for _, e in ipairs(idx) do
            if e.haystack:find(query, 1, true) then
                tinsert(matches, e)
                if #matches >= DROP_MAX_ROWS then break end
            end
        end

        searchDrop:Show()
        -- Lazily create row buttons
        for i = #dropRows + 1, DROP_MAX_ROWS do dropRows[i] = MakeDropRow(i) end

        if #matches == 0 then
            for i, r in ipairs(dropRows) do r:Hide() end
            searchDrop:SetHeight(40)
            emptyHint:Show()
            return
        end
        emptyHint:Hide()

        for i, r in ipairs(dropRows) do
            local m = matches[i]
            if m then
                r:Show()
                r.lbl:SetText(m.label)
                if m.subKey then
                    r.sub:SetText(string.format(L["SEARCH_RESULT_IN"] or "in %s", m.tabLabel))
                    r.sub:Show()
                else
                    r.sub:SetText("")
                    r.sub:Hide()
                end
                r:SetScript("OnClick", function() NavigateToResult(m) end)
                r:ClearAllPoints()
                r:SetPoint("LEFT",  searchDrop, "LEFT",   1, 0)
                r:SetPoint("RIGHT", searchDrop, "RIGHT", -1, 0)
                r:SetPoint("TOP",   searchDrop, "TOP",    0, -((i-1) * DROP_ROW_H + 3))
            else
                r:Hide()
            end
        end
        searchDrop:SetHeight(math.max(DROP_ROW_H, #matches * DROP_ROW_H + 6))
    end

    searchBox:SetScript("OnTextChanged", function(self, userInput)
        local txt = self:GetText()
        if txt and txt ~= "" then
            searchPlaceholder:Hide()
            searchClear:Show()
        else
            searchPlaceholder:Show()
            searchClear:Hide()
        end
        if userInput then RunSearch(txt) end
    end)
    searchBox:SetScript("OnEditFocusGained", function(self)
        searchShell:SetBackdropBorderColor(C.accent[1]*0.9, C.accent[2]*0.9, C.accent[3]*0.9, 1)
        if self:GetText() ~= "" then RunSearch(self:GetText()) end
    end)
    searchBox:SetScript("OnEditFocusLost", function(self)
        searchShell:SetBackdropBorderColor(C.accent[1]*0.45, C.accent[2]*0.45, C.accent[3]*0.45, 1)
        -- Leave the dropdown visible briefly so clicks on a row register
        C_Timer.After(0.15, function()
            if not searchBox:HasFocus() then HideDropdown() end
        end)
    end)
    searchBox:SetScript("OnEscapePressed", function(self)
        self:SetText("")
        self:ClearFocus()
        searchPlaceholder:Show()
        searchClear:Hide()
        HideDropdown()
    end)
    searchBox:SetScript("OnEnterPressed", function(self)
        -- Pick the first match
        local idx = GetSearchIndex()
        local q = (self:GetText() or ""):lower():gsub("^%s+", ""):gsub("%s+$", "")
        if q == "" then return end
        for _, e in ipairs(idx) do
            if e.haystack:find(q, 1, true) then NavigateToResult(e) return end
        end
    end)

    searchClear:SetScript("OnClick", function()
        searchBox:SetText("")
        searchBox:ClearFocus()
        searchPlaceholder:Show()
        searchClear:Hide()
        HideDropdown()
    end)

    -- ── SIDEBAR ───────────────────────────────────────────────────────────────
    local SIDEBAR_W = 180

    local sidebar = CreateFrame("Frame", nil, Panel, "BackdropTemplate")
    sidebar:SetPoint("TOPLEFT",    Panel, "TOPLEFT",     2,  -2)
    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.40, C.accent[2]*0.40, C.accent[3]*0.40, 1)

    -- Custom class icon + Addon-Titel/Version (HorizonSuite-Style, unten in der Sidebar)
    do
        local ICON_SZ = 56
        local classIconHolder = CreateFrame("Frame", nil, sidebar)
        classIconHolder:SetSize(ICON_SZ, ICON_SZ)
        classIconHolder:SetPoint("BOTTOM", sidebar, "BOTTOM", 0, 10)

        local ciTex = classIconHolder:CreateTexture(nil, "ARTWORK", nil, 2)
        ciTex:SetAllPoints()
        ciTex:SetVertexColor(1, 1, 1, 1)

        if not ns.ApplyCustomClassIconToTexture(ciTex) then
            classIconHolder:Hide()
        end
        Panel.classIconHolder = classIconHolder

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

        -- Addon-Titel über der Version (größer, HorizonSuite-Style)
        local sbTitle = sidebar:CreateFontString(nil, "OVERLAY")
        sbTitle:SetFont("Fonts\\FRIZQT__.TTF", 18, "OUTLINE")
        if sbVer then
            sbTitle:SetPoint("BOTTOM", sbVer, "TOP", 0, 2)
        else
            sbTitle:SetPoint("BOTTOM", classIconHolder, "TOP", 0, 10)
        end
        sbTitle:SetText(L["SETTINGS_TITLE"])
        sbTitle:SetTextColor(1, 1, 1, 1)

        -- Dezenter Akzent-Trenner DIREKT über dem Addon-Namen
        local sbFooterDiv = sidebar:CreateTexture(nil, "ARTWORK")
        sbFooterDiv:SetHeight(1)
        sbFooterDiv:SetPoint("LEFT",   sidebar, "LEFT",   8, 0)
        sbFooterDiv:SetPoint("RIGHT",  sidebar, "RIGHT", -8, 0)
        sbFooterDiv:SetPoint("BOTTOM", sbTitle, "TOP",    0, 8)
        sbFooterDiv:SetColorTexture(C.accent[1]*0.55, C.accent[2]*0.55, C.accent[3]*0.55, 0.30)

        Panel.sidebarTitle   = sbTitle
        Panel.sidebarVersion = sbVer
    end

    -- ── 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, C.accent[2]*0.30, C.accent[3]*0.30, 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
    -- Kein Rahmen mehr – HorizonSuite-Style: nur noch Hintergrund, sauberer Übergang oben.
    contentBg:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8"})
    contentBg:SetBackdropColor(C.content[1], C.content[2], C.content[3], C.content[4])

    -- 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)

    -- ── Suchfeld ankert jetzt in der Bottom-Bar (HorizonSuite-Style) ───────
    -- (searchShell / searchDrop wurden oben erzeugt; hier nur umsetzen.)
    do
        if Panel.searchShell then
            Panel.searchShell:ClearAllPoints()
            Panel.searchShell:SetParent(bot)
            Panel.searchShell:SetWidth(170)
            Panel.searchShell:SetPoint("LEFT",  bot, "LEFT",  12, 0)
            Panel.searchShell:SetFrameLevel(bot:GetFrameLevel() + 2)
        end
        -- Dropdown jetzt nach oben öffnen
        if Panel.HideSearchDropdown then Panel.HideSearchDropdown() end
    end

    -- Panel-Scale einmalig setzen (gespeicherter Wert oder 1.0)
    do
        local function _apply()
            Panel:SetScale((rOrbsDB and rOrbsDB.guiScale) or 1.0)
        end
        Panel.SyncScale = _apply
        Panel:HookScript("OnShow", _apply)
        _apply()
    end

    -- Bottom-right resize grip: anchored inside panel corner
    do
        local RMIN_W, RMIN_H, RMAX_W, RMAX_H = 800, 520, 1900, 1100
        Panel:SetResizable(true)
        if Panel.SetResizeBounds then
            Panel:SetResizeBounds(RMIN_W, RMIN_H, RMAX_W, RMAX_H)
        end
        local grip = CreateFrame("Button", "rOrbsSettingsResizeGrip", Panel)
        grip:EnableMouse(true)
        grip:SetSize(22, 22)
        grip:SetFrameStrata(Panel:GetFrameStrata())
        grip:SetFrameLevel(Panel:GetFrameLevel() + 55)
        grip:Hide()
        local function SyncGripToPanel()
            if not Panel or not grip then return end
            grip:ClearAllPoints()
            grip:SetPoint("BOTTOMRIGHT", Panel, "BOTTOMRIGHT", 0, 0)
            local ps = Panel:GetScale() or 1
            if ps > 0 then grip:SetScale(ps) end
        end
        local gt = grip:CreateTexture(nil, "OVERLAY")
        gt:SetAllPoints()
        gt:SetTexture("Interface\\AddOns\\rOrbs\\media\\resize.tga")
        gt:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 0.9)
        grip:SetScript("OnEnter", function()
            gt:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
            GameTooltip:SetOwner(grip, "ANCHOR_LEFT")
            GameTooltip:SetText(L["GUI_RESIZE_GRIP"], 1, 1, 1)
            GameTooltip:Show()
        end)
        grip:SetScript("OnLeave", function()
            gt:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 0.9)
            GameTooltip:Hide()
        end)
        grip:RegisterForDrag("LeftButton")
        grip:SetScript("OnDragStart", function()
            Panel:StartSizing("BOTTOMRIGHT")
        end)
        grip:SetScript("OnDragStop", function()
            Panel:StopMovingOrSizing()
            if rOrbsDB then
                rOrbsDB.guiFrameW = math.min(RMAX_W, math.max(RMIN_W, math.floor(Panel:GetWidth() + 0.5)))
                rOrbsDB.guiFrameH = math.min(RMAX_H, math.max(RMIN_H, math.floor(Panel:GetHeight() + 0.5)))
                PANEL_W, PANEL_H = rOrbsDB.guiFrameW, rOrbsDB.guiFrameH
            end
            SyncGripToPanel()
        end)
        Panel:HookScript("OnShow", function()
            SyncGripToPanel()
            grip:Show()
        end)
        Panel:HookScript("OnHide", function()
            grip:Hide()
        end)
        Panel:HookScript("OnSizeChanged", SyncGripToPanel)
        -- Panel wird direkt per Drag verschoben; Grip nach Bewegung neu pinnen
        Panel:HookScript("OnDragStop", SyncGripToPanel)
        do
            local prevSyncScale = Panel.SyncScale
            if type(prevSyncScale) == "function" then
                Panel.SyncScale = function()
                    prevSyncScale()
                    SyncGripToPanel()
                end
            end
        end
        Panel._rOrbsResizeGrip = grip
    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.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
        btn:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 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 width when w not explicitly passed
        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.42, C.accent[2]*0.42, C.accent[3]*0.42, 1)
            s:SetBackdropBorderColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
            lbl:SetTextColor(1, 1, 1, 1)
        end)
        btn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(C.accent[1]*0.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
            s:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 1)
            lbl:SetTextColor(C.accent[1], C.accent[2], C.accent[3], 1)
        end)
        btn:SetScript("OnClick", onClick)
        return btn, lbl
    end

    -- ── TAB DEFINITIONS (grouped; HorizonSuite-style sidebar) ───────────────
    -- Top-level entries are either flat tabs ("tab") or collapsible groups ("group").
    -- Every group child is a real tab (same keys consumed by GUI:SetTab / contentFrames).
    local sidebarStructure = {
        { type = "tab",   key = "general",  label = L["TAB_GENERAL"], icon = "Interface\\Icons\\INV_Misc_Note_06" },
        { type = "group", key = "orbs",     label = L["SIDEBAR_GROUP_ORBS"] or "Orbs",
            icon = "Interface\\Icons\\inv_elemental_mote_life01",
            children = {
                { 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" },
            },
        },
        { type = "group", key = "tools",    label = L["SIDEBAR_GROUP_TOOLS"] or "Werkzeuge",
            icon = "Interface\\Icons\\INV_Misc_Wrench_02",
            children = {
                { 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             = {}
    Panel.sidebarGroups    = {}
    Panel.sidebarStructure = sidebarStructure

    rOrbsDB = rOrbsDB or {}
    rOrbsDB.guiSidebarCollapsed = rOrbsDB.guiSidebarCollapsed or {}
    local groupCollapsed = rOrbsDB.guiSidebarCollapsed

    Panel.tabGroupOf = {}
    for _, _entry in ipairs(sidebarStructure) do
        if _entry.type == "group" and _entry.children then
            for _, _ch in ipairs(_entry.children) do
                Panel.tabGroupOf[_ch.key] = _entry.key
            end
        end
    end

    -- ── SIDEBAR ROWS (grouped with collapsible groups) ──────────────────────
    local TAB_GAP         = 1
    local SB_HEADER_H     = TAB_H          -- top-level / group header row height (40)
    local SB_CHILD_H      = TAB_H - 4      -- slightly shorter child rows (36)
    local SB_CHILD_INDENT = 16
    local SB_ICON         = 18
    local SB_CHILD_ICON   = 14

    local function _SB_Idle(t)
        if t._isGroupHeader then
            t.bg:SetColorTexture(0, 0, 0, 0)
        else
            t.bg:SetColorTexture(C.tabIdle[1], C.tabIdle[2], C.tabIdle[3], 1)
        end
        if t.fs then t.fs:SetTextColor(0.75, 0.73, 0.72, 1) end
        if t.icon then t.icon:SetVertexColor(0.55, 0.55, 0.58, 1) end
        if t.chevron then t.chevron:SetVertexColor(0.55, 0.55, 0.60, 1) end
        if t.underline then t.underline:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0) end
    end

    local function _SB_Hover(t)
        t.bg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 1)
        if t.fs then t.fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
        if t.icon then t.icon:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1) end
        if t.chevron then t.chevron:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
    end

    local function _SB_MakeRow(parent, height, isHeader)
        local row = CreateFrame("Button", nil, parent)
        row:SetHeight(height)

        local bg = row:CreateTexture(nil, "BACKGROUND"); bg:SetAllPoints()
        bg:SetColorTexture(0, 0, 0, 0)
        row.bg = bg

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

        local sep = row:CreateTexture(nil, "BORDER"); sep:SetHeight(1)
        sep:SetPoint("BOTTOMLEFT",  row, "BOTTOMLEFT",  0, 0)
        sep:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", 0, 0)
        sep:SetColorTexture(0.12, 0.12, 0.14, isHeader and 0.9 or 0.55)

        return row
    end

    local function LayoutSidebar()
        local y = 0
        for _, e in ipairs(sidebarStructure) do
            if e.type == "tab" then
                local t = Panel.tabs[e.key]
                if t then
                    t:ClearAllPoints()
                    t:SetPoint("TOPLEFT",  sidebar, "TOPLEFT",   0, -y)
                    t:SetPoint("TOPRIGHT", sidebar, "TOPRIGHT", -1, -y)
                    t:Show()
                    y = y + SB_HEADER_H + TAB_GAP
                end
            elseif e.type == "group" then
                local g = Panel.sidebarGroups[e.key]
                if g then
                    g:ClearAllPoints()
                    g:SetPoint("TOPLEFT",  sidebar, "TOPLEFT",   0, -y)
                    g:SetPoint("TOPRIGHT", sidebar, "TOPRIGHT", -1, -y)
                    g:Show()
                    y = y + SB_HEADER_H + TAB_GAP
                end
                local collapsed = groupCollapsed[e.key] ~= false  -- default = collapsed on first open
                if g and g.chevron and g.chevron.SetAtlas then
                    g.chevron:SetAtlas(collapsed
                        and "friendslist-categorybutton-arrow-right"
                        or  "friendslist-categorybutton-arrow-down")
                end
                if e.children then
                    for _, ch in ipairs(e.children) do
                        local t = Panel.tabs[ch.key]
                        if t then
                            if collapsed then
                                t:Hide()
                            else
                                t:ClearAllPoints()
                                t:SetPoint("TOPLEFT",  sidebar, "TOPLEFT",   0, -y)
                                t:SetPoint("TOPRIGHT", sidebar, "TOPRIGHT", -1, -y)
                                t:Show()
                                y = y + SB_CHILD_H + TAB_GAP
                            end
                        end
                    end
                end
            end
        end
    end
    Panel.LayoutSidebar = LayoutSidebar

    local function ToggleGroup(gKey)
        local currentlyCollapsed = groupCollapsed[gKey] ~= false
        groupCollapsed[gKey] = (not currentlyCollapsed) and true or false
        LayoutSidebar()
    end

    Panel.SetGroupExpanded = function(gKey, expanded)
        groupCollapsed[gKey] = not expanded
        LayoutSidebar()
    end

    for _, e in ipairs(sidebarStructure) do
        if e.type == "tab" then
            local tab = _SB_MakeRow(sidebar, SB_HEADER_H, false)

            local icon = tab:CreateTexture(nil, "OVERLAY"); icon:SetSize(SB_ICON, SB_ICON)
            icon:SetPoint("LEFT", tab, "LEFT", 14, 0)
            icon:SetTexture(e.icon)
            tab.icon = icon

            local bfs = tab:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
            bfs:SetPoint("LEFT",  icon, "RIGHT", 10, 0)
            bfs:SetPoint("RIGHT", tab,  "RIGHT", -6, 0)
            bfs:SetJustifyH("LEFT")
            bfs:SetText(e.label)
            tab.fs = bfs

            local cKey = e.key
            tab:SetScript("OnClick", function() ns.GUI:SetTab(cKey) end)
            tab:SetScript("OnEnter", function(s) if activeTab ~= cKey then _SB_Hover(s) end end)
            tab:SetScript("OnLeave", function(s) if activeTab ~= cKey then _SB_Idle(s)  end end)

            tab.key   = e.key
            tab.label = e.label
            _SB_Idle(tab)
            Panel.tabs[e.key] = tab

        elseif e.type == "group" then
            local header = _SB_MakeRow(sidebar, SB_HEADER_H, true)
            header._isGroupHeader = true

            local icon = header:CreateTexture(nil, "OVERLAY"); icon:SetSize(SB_ICON, SB_ICON)
            icon:SetPoint("LEFT", header, "LEFT", 14, 0)
            icon:SetTexture(e.icon)
            header.icon = icon

            local chevron = header:CreateTexture(nil, "OVERLAY")
            chevron:SetSize(10, 10)
            chevron:SetPoint("RIGHT", header, "RIGHT", -12, 0)
            if chevron.SetAtlas then chevron:SetAtlas("friendslist-categorybutton-arrow-down") end
            chevron:SetVertexColor(0.55, 0.55, 0.60, 1)
            header.chevron = chevron

            local bfs = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
            bfs:SetPoint("LEFT",  icon,    "RIGHT", 10, 0)
            bfs:SetPoint("RIGHT", chevron, "LEFT",  -4, 0)
            bfs:SetJustifyH("LEFT")
            bfs:SetText(e.label)
            bfs:SetTextColor(0.92, 0.90, 0.90, 1)
            header.fs = bfs

            local gKey = e.key
            header:SetScript("OnClick", function() ToggleGroup(gKey) end)
            header:SetScript("OnEnter", function(s)
                s.bg:SetColorTexture(C.tabHover[1], C.tabHover[2], C.tabHover[3], 0.7)
                if s.fs      then s.fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
                if s.icon    then s.icon:SetVertexColor(C.accent[1], C.accent[2], C.accent[3], 1) end
                if s.chevron then s.chevron:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
            end)
            header:SetScript("OnLeave", function(s)
                s.bg:SetColorTexture(0, 0, 0, 0)
                if s.fs      then s.fs:SetTextColor(0.92, 0.90, 0.90, 1) end
                if s.icon    then s.icon:SetVertexColor(0.55, 0.55, 0.58, 1) end
                if s.chevron then s.chevron:SetVertexColor(0.55, 0.55, 0.60, 1) end
            end)
            _SB_Idle(header)
            header.icon:SetVertexColor(0.55, 0.55, 0.58, 1)
            Panel.sidebarGroups[e.key] = header

            for _, ch in ipairs(e.children or {}) do
                local tab = _SB_MakeRow(sidebar, SB_CHILD_H, false)

                local cic = tab:CreateTexture(nil, "OVERLAY"); cic:SetSize(SB_CHILD_ICON, SB_CHILD_ICON)
                cic:SetPoint("LEFT", tab, "LEFT", 14 + SB_CHILD_INDENT, 0)
                cic:SetTexture(ch.icon)
                tab.icon = cic

                local cfs = tab:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
                cfs:SetPoint("LEFT",  cic, "RIGHT", 10, 0)
                cfs:SetPoint("RIGHT", tab, "RIGHT", -6, 0)
                cfs:SetJustifyH("LEFT")
                cfs:SetText(ch.label)
                tab.fs = cfs

                local cKey = ch.key
                tab:SetScript("OnClick", function() ns.GUI:SetTab(cKey) end)
                tab:SetScript("OnEnter", function(s) if activeTab ~= cKey then _SB_Hover(s) end end)
                tab:SetScript("OnLeave", function(s) if activeTab ~= cKey then _SB_Idle(s)  end end)

                tab.key   = ch.key
                tab.label = ch.label
                _SB_Idle(tab)
                Panel.tabs[ch.key] = tab
            end
        end
    end

    LayoutSidebar()

    -- ── SCROLL PANE BUILDER ───────────────────────────────────────────────────
    local contentTop = 0
    local CONTENT_X  = SIDEBAR_W + 6
    local SB_W = 6   -- narrow 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 = "clickcast",  label = L["SUBNAV_CLICKCAST"]  },
            { 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 = "clickcast", label = L["SUBNAV_CLICKCAST"] },
            { 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 = "clickcast", label = L["SUBNAV_CLICKCAST"] },
            { 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 = "auras",     label = L["SUBNAV_PARTY_AURAS"] },
            { key = "font",      label = L["SUBNAV_FONT"]         },
            { key = "icons",     label = L["SUBNAV_ICONS"]        },
            { key = "clickcast", label = L["SUBNAV_CLICKCAST"]    },
            { key = "reset",     label = L["SUBNAV_RESET"]        },
        },
    }

    -- Expose sub-nav data to outer helpers (search index, etc.)
    Panel.subNavData = subNavData
    if Panel.InvalidateSearchIndex then Panel.InvalidateSearchIndex() end

    -- Also expose SetSubTab once defined below; index is built lazily.

    local SUBNAV_BTN_H = 36
    local SUBNAV_GAP   = 1

    local function SetSubTab(tabKey, subKey)
        activeSubTab = tabKey.."."..subKey
        -- Show/hide sub-panes (mit HorizonSuite-artigem Fade-In beim Wechsel)
        for k, sf in pairs(contentFrames) do
            if k:sub(1, #tabKey+1) == tabKey.."." then
                if k == activeSubTab then
                    FadePane(sf, 0.14)
                    local child = sf._sf and sf._sf:GetScrollChild()
                    if child and child._updateThumb then
                        local fn = child._updateThumb
                        if C_Timer and C_Timer.After then
                            C_Timer.After(0, fn)
                        else
                            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
                if btnData.key == "reset" then
                    -- Reset-Style fest, nicht überschreiben
                else
                    local isActive = (tabKey.."."..btnData.key == activeSubTab)
                    btnData.bg:SetColorTexture(
                        isActive and C.tabActive[1] or C.bgDeep[1]+0.005,
                        isActive and C.tabActive[2] or C.bgDeep[2]+0.005,
                        isActive and C.tabActive[3] 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"]},
                {key="party", label=L["TAB_PARTY"]},
            }) 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: no pane, open dialog directly. Persistent red accent.
                bg:SetColorTexture(C.accent[1]*0.32, C.accent[2]*0.32, C.accent[3]*0.32, 1)
                bar:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0.70)
                fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
                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.52, C.accent[2]*0.52, C.accent[3]*0.52, 1)
                    fs:SetTextColor(1, 1, 1, 1)
                end)
                btn:SetScript("OnLeave", function()
                    bg:SetColorTexture(C.accent[1]*0.32, C.accent[2]*0.32, C.accent[3]*0.32, 1)
                    fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 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 (dark gray)
        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)   -- track background

        -- Scrollbar thumb (opaque accent)
        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
        child._paneKey = key

        -- Hooks auf CreateFontString / CreateTexture: Inline-Erzeugungen wie
        -- `pane:CreateFontString(...)` werden automatisch in den settings-
        -- Container der aktuellen Section umgeleitet, und SetPoint-Aufrufe
        -- mit der Pane als Anchor-Target werden auf den Container umgelenkt
        -- (mit Y-Transform). So verhalten sich inline erzeugte Labels /
        -- Texturen identisch zu Helper-Widgets und verschwinden beim
        -- Einklappen zuverlässig.
        local _childCFS = child.CreateFontString
        child.CreateFontString = function(self, ...)
            local sec = self._activeSection
            if sec and sec.settingsContainer and not self._suspendSectionTracking then
                local fs = sec.settingsContainer:CreateFontString(...)
                _WrapRegionSetPoint(fs, self, sec)
                return fs
            end
            return _childCFS(self, ...)
        end
        local _childCT = child.CreateTexture
        child.CreateTexture = function(self, ...)
            local sec = self._activeSection
            if sec and sec.settingsContainer and not self._suspendSectionTracking then
                local tex = sec.settingsContainer:CreateTexture(...)
                _WrapRegionSetPoint(tex, self, sec)
                return tex
            end
            return _childCT(self, ...)
        end
        -- 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)
            -- Hide thumb and track when no scroll needed
            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

        -- Mausrad: delta * 24 px pro 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()
            -- On first show: finalize section buckets and apply persisted collapse state.
            if child._sections and not child._sectionsFinalized then
                _Section_FinalizePane(child)
                _Section_ApplyPersisted(child)
            end
            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

    -- Centered logo at top of 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"])
        -- Show reload reminder (dialog)
        ShowMinimapReloadWarning(function()
            -- Cancel: revert change
            ns.GUI:SetMinimapShown(not val)
            self:SetChecked(not val)
        end)
    end)
    Panel.cbMinimap = cbMinimap
    yg = yg - 50

    local cbBlizzardFrames = MakeCheckbox(
        genPane,
        L["GEN_BLIZZARD_UNITFRAMES"],
        L["GEN_BLIZZARD_UNITFRAMES_TIP"],
        PAD,
        yg
    )
    cbBlizzardFrames:SetScript("OnClick", function(self)
        local val = self:GetChecked() == true
        rOrbsDB.showBlizzardUnitFrames = val
        ns.GUI:RefreshValues()
        ShowMinimapReloadWarning(function()
            rOrbsDB.showBlizzardUnitFrames = not val
            self:SetChecked(not val)
            ns.GUI:RefreshValues()
        end)
    end)
    Panel.cbBlizzardFrames = cbBlizzardFrames
    yg = yg - 50

    local holdIndentX = PAD + 24
    local holdBlock = CreateFrame("Frame", nil, genPane, "BackdropTemplate")
    holdBlock:SetPoint("TOPLEFT", genPane, "TOPLEFT", holdIndentX - 10, yg + 6)
    holdBlock:SetSize(400, 96)
    holdBlock:SetFrameStrata(genPane:GetFrameStrata())
    holdBlock:SetFrameLevel((genPane:GetFrameLevel() or 1) - 1)
    holdBlock:SetBackdrop({
        bgFile = "Interface\\Buttons\\WHITE8X8",
        edgeFile = "Interface\\Buttons\\WHITE8X8",
        tile = false,
        edgeSize = 1,
        insets = { left = 0, right = 0, top = 0, bottom = 0 },
    })
    -- Subtle dark grouping box; keep text readable.
    holdBlock:SetBackdropColor(0.03, 0.03, 0.04, 0.72)
    holdBlock:SetBackdropBorderColor(0.14, 0.14, 0.16, 0.9)

    local function BlizzardHoldKeyLabel()
        local key = rOrbsDB and rOrbsDB.blizzardFramesHoldKey
        if not key or key == "" then
            return L["GEN_BLIZZARD_HOLDKEY_NONE"]
        end
        return GetBindingText(key, "KEY_") or key
    end

    local holdKeyTitle = genPane:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    holdKeyTitle:SetPoint("TOPLEFT", genPane, "TOPLEFT", holdIndentX, yg - 8)
    holdKeyTitle:SetText(L["GEN_BLIZZARD_HOLDKEY"])
    holdKeyTitle:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
    yg = yg - 28

    local holdKeyValue = genPane:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    holdKeyValue:SetPoint("TOPLEFT", genPane, "TOPLEFT", holdIndentX, yg)
    holdKeyValue:SetTextColor(1.0, 0.82, 0.0)
    holdKeyValue:SetText(L["GEN_BLIZZARD_HOLDKEY_CURRENT"]:format(BlizzardHoldKeyLabel()))
    Panel.lblBlizzardHoldKey = holdKeyValue

    yg = yg - 30
    local btnSetHoldKey = MakeButton(genPane, L["GEN_BLIZZARD_HOLDKEY_SET"], holdIndentX, yg, 150, 24, 0.08, 0.15, 0.25)
    local btnClearHoldKey = MakeButton(genPane, L["GEN_BLIZZARD_HOLDKEY_CLEAR"], holdIndentX + 160, yg, 110, 24, AccentWarnBtnRGB())

    local function SetBlizzHoldKey(key)
        if key == "ESCAPE" then
            key = nil
        end
        rOrbsDB.blizzardFramesHoldKey = key
        holdKeyValue:SetText(L["GEN_BLIZZARD_HOLDKEY_CURRENT"]:format(BlizzardHoldKeyLabel()))
    end

    btnSetHoldKey:SetScript("OnClick", function()
        if Panel._blizzHoldCapture and Panel._blizzHoldCapture:IsShown() then return end
        local cap = Panel._blizzHoldCapture
        if not cap then
            cap = CreateFrame("Frame", nil, Panel)
            cap:SetAllPoints(Panel)
            cap:EnableKeyboard(true)
            cap:SetFrameStrata("FULLSCREEN_DIALOG")
            cap:SetPropagateKeyboardInput(false)
            cap:SetScript("OnKeyDown", function(self, key)
                if key == "UNKNOWN" then return end
                SetBlizzHoldKey(key)
                self:Hide()
            end)
            Panel._blizzHoldCapture = cap
        end
        holdKeyValue:SetText(L["GEN_BLIZZARD_HOLDKEY_PRESS"])
        cap:Show()
    end)

    btnClearHoldKey:SetScript("OnClick", function()
        SetBlizzHoldKey(nil)
    end)

    Panel.btnSetBlizzardHoldKey = btnSetHoldKey
    Panel.btnClearBlizzardHoldKey = btnClearHoldKey
    yg = yg - 64

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

    local cbOrbDragLocked = MakeCheckbox(genPane, L["FRAME_POS_LOCK_DRAG"], L["FRAME_POS_LOCK_DRAG_TIP"], PAD, yg)
    cbOrbDragLocked:SetScript("OnClick", function(self)
        rOrbsDB.orbDragLocked = self:GetChecked() == true
    end)
    Panel.cbOrbDragLocked = cbOrbDragLocked
    yg = yg - 50

    local cbSnapToGrid = MakeCheckbox(genPane, L["FRAME_POS_SNAP_GRID"], L["FRAME_POS_SNAP_GRID_TIP"], PAD, yg)
    cbSnapToGrid:SetScript("OnClick", function(self)
        rOrbsDB.snapToGrid = self:GetChecked() == true
    end)
    Panel.cbSnapToGrid = cbSnapToGrid
    yg = yg - 50

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



    -- Credits and localization overview (always visible, no accordion card)
    local _savedSuspend = genPane._suspendSectionTracking
    genPane._suspendSectionTracking = true

    local creditsBlock = _origCreateFrame("Frame", nil, genPane)
    if genPane._lastCard then
        creditsBlock:SetPoint("TOPLEFT", genPane._lastCard, "BOTTOMLEFT", PAD, -SECTION_CARD_GAP - 6)
        creditsBlock:SetPoint("TOPRIGHT", genPane._lastCard, "BOTTOMRIGHT", -PAD - SB_W - 6, -SECTION_CARD_GAP - 6)
    else
        creditsBlock:SetPoint("TOPLEFT", genPane, "TOPLEFT", PAD, yg)
        creditsBlock:SetPoint("TOPRIGHT", genPane, "TOPRIGHT", -PAD - SB_W - 6, yg)
    end
    creditsBlock:SetHeight(220)
    creditsBlock:SetFrameLevel((genPane:GetFrameLevel() or 1) + 20)

    local by = 0
    local creditsHeader = creditsBlock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    creditsHeader:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", 0, by)
    creditsHeader:SetJustifyH("LEFT")
    creditsHeader:SetTextColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 1)
    creditsHeader:SetText(L["CREDITS_HEADER"])
    by = by - 20

    local hdrLine = creditsBlock:CreateTexture(nil, "ARTWORK")
    hdrLine:SetHeight(1)
    hdrLine:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", 0, by)
    hdrLine:SetPoint("TOPRIGHT", creditsBlock, "TOPRIGHT", 0, by)
    hdrLine:SetGradient("HORIZONTAL",
        CreateColor(C.sectionHdr[1], C.sectionHdr[2], C.sectionHdr[3], 0.6),
        CreateColor(0, 0, 0, 0))
    by = by - 14

    local contentW = PANEL_W - CONTENT_X - PAD - SB_W - 30
    local colGap = 38
    local colW = math.floor((contentW - colGap) / 2)
    local rightColX = colW + colGap

    local creditsTitle = creditsBlock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    creditsTitle:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", 0, by)
    creditsTitle:SetJustifyH("LEFT")
    creditsTitle:SetText("Credits")

    local localesTitle = creditsBlock:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    localesTitle:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", rightColX, by)
    localesTitle:SetJustifyH("LEFT")
    localesTitle:SetText("Lokalisierungen")
    by = by - 24

    local creditsText = creditsBlock:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    creditsText:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", 0, by)
    creditsText:SetWidth(colW)
    creditsText:SetJustifyH("LEFT")
    creditsText:SetJustifyV("TOP")
    creditsText:SetWordWrap(true)
    creditsText:SetNonSpaceWrap(false)
    creditsText:SetTextColor(0.82, 0.82, 0.82)
    creditsText:SetText("|cffffff99Zork|r und |cffffff99Yabeela|r,\n\nohne eure Arbeit, eure Zeit und eure Unterstuetzung waere rOrbs niemals das geworden, was es heute ist. Eure Ideen, euer Einsatz und eure Hilfe haben dieses Addon erst moeglich gemacht. Danke von Herzen fuer alles.")

    local localesText = creditsBlock:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    localesText:SetPoint("TOPLEFT", creditsBlock, "TOPLEFT", rightColX, by)
    localesText:SetWidth(colW)
    localesText:SetJustifyH("LEFT")
    localesText:SetJustifyV("TOP")
    localesText:SetWordWrap(true)
    localesText:SetNonSpaceWrap(false)
    localesText:SetTextColor(0.82, 0.82, 0.82)
    localesText:SetText(
        "enUS - locals/enUS.lua\n"
        .. "deDE - locals/deDE.lua\n"
        .. "frFR - locals/frFR.lua\n"
        .. "esES - locals/esES.lua\n"
        .. "esMX - locals/esMX.lua\n"
        .. "ruRU - locals/ruRU.lua\n"
        .. "itIT - locals/itIT.lua\n"
        .. "koKR - locals/koKR.lua\n"
        .. "zhCN - locals/zhCN.lua\n"
        .. "zhTW - locals/zhTW.lua\n"
        .. "ptBR - locals/ptBR.lua"
    )

    local creditsH = creditsText:GetStringHeight() or 0
    local localesH = localesText:GetStringHeight() or 0
    local blockH = math.abs(by) + math.max(creditsH, localesH) + 16
    creditsBlock:SetHeight(blockH)

    yg = yg - blockH - 24
    MakeSep(genPane, yg) ; yg = yg - 18
    genPane._suspendSectionTracking = _savedSuspend

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

    -- ---- Click-cast: inputs + buttons (orb palette) ------------------------
    local SPELLBOOK_GREY   = "Interface\\AddOns\\rOrbs\\media\\spellbook_grey.tga"
    local SPELLBOOK_COLOR  = "Interface\\AddOns\\rOrbs\\media\\spellbook_color.tga"
    local CC_ROW_EB_H      = 24

    local function MakeClickCastGreenBtn(parent, text, w, onClick)
        local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
        btn:SetSize(w or 96, CC_ROW_EB_H + 2)
        local gr, gg, gb = C.green[1] * 0.20, C.green[2] * 0.32, C.green[3] * 0.20
        local br = C.green[1] * 0.50
        local bg = C.green[2] * 0.55
        local bb = C.green[3] * 0.50
        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(gr, gg, gb, 1)
        local lbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        lbl:SetPoint("CENTER")
        lbl:SetText(text)
        lbl:SetTextColor(0.88, 1, 0.88, 1)
        btn:SetBackdropBorderColor(br, bg, bb, 1)
        btn:SetScript("OnEnter", function(s)
            s:SetBackdropColor(C.green[1] * 0.30, C.green[2] * 0.48, C.green[3] * 0.30, 1)
            s:SetBackdropBorderColor(C.green[1] * 0.95, C.green[2], C.green[3] * 0.95, 1)
            lbl:SetTextColor(1, 1, 1, 1)
        end)
        btn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(gr, gg, gb, 1)
            s:SetBackdropBorderColor(br, bg, bb, 1)
            lbl:SetTextColor(0.88, 1, 0.88, 1)
        end)
        btn:SetScript("OnDisable", function(s)
            s:SetBackdropColor(gr, gg, gb, 0.35)
            s:SetBackdropBorderColor(br, bg, bb, 0.35)
            lbl:SetTextColor(0.55, 0.65, 0.55, 0.65)
        end)
        btn:SetScript("OnEnable", function(s)
            s:SetBackdropColor(gr, gg, gb, 1)
            s:SetBackdropBorderColor(br, bg, bb, 1)
            lbl:SetTextColor(0.88, 1, 0.88, 1)
        end)
        btn:SetScript("OnClick", onClick)
        return btn
    end

    local function MakeClickCastRedBtn(parent, text, w, onClick)
        local btn = CreateFrame("Button", nil, parent, "BackdropTemplate")
        btn:SetSize(w or 88, CC_ROW_EB_H + 2)
        local rr = C.accent[1] * 0.28 + 0.04
        local rg = C.accent[2] * 0.26 + 0.04
        local rb = C.accent[3] * 0.26 + 0.04
        local br = math.min(1, C.accent[1] * 0.62 + 0.08)
        local bg = math.min(1, C.accent[2] * 0.48 + 0.08)
        local bb = math.min(1, C.accent[3] * 0.48 + 0.08)
        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(rr, rg, rb, 1)
        local lbl = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
        lbl:SetPoint("CENTER")
        lbl:SetText(text)
        lbl:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        btn:SetBackdropBorderColor(br, bg, bb, 1)
        btn:SetScript("OnEnter", function(s)
            s:SetBackdropColor(C.accent[1] * 0.42, C.accent[2]*0.30, C.accent[3]*0.30, 1)
            s:SetBackdropBorderColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
            lbl:SetTextColor(1, 1, 1, 1)
        end)
        btn:SetScript("OnLeave", function(s)
            s:SetBackdropColor(rr, rg, rb, 1)
            s:SetBackdropBorderColor(br, bg, bb, 1)
            lbl:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        end)
        btn:SetScript("OnDisable", function(s)
            s:SetBackdropColor(rr, rg, rb, 0.35)
            s:SetBackdropBorderColor(br, bg, bb, 0.35)
            lbl:SetTextColor(C.textDim[1], C.textDim[2], C.textDim[3], 0.65)
        end)
        btn:SetScript("OnEnable", function(s)
            s:SetBackdropColor(rr, rg, rb, 1)
            s:SetBackdropBorderColor(br, bg, bb, 1)
            lbl:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        end)
        btn:SetScript("OnClick", onClick)
        return btn
    end

    -- Click-Cast UI block (orbs + group tab); panelStore = Panel[key] or Panel.party
    local function BuildClickCastPaneContent(pane, yo, dbKeyEn, dbKeyL, dbKeyM, dbKeyR, panelStore, hintSuffix, lblEnable, tipEnable)
        lblEnable = lblEnable or L["CLICKCAST_ENABLE"]
        tipEnable = tipEnable or L["CLICKCAST_ENABLE_TIP"]

        local function CCApply()
            if not (ns.ClickCast and ns.ClickCast.ApplyAll) then return end
            if InCombatLockdown() then
                ns.GUI:ChatMsg("|cffff8800" .. L["CLICKCAST_COMBAT"] .. "|r")
            else
                ns.ClickCast.ApplyAll()
            end
        end

        MakeSectionHeader(pane, L["SUBNAV_CLICKCAST"], PAD, yo, true, function()
            rOrbsDB[dbKeyEn] = false
            rOrbsDB[dbKeyL] = ""
            rOrbsDB[dbKeyM] = ""
            rOrbsDB[dbKeyR] = ""
            CCApply()
            ns.GUI:RefreshValues()
            ns.GUI:ChatMsg("|cffffff00" .. L["SUBNAV_CLICKCAST"] .. "|r reset.")
        end)
        yo = yo - 44

        local lblHint = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        lblHint:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        lblHint:SetWidth(math.max(280, PANEL_W - SUBNAV_W - 180 - PAD * 3))
        lblHint:SetJustifyH("LEFT")
        lblHint:SetWordWrap(true)
        lblHint:SetNonSpaceWrap(false)
        local hintText = L["CLICKCAST_DESC"] .. "\n\n" .. L["CLICKCAST_CTRL_INFO"]
        if hintSuffix and hintSuffix ~= "" then
            hintText = hintText .. "\n\n" .. hintSuffix
        end
        lblHint:SetText(hintText)
        lblHint:SetTextColor(C.text[1] * 0.85, C.text[2] * 0.85, C.text[3] * 0.85, 1)
        local hintH = lblHint:GetStringHeight()
        if not hintH or hintH < 8 then hintH = 72 end
        yo = yo - hintH - 38

        local cbCC = MakeCheckbox(pane, lblEnable, tipEnable, PAD, yo)
        cbCC:SetChecked(rOrbsDB[dbKeyEn] == true)
        cbCC:SetScript("OnClick", function(self)
            rOrbsDB[dbKeyEn] = self:GetChecked() == true
            CCApply()
        end)
        panelStore.clickCastEnable = cbCC
        yo = yo - 62

        local function openPickerForRow(eb, afterPick)
            if not (ns.ClickCastSpellUI and ns.ClickCastSpellUI.OpenSpellPicker) then return end
            ns.ClickCastSpellUI.OpenSpellPicker(function(spellID)
                if spellID then
                    eb:SetText(tostring(spellID))
                    if afterPick then afterPick() end
                end
            end)
        end

        local rowLeftX = PAD + 30 + CC_ROW_EB_H + 8
        local ebW = 168

        for _, sl in ipairs({
            { labelKey = "CLICKCAST_LEFT",   dbKey = dbKeyL, panelSuffix = "Left" },
            { labelKey = "CLICKCAST_MIDDLE", dbKey = dbKeyM, panelSuffix = "Middle" },
            { labelKey = "CLICKCAST_RIGHT",  dbKey = dbKeyR, panelSuffix = "Right" },
        }) do
            local dbKey = sl.dbKey
            local lblRow = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
            lblRow:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
            lblRow:SetText(L[sl.labelKey])
            lblRow:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
            yo = yo - 22

            local rowTopY = yo
            local iconHolder = CreateFrame("Button", nil, pane)
            iconHolder:SetSize(28, 28)
            iconHolder:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, rowTopY)
            local icon = iconHolder:CreateTexture(nil, "ARTWORK")
            icon:SetSize(24, 24)
            icon:SetPoint("CENTER", iconHolder, "CENTER", 0, 0)
            icon:Hide()

            local ebWrap = CreateFrame("Frame", nil, pane)
            ebWrap:SetSize(ebW + 2, CC_ROW_EB_H + 4)
            ebWrap:SetPoint("TOPLEFT", pane, "TOPLEFT", rowLeftX, rowTopY + 1)
            local ebB = ebWrap:CreateTexture(nil, "BACKGROUND")
            ebB:SetAllPoints()
            ebB:SetColorTexture(C.border[1], C.border[2], C.border[3], 1)
            local ebI = ebWrap:CreateTexture(nil, "ARTWORK")
            ebI:SetPoint("TOPLEFT", ebWrap, "TOPLEFT", 1, -1)
            ebI:SetPoint("BOTTOMRIGHT", ebWrap, "BOTTOMRIGHT", -1, 1)
            ebI:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 1)
            local eb = CreateFrame("EditBox", nil, ebWrap)
            eb:SetPoint("TOPLEFT", ebI, "TOPLEFT", 5, -3)
            eb:SetPoint("BOTTOMRIGHT", ebI, "BOTTOMRIGHT", -5, 3)
            eb:SetAutoFocus(false)
            eb:SetFontObject("ChatFontNormal")
            eb:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
            eb:SetText(rOrbsDB[dbKey] or "")

            local browse = CreateFrame("Button", nil, pane)
            browse:SetSize(CC_ROW_EB_H, CC_ROW_EB_H)
            browse:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD + 30, rowTopY + 2)
            local bTex = browse:CreateTexture(nil, "ARTWORK")
            bTex:SetAllPoints()
            bTex:SetTexture(SPELLBOOK_GREY)

            local lastClick = 0

            local btnOk, btnDel
            local function updateRowState()
                local draft = eb:GetText() or ""
                local draftTrim = (draft:gsub("^%s+", ""):gsub("%s+$", ""))
                local savedTrim = ((rOrbsDB[dbKey] or ""):gsub("^%s+", ""):gsub("%s+$", ""))
                local noChange = (draftTrim == savedTrim)
                local valid, spellID, _, iconID = ns.ClickCastSpellUI.ValidateSpellInput(draft)
                if draftTrim == "" then
                    icon:Hide()
                    if noChange then
                        btnOk:Disable()
                    else
                        btnOk:Enable()
                    end
                    btnDel:Disable()
                else
                    if valid and spellID then
                        icon:Show()
                        if iconID then
                            icon:SetTexture(iconID)
                        else
                            icon:SetTexture(134400)
                        end
                    else
                        icon:Hide()
                    end
                    if noChange or not valid or not spellID then
                        btnOk:Disable()
                    else
                        btnOk:Enable()
                    end
                    btnDel:Enable()
                end
            end

            local function commitRow()
                local draft = eb:GetText() or ""
                local draftTrim = (draft:gsub("^%s+", ""):gsub("%s+$", ""))
                if draftTrim == "" then
                    rOrbsDB[dbKey] = ""
                else
                    local valid, spellID = ns.ClickCastSpellUI.ValidateSpellInput(draft)
                    if valid and spellID then
                        rOrbsDB[dbKey] = tostring(spellID)
                        eb:SetText(tostring(spellID))
                    end
                end
                CCApply()
                updateRowState()
            end

            btnOk = MakeClickCastGreenBtn(pane, L["CLICKCAST_CONFIRM"], 100, function()
                commitRow()
            end)
            btnOk:SetPoint("TOPLEFT", ebWrap, "TOPRIGHT", 8, -1)

            btnDel = MakeClickCastRedBtn(pane, L["CLICKCAST_DELETE"], 88, function()
                eb:SetText("")
                rOrbsDB[dbKey] = ""
                CCApply()
                updateRowState()
            end)
            btnDel:SetPoint("LEFT", btnOk, "RIGHT", 8, 0)

            btnOk:HookScript("OnEnter", function()
                GameTooltip:SetOwner(btnOk, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["CLICKCAST_CONFIRM_TIP"], 1, 1, 1, 1)
                GameTooltip:Show()
            end)
            btnOk:HookScript("OnLeave", GameTooltip_Hide)

            browse:SetScript("OnEnter", function()
                bTex:SetTexture(SPELLBOOK_COLOR)
                GameTooltip:SetOwner(browse, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["CLICKCAST_BROWSE_TIP"], 1, 1, 1, 1)
                GameTooltip:Show()
            end)
            browse:SetScript("OnLeave", function()
                bTex:SetTexture(SPELLBOOK_GREY)
                GameTooltip_Hide()
            end)
            browse:SetScript("OnClick", function()
                openPickerForRow(eb, updateRowState)
            end)

            eb:SetScript("OnTextChanged", function()
                updateRowState()
            end)
            eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
            eb:SetScript("OnEnter", function()
                GameTooltip:SetOwner(eb, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["CLICKCAST_FIELD_TIP"], 1, 1, 1, 1)
                GameTooltip:Show()
            end)
            eb:SetScript("OnLeave", GameTooltip_Hide)
            eb:SetScript("OnMouseDown", function(self, button)
                if button ~= "LeftButton" then return end
                local t = GetTime()
                if t - lastClick < 0.35 and lastClick > 0 then
                    openPickerForRow(eb, updateRowState)
                    lastClick = 0
                    self:ClearFocus()
                else
                    lastClick = t
                end
            end)

            iconHolder:SetScript("OnClick", function()
                openPickerForRow(eb, updateRowState)
            end)
            iconHolder:SetScript("OnEnter", function()
                GameTooltip:SetOwner(iconHolder, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["CLICKCAST_ICON_TIP"], 1, 1, 1, 1)
                GameTooltip:Show()
            end)
            iconHolder:SetScript("OnLeave", GameTooltip_Hide)

            btnDel:HookScript("OnEnter", function()
                GameTooltip:SetOwner(btnDel, "ANCHOR_RIGHT")
                GameTooltip:SetText(L["CLICKCAST_DELETE_TIP"], 1, 1, 1, 1)
                GameTooltip:Show()
            end)
            btnDel:HookScript("OnLeave", GameTooltip_Hide)

            panelStore["clickCast" .. sl.panelSuffix] = eb
            panelStore["clickCast" .. sl.panelSuffix .. "Sync"] = function()
                eb:SetText(rOrbsDB[dbKey] or "")
                updateRowState()
            end

            updateRowState()
            yo = rowTopY - 48
        end

        return yo
    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 paneClickCast = (orbType == "HEALTH" or orbType == "POWER" or orbType == "TARGET_HEALTH") and BuildSubPane(key..".clickcast") or nil
        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()
            EnsureOrbCharCfg(cfgKey)
        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
                info.previewTexture = entry.value
                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)
                    if cfgKey == "POWER" or cfgKey == "HEALTH" then
                        ns.GUI:ApplyColorAuto(cfgKey, false)
                    end
                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, L["ORB_COLOR_RESET_BTN"], 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)
            if cfgKey == "POWER" or cfgKey == "HEALTH" then
                ns.GUI:ApplyColorAuto(cfgKey, false)
            end
            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() == true or self:GetChecked() == 1)
                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 = val and L["CHAT_HEALTH_CLASSCOLOR_ON"] or L["CHAT_HEALTH_CLASSCOLOR_OFF"]
                else
                    msg = val and L["CHAT_POWER_AUTOCOLOR_ON"] or L["CHAT_POWER_AUTOCOLOR_OFF"]
                end
                ns.GUI:ChatMsg(msg)
            end)
            Panel[key].cbAuto = cbAuto
            yo = yo - 56
        end

        -- Click-Cast (SecureActionButton: left / middle / right)
        if paneClickCast and (cfgKey == "HEALTH" or cfgKey == "POWER" or cfgKey == "TARGET_HEALTH") then
            SwitchPane(paneClickCast)
            local dbKeyEn, dbKeyL, dbKeyM, dbKeyR
            if cfgKey == "HEALTH" then
                dbKeyEn, dbKeyL, dbKeyM, dbKeyR = "clickCastHealthEnabled", "clickCastHealthLeft", "clickCastHealthMiddle", "clickCastHealthRight"
            elseif cfgKey == "POWER" then
                dbKeyEn, dbKeyL, dbKeyM, dbKeyR = "clickCastPowerEnabled", "clickCastPowerLeft", "clickCastPowerMiddle", "clickCastPowerRight"
            else
                dbKeyEn, dbKeyL, dbKeyM, dbKeyR = "clickCastTargetEnabled", "clickCastTargetLeft", "clickCastTargetMiddle", "clickCastTargetRight"
            end
            yo = BuildClickCastPaneContent(pane, yo, dbKeyEn, dbKeyL, dbKeyM, dbKeyR, Panel[key])
        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(L["CHAT_RING_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,
                L["RING_SHOW"],
                L["RING_SHOW_ORB_FMT_TIP"]:format(ringOrbLabel),
                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((val and L["RING_CHAT_SHOW_ON"] or L["RING_CHAT_SHOW_OFF"]):format(ringOrbLabel))
            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,
                L["RING_ROTATE"],
                L["RING_ROTATE_FMT_TIP"]:format(ringOrbLabel),
                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((val and L["RING_CHAT_ROTATE_ON"] or L["RING_CHAT_ROTATE_OFF"]):format(ringOrbLabel))
            end)
            Panel[key].cbRingRotate = cbRingRotate ; yo = yo - 50

            local sRingSpeed, _ = MakeSlider(pane,
                L["RING_SPEED"],
                L["RING_SPEED_TIP"],
                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
                    rOrbsDB.runePointsFillRTL = 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          = L["CLASS_POWER_STYLE_RUNES"],
                    combobar       = L["CLASS_POWER_STYLE_COMBO"],
                    model3d        = L["CLASS_POWER_STYLE_MODEL3D"],
                    ["model3d-legion"] = L["CLASS_POWER_STYLE_MODEL3D_LEGION"],
                    hidden         = L["CLASS_POWER_STYLE_HIDDEN"],
                }
                local ddStyle, _ = MakeDropdown(pane,
                    L["CLASS_POWER_STYLE_LABEL"],
                    L["CLASS_POWER_STYLE_TIP"],
                    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(string.format(L["CHAT_CLASS_POWER_STYLE"], styleLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddStyle, function(self, level)
                    local current = rOrbsDB.comboPointStyle or "runes"
                    local entries = {
                        { value = "runes",          text = L["CLASS_POWER_STYLE_RUNES"]          },
                        { value = "combobar",       text = L["CLASS_POWER_STYLE_COMBO"]           },
                        { value = "model3d",        text = L["CLASS_POWER_STYLE_MODEL3D"]         },
                        { value = "model3d-legion", text = L["CLASS_POWER_STYLE_MODEL3D_LEGION"]  },
                        { value = "hidden",         text = L["CLASS_POWER_STYLE_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 L["CLASS_POWER_STYLE_RUNES"])
                    UIDropDownMenu_SetSelectedValue(ddStyle, cur)
                end
                yo = yo - 56

                local cbFillRTL = MakeCheckbox(pane,
                    L["CLASS_POWER_FILL_RTL"],
                    L["CLASS_POWER_FILL_RTL_TIP"],
                    PAD, yo)
                cbFillRTL:SetChecked(rOrbsDB.runePointsFillRTL == true)
                cbFillRTL:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.runePointsFillRTL = val and true or nil
                    if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    if ns.ComboBar   then ns.ComboBar.ForceRefresh()   end
                    ns.GUI:ChatMsg("|cffffff00" .. L["CLASS_POWER_FILL_RTL"] .. "|r: "
                        .. (val and L["CLASS_POWER_FILL_RTL_ON"] or L["CLASS_POWER_FILL_RTL_OFF"]))
                end)
                Panel[key].cbClassPowerFillRTL = cbFillRTL
                yo = yo - 44

                -- 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.runeArcAngle       = nil
                    rOrbsDB.runeArcOutside     = 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].ddArcSide and ns.SetRuneArcSide then
                        ns.SetRuneArcSide("top")
                        UIDropDownMenu_SetText(Panel[key].ddArcSide, L["RUNE_ARC_TOP"])
                        UIDropDownMenu_SetSelectedValue(Panel[key].ddArcSide, "top")
                    end
                    if Panel[key].cbRuneArcOutside then Panel[key].cbRuneArcOutside:SetChecked(true) end
                    if Panel[key].sArcOffX  then Panel[key].sArcOffX:SetValue(0)   end
                    if Panel[key].sArcOffY  then Panel[key].sArcOffY:SetValue(0)   end
                    if Panel[key].sArcRuneSpacing then Panel[key].sArcRuneSpacing:SetValue(6) 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, L["RUNE_LAYOUT_ARC"])
                        UIDropDownMenu_SetSelectedValue(Panel[key].ddRunePos, "arc")
                    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"])
                            SetTextureAccentDanger(self.innerTex)
                            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,
                    L["RUNE_BG_SHOW"],
                    L["RUNE_BG_SHOW_TIP"],
                    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(val and L["CHAT_RUNE_BG_SHOWN"] or L["CHAT_RUNE_BG_HIDDEN"])
                end)
                Panel[key].cbRuneBG = cbRuneBG ; yo = yo - 44

                -- RUNE LAYOUT: arc around orb (angle slider) vs horizontal row
                local posLabels = {
                    arc        = L["RUNE_LAYOUT_ARC"],
                    horizontal = L["RUNE_POS_HORIZONTAL"],
                }
                local ddRunePos, _ = MakeDropdown(pane,
                    L["RUNE_LAYOUT_MODE"],
                    L["RUNE_LAYOUT_MODE_TIP"],
                    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 "arc"
                    if current ~= "horizontal" and current ~= "arc" then current = "arc" end
                    local entries = {
                        { value = "arc",        text = L["RUNE_LAYOUT_ARC"] },
                        { 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)
                            -- arc: 2 sliders + checkbox + 3 sliders; horiz: header + 4 sliders
                            if pane and pane._contentH then
                                local arcH   = 46 + 56 + 50 + 3 * 46 + 14 + 14
                                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("|cffffff00"..L["RUNE_LAYOUT_MODE"].."|r: "..e.text)
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end)
                do
                    local cur = rOrbsDB.runePointsPosition or "arc"
                    if cur ~= "horizontal" and cur ~= "arc" then cur = "arc" end
                    UIDropDownMenu_SetText(ddRunePos, posLabels[cur] or L["RUNE_LAYOUT_ARC"])
                    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
                -- Bottom Y after arc widgets (used so Combo Bar does not overlap when arc mode)
                local yoAfterArc

                -- Arc sub-options (shown for all non-horizontal positions) ------
                do
                    -- (yoBeforeArc declared above for shared scope)
                    local sArcSize, lArcSize = MakeSlider(pane, L["RUNE_ARC_ICON_SIZE"],
                        L["RUNE_ARC_ICON_SIZE_TIP"],
                        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 arcSideLabels = {
                        top    = L["RUNE_ARC_TOP"],
                        bottom = L["RUNE_ARC_BOTTOM"],
                    }
                    local ddArcSide, lArcSide = MakeDropdown(pane,
                        L["RUNE_ARC_SIDE"],
                        L["RUNE_ARC_SIDE_TIP"],
                        PAD + 20, yo, 200)
                    Panel[key].ddArcSide = ddArcSide
                    UIDropDownMenu_Initialize(ddArcSide, function(self, level)
                        local current = ns.GetRuneArcSide and ns.GetRuneArcSide() or "top"
                        local entries = {
                            { value = "top",    text = L["RUNE_ARC_TOP"] },
                            { value = "bottom", text = L["RUNE_ARC_BOTTOM"] },
                        }
                        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(ddArcSide, e.value)
                                UIDropDownMenu_SetText(ddArcSide, e.text)
                                if ns.SetRuneArcSide then ns.SetRuneArcSide(e.value) end
                                if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                                ns.GUI:ChatMsg("|cffffff00"..L["RUNE_ARC_SIDE"].."|r: "..e.text)
                            end
                            UIDropDownMenu_AddButton(info, level)
                        end
                    end)
                    do
                        local cur = ns.GetRuneArcSide and ns.GetRuneArcSide() or "top"
                        UIDropDownMenu_SetText(ddArcSide, arcSideLabels[cur] or L["RUNE_ARC_TOP"])
                        UIDropDownMenu_SetSelectedValue(ddArcSide, cur)
                    end
                    tinsert(arcWidgets, ddArcSide) ; tinsert(arcWidgets, lArcSide)
                    yo = yo - 56
                    -- Extra gap before checkbox (below arc side dropdown)
                    yo = yo - 14

                    local cbArcOutside = MakeCheckbox(pane, L["RUNE_ARC_OUTSIDE"], L["RUNE_ARC_OUTSIDE_TIP"],
                        PAD + 20, yo)
                    cbArcOutside:SetChecked(rOrbsDB.runeArcOutside ~= false)
                    cbArcOutside:SetScript("OnClick", function(self)
                        rOrbsDB.runeArcOutside = self:GetChecked()
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    tinsert(arcWidgets, cbArcOutside)
                    if cbArcOutside.label then tinsert(arcWidgets, cbArcOutside.label) end
                    if cbArcOutside.descLabel then tinsert(arcWidgets, cbArcOutside.descLabel) end
                    Panel[key].cbRuneArcOutside = cbArcOutside
                    yo = yo - 50
                    -- Extra gap before Arc Offset sliders (below checkbox + description)
                    yo = yo - 14

                    local sArcOffX, lArcOffX = MakeSlider(pane, L["RUNE_ARC_OFFX_LABEL"],
                        L["RUNE_ARC_OFFX_TIP"],
                        -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, L["RUNE_ARC_OFFY_LABEL"],
                        L["RUNE_ARC_OFFY_TIP"],
                        -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

                    local sArcGap, lArcGap = MakeSlider(pane, L["RUNE_HORIZ_SPACING"], L["RUNE_HORIZ_SPACING_TIP"],
                        0, 30, 1, PAD + 20, yo, SLIDER_W)
                    SetSliderHandler(sArcGap, function(self, val)
                        rOrbsDB.runeHorizSpacing = val
                        if Panel[key].sRuneHorizSpacing and math.abs(Panel[key].sRuneHorizSpacing:GetValue() - val) > 0.001 then
                            Panel[key].sRuneHorizSpacing:SetValue(val)
                        end
                        if ns.RunePoints then ns.RunePoints.ForceRefresh() end
                    end)
                    sArcGap:SetValue((rOrbsDB and rOrbsDB.runeHorizSpacing) or 6)
                    tinsert(arcWidgets, sArcGap) ; tinsert(arcWidgets, lArcGap)
                    tinsert(arcSliders, sArcGap)
                    Panel[key].sArcRuneSpacing = sArcGap ; yo = yo - 46

                    yoAfterArc = yo
                    -- Initial visibility; if horizontal, restore yo so arc
                    -- widgets don't leave a whitespace gap.
                    local initIsHoriz = ((rOrbsDB and rOrbsDB.runePointsPosition) or "arc") == "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
                    yo = yoBeforeHoriz
                    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 Panel[key].sArcRuneSpacing and math.abs(Panel[key].sArcRuneSpacing:GetValue() - val) > 0.001 then
                            Panel[key].sArcRuneSpacing:SetValue(val)
                        end
                        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 "arc") == "horizontal"
                    SetHorizSubOptionsVisible(isHoriz)
                    if not isHoriz then
                        -- Was: yo = yoBeforeHoriz — that placed the next section on top of the arc sliders.
                        yo = yoAfterArc
                    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"])
                            SetTextureAccentDanger(self.innerTex)
                            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(L["COMBO_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, L["COMBO_BAR_SCALE"],
                    L["COMBO_BAR_SCALE_TIP"],
                    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

                -- Offsets are from screen centre; span ~half UIParent + margin so the bar can reach any screen edge.
                local _uiw = (UIParent and UIParent:GetWidth()) or 1920
                local _uih = (UIParent and UIParent:GetHeight()) or 1080
                _uiw = math.max(_uiw, 800)
                _uih = math.max(_uih, 600)
                local _comboRx = math.ceil(_uiw * 0.5 + 250)
                local _comboRy = math.ceil(_uih * 0.5 + 250)

                local sComboOffX, _ = MakeSlider(pane, L["COMBO_BAR_OFFX"],
                    L["COMBO_BAR_OFFX_TIP"],
                    -_comboRx, _comboRx, 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, L["COMBO_BAR_OFFY"],
                    L["COMBO_BAR_OFFY_TIP"],
                    -_comboRy, _comboRy, 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,
                    L["COMBAT_ICON_SHOW"],
                    L["COMBAT_ICON_SHOW_TIP"],
                    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(val and L["COMBAT_ICON_CHAT_ON"] or L["COMBAT_ICON_CHAT_OFF"])
                end)
                Panel[key].cbPlayerCombatIcon = cbPlayerCombat ; yo = yo - 50

                -- Size / position sliders for Player Combat Icon
                local sPCombatSize, _ = MakeSlider(pane, L["COMBAT_ICON_SIZE"],
                    L["COMBAT_ICON_SIZE_TIP"],
                    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, L["COMBAT_ICON_OFFX"],
                    L["COMBAT_ICON_OFFX_TIP"],
                    -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, L["COMBAT_ICON_OFFY"],
                    L["COMBAT_ICON_OFFY_TIP"],
                    -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,
                    L["RESTING_ICON_SHOW"],
                    L["RESTING_ICON_SHOW_TIP"],
                    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(val and L["RESTING_ICON_CHAT_ON"] or L["RESTING_ICON_CHAT_OFF"])
                end)
                Panel[key].cbPlayerRestingIcon = cbPlayerResting ; yo = yo - 50

                -- Size / position sliders for Player Resting Icon
                local sPRestSize, _ = MakeSlider(pane, L["RESTING_ICON_SIZE"],
                    L["RESTING_ICON_SIZE_TIP"],
                    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, L["RESTING_ICON_OFFX"],
                    L["RESTING_ICON_OFFX_TIP"],
                    -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, L["RESTING_ICON_OFFY"],
                    L["RESTING_ICON_OFFY_TIP"],
                    -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, L["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, L["RESTING_ICON_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(L["RESTING_ICON_COLOR_RESET"])
                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,
                    L["PLAYER_LEADER_ICON_SHOW"],
                    L["PLAYER_LEADER_ICON_SHOW_TIP"],
                    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(val and L["PLAYER_LEADER_ICON_CHAT_ON"] or L["PLAYER_LEADER_ICON_CHAT_OFF"])
                end)
                Panel[key].cbPlayerLeaderIcon = cbPlayerLeader ; yo = yo - 50

                -- Size / position sliders for Player Leader Icon
                local sPLeaderSize, _ = MakeSlider(pane, L["LEADER_ICON_SIZE"],
                    L["LEADER_ICON_SIZE_TIP"],
                    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, L["LEADER_ICON_OFFX"],
                    L["LEADER_ICON_OFFX_TIP"],
                    -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, L["LEADER_ICON_OFFY"],
                    L["LEADER_ICON_OFFY_TIP"],
                    -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  = L["FIGURE_MODE_ANGEL"],
                    diablo = L["FIGURE_MODE_DIABLO"],
                    skull  = L["FIGURE_MODE_SKULL"],
                    demon  = L["FIGURE_MODE_DEMON"],
                    none   = L["FIGURE_MODE_NONE"],
                }
                local ddFigureMode, _ = MakeDropdown(pane,
                    L["SECTION_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(string.format(L["CHAT_FIGURE_HEALTH"], figureModeLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddFigureMode, function(self, level)
                    local current = rOrbsDB.healthFigureMode or "angel"
                    local entries = {
                        { value = "angel",  text = L["FIGURE_MODE_ANGEL"]  },
                        { value = "diablo", text = L["FIGURE_MODE_DIABLO"] },
                        { value = "skull",  text = L["FIGURE_MODE_SKULL"]  },
                        { value = "demon",  text = L["FIGURE_MODE_DEMON"]  },
                        { value = "none",   text = L["FIGURE_MODE_NONE"] },
                    }
                    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 L["FIGURE_MODE_ANGEL"])
                    UIDropDownMenu_SetSelectedValue(ddFigureMode, cur)
                end
                yo = yo - 56

                local sFigureScale, _ = MakeSlider(pane, L["ANGEL_SCALE"], L["ANGEL_SCALE_TIP"], 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, L["ANGEL_OFFX"], L["ANGEL_OFFX_TIP"], -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, L["ANGEL_OFFY"], L["ANGEL_OFFY_TIP"], -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  = L["FIGURE_MODE_ANGEL"],
                    demon  = L["FIGURE_MODE_DEMON"],
                    skull  = L["FIGURE_MODE_SKULL"],
                    diablo = L["FIGURE_MODE_DIABLO"],
                    none   = L["FIGURE_MODE_NONE"],
                }
                local ddPowerFigure, _ = MakeDropdown(pane,
                    L["SECTION_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(string.format(L["CHAT_FIGURE_POWER"], powerFigureLabels[val] or val))
                end
                UIDropDownMenu_Initialize(ddPowerFigure, function(self, level)
                    local current = rOrbsDB.powerFigureMode or "angel"
                    local entries = {
                        { value = "angel",  text = L["FIGURE_MODE_ANGEL"]  },
                        { value = "demon",  text = L["FIGURE_MODE_DEMON"]  },
                        { value = "skull",  text = L["FIGURE_MODE_SKULL"]  },
                        { value = "diablo", text = L["FIGURE_MODE_DIABLO"] },
                        { value = "none",   text = L["FIGURE_MODE_NONE"] },
                    }
                    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 L["FIGURE_MODE_ANGEL"])
                    UIDropDownMenu_SetSelectedValue(ddPowerFigure, cur)
                end
                yo = yo - 56

                local sDemonScale, _ = MakeSlider(pane, L["DEMON_SCALE"], L["DEMON_SCALE_TIP"], 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, L["DEMON_OFFX"], L["DEMON_OFFX_TIP"], -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, L["DEMON_OFFY"], L["DEMON_OFFY_TIP"], -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,
                L["TARGET_ORB_CLASS_COLOR"],
                L["TARGET_ORB_CLASS_COLOR_TIP"],
                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(val and L["CHAT_ORB_CLASS_COLOR_ON"] or L["CHAT_ORB_CLASS_COLOR_OFF"])
              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,
                L["RING_SHOW"],
                L["RING_SHOW_ORB_TIP"],
                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(val and L["CHAT_DECOR_RING_SHOWN"] or L["CHAT_DECOR_RING_HIDDEN"])
            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 = class color
                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

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

                -- ── BLOCK 2: COLORS ────────────────────────────────────────
                -- Cast color (fallback) — label + swatch below
                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 color — label + swatch below
                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 color — label + swatch below
                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

                -- ── SEPARATOR ───────────────────────────────────────────────
                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 opacity
                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

                -- Fade speed
                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

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

                -- ── BLOCK 2: COLORS ────────────────────────────────────────
                -- Cast color — label + swatch below
                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 color — label + swatch below
                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 color — label + swatch below
                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

                -- ── SEPARATOR ───────────────────────────────────────────────
                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 opacity
                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

                -- Fade speed
                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.targetAurasMode = nil
                rOrbsDB.targetBuffSide = nil
                rOrbsDB.targetBuffOffX = nil
                rOrbsDB.targetBuffOffY = nil
                rOrbsDB.targetBuffSize = nil
                rOrbsDB.targetBuffScale = nil
                rOrbsDB.targetBuffSpacing = nil
                rOrbsDB.targetBuffCountFontSize = nil
                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
                if ns.RefreshTargetAurasVisibility   then ns.RefreshTargetAurasVisibility()   end
                if ns.ApplyTargetBuffLayout          then ns.ApplyTargetBuffLayout()          end
                ns.GUI:ChatMsg("|cffffff00" .. L["SUBNAV_ICONS"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end) ; yo = yo - 44

            -- ── BUFFS / DEBUFFS (Ziel) ───────────────────────────────────
            MakeSep(pane, yo) ; yo = yo - 14
            local ddTargetAuras, _ = MakeDropdown(pane,
                L["TARGET_AURA_MODE_LABEL"],
                L["TARGET_AURA_MODE_TIP"],
                PAD + 20, yo, 260)
            Panel[key].ddTargetAuras = ddTargetAuras
            yo = yo - 52

            local lblTargetAurasHint = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            lblTargetAurasHint:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD + 20, yo)
            lblTargetAurasHint:SetWidth(math.max(280, PANEL_W - SUBNAV_W - PAD * 4))
            lblTargetAurasHint:SetJustifyH("LEFT")
            lblTargetAurasHint:SetWordWrap(true)
            lblTargetAurasHint:SetNonSpaceWrap(false)
            lblTargetAurasHint:SetTextColor(C.text[1] * 0.82, C.text[2] * 0.82, C.text[3] * 0.82, 1)
            Panel[key].lblTargetAurasHint = lblTargetAurasHint

            local auraModeLabels = {
                off = L["TARGET_AURA_MODE_OFF"],
                mouseover = L["TARGET_AURA_MODE_MOUSEOVER"],
                always = L["TARGET_AURA_MODE_ALWAYS"],
            }
            local function normalizeTargetAuraMode(m)
                if m == "off" or m == "mouseover" or m == "always" then return m end
                return "mouseover"
            end
            local function applyTargetAuraHint(mode)
                mode = normalizeTargetAuraMode(mode)
                if mode == "mouseover" then
                    lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_MOUSEOVER_INFO"])
                elseif mode == "always" then
                    lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_ALWAYS_INFO"])
                else
                    lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_OFF_INFO"])
                end
            end
            local function syncTargetAuraDropdownFromDB()
                local v = normalizeTargetAuraMode(rOrbsDB.targetAurasMode)
                UIDropDownMenu_SetText(ddTargetAuras, auraModeLabels[v])
                UIDropDownMenu_SetSelectedValue(ddTargetAuras, v)
                applyTargetAuraHint(v)
            end
            UIDropDownMenu_Initialize(ddTargetAuras, function(self, level)
                for _, opt in ipairs({
                    { v = "off",       t = auraModeLabels.off },
                    { v = "mouseover", t = auraModeLabels.mouseover },
                    { v = "always",    t = auraModeLabels.always },
                }) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = opt.t
                    info.value = opt.v
                    info.checked = (normalizeTargetAuraMode(UIDropDownMenu_GetSelectedValue(ddTargetAuras) or rOrbsDB.targetAurasMode) == opt.v)
                    info.func = function()
                        rOrbsDB.targetAurasMode = opt.v
                        syncTargetAuraDropdownFromDB()
                        if ns.RefreshTargetAurasVisibility then ns.RefreshTargetAurasVisibility() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            syncTargetAuraDropdownFromDB()
            do
                local hh = lblTargetAurasHint:GetStringHeight()
                if not hh or hh < 16 then hh = 40 end
                yo = yo - hh - 20
            end

            -- Buff layout: side, offsets, size
            local ddTargetBuffSide, _ = MakeDropdown(pane,
                L["TARGET_BUFF_SIDE"],
                L["TARGET_BUFF_SIDE_TIP"],
                PAD + 20, yo, 220)
            Panel[key].ddTargetBuffSide = ddTargetBuffSide
            local buffSideLabels = {
                bottom = L["TARGET_BUFF_SIDE_BOTTOM"],
                top    = L["TARGET_BUFF_SIDE_TOP"],
                left   = L["TARGET_BUFF_SIDE_LEFT"],
                right  = L["TARGET_BUFF_SIDE_RIGHT"],
            }
            local function normalizeTargetBuffSide(s)
                if buffSideLabels[s] then return s end
                return "bottom"
            end
            local function syncTargetBuffSideDropdown()
                local side = "bottom"
                if rOrbsDB.targetBuffSide then
                    side = normalizeTargetBuffSide(rOrbsDB.targetBuffSide)
                elseif rOrbsDB.targetBuffOffY and tonumber(rOrbsDB.targetBuffOffY) < 0 then
                    side = "top"
                end
                UIDropDownMenu_SetText(ddTargetBuffSide, buffSideLabels[side] or side)
                UIDropDownMenu_SetSelectedValue(ddTargetBuffSide, side)
            end
            UIDropDownMenu_Initialize(ddTargetBuffSide, function(self, level)
                for _, opt in ipairs({
                    { v = "bottom", t = buffSideLabels.bottom },
                    { v = "top",    t = buffSideLabels.top },
                    { v = "left",   t = buffSideLabels.left },
                    { v = "right",  t = buffSideLabels.right },
                }) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = opt.t
                    info.value = opt.v
                    info.checked = (normalizeTargetBuffSide(rOrbsDB.targetBuffSide or (rOrbsDB.targetBuffOffY and tonumber(rOrbsDB.targetBuffOffY) < 0 and "top" or "bottom")) == opt.v)
                    info.func = function()
                        rOrbsDB.targetBuffSide = opt.v
                        syncTargetBuffSideDropdown()
                        if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            syncTargetBuffSideDropdown()
            yo = yo - 56

            local sTargetBuffOffX, _ = MakeSlider(pane,
                L["TARGET_BUFF_OFFX"],
                L["TARGET_BUFF_OFFX_TIP"],
                -120, 120, 1, PAD + 20, yo, SLIDER_W)
            sTargetBuffOffX:SetValue(rOrbsDB.targetBuffOffX or 0)
            SetSliderHandler(sTargetBuffOffX, function(_, val)
                rOrbsDB.targetBuffOffX = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffOffX = sTargetBuffOffX
            yo = yo - 52

            local sTargetBuffY, _ = MakeSlider(pane,
                L["TARGET_BUFF_OFFY"],
                L["TARGET_BUFF_OFFY_TIP"],
                -120, 120, 1, PAD + 20, yo, SLIDER_W)
            sTargetBuffY:SetValue(rOrbsDB.targetBuffOffY or 8)
            SetSliderHandler(sTargetBuffY, function(_, val)
                rOrbsDB.targetBuffOffY = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffOffY = sTargetBuffY
            yo = yo - 52

            local sTargetBuffSize, _ = MakeSlider(pane,
                L["TARGET_BUFF_SIZE"],
                L["TARGET_BUFF_SIZE_TIP"],
                12, 40, 1, PAD + 20, yo, SLIDER_W)
            sTargetBuffSize:SetValue(rOrbsDB.targetBuffSize or 22)
            SetSliderHandler(sTargetBuffSize, function(_, val)
                rOrbsDB.targetBuffSize = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffSize = sTargetBuffSize
            yo = yo - 52

            local sTargetBuffScale, _ = MakeSlider(pane,
                L["TARGET_BUFF_ROW_SCALE"],
                L["TARGET_BUFF_ROW_SCALE_TIP"],
                0.4, 1.8, 0.05, PAD + 20, yo, SLIDER_W)
            sTargetBuffScale:SetValue(rOrbsDB.targetBuffScale or 1.0)
            SetSliderHandler(sTargetBuffScale, function(_, val)
                rOrbsDB.targetBuffScale = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffScale = sTargetBuffScale
            yo = yo - 52

            local sTargetBuffSpacing, _ = MakeSlider(pane,
                L["TARGET_BUFF_SPACING"],
                L["TARGET_BUFF_SPACING_TIP"],
                0, 12, 1, PAD + 20, yo, SLIDER_W)
            sTargetBuffSpacing:SetValue(rOrbsDB.targetBuffSpacing or 4)
            SetSliderHandler(sTargetBuffSpacing, function(_, val)
                rOrbsDB.targetBuffSpacing = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffSpacing = sTargetBuffSpacing
            yo = yo - 52

            local sTargetBuffCount, _ = MakeSlider(pane,
                L["TARGET_BUFF_COUNT_FONT"],
                L["TARGET_BUFF_COUNT_FONT_TIP"],
                0, 24, 1, PAD + 20, yo, SLIDER_W)
            sTargetBuffCount:SetValue(rOrbsDB.targetBuffCountFontSize or 0)
            SetSliderHandler(sTargetBuffCount, function(_, val)
                rOrbsDB.targetBuffCountFontSize = val
                if ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
            end)
            Panel[key].sTargetBuffCount = sTargetBuffCount
            yo = yo - 52

            -- ── 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(val and L["COMBAT_ICON_CHAT_ON"] or L["COMBAT_ICON_CHAT_OFF"])
            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(val and L["PLAYER_LEADER_ICON_CHAT_ON"] or L["PLAYER_LEADER_ICON_CHAT_OFF"])
            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, L["EFFECTS_SECTION"], 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
                    local defSpk  = defs[cfgKey].spark     and defs[cfgKey].spark.alpha     or 1.0
                    local defSpkC = defs[cfgKey].spark     and defs[cfgKey].spark.color
                    local defSpkMatch = defs[cfgKey].spark and defs[cfgKey].spark.matchFillColor == true
                    db2.char[cfgKey].galaxies.alpha  = defGal
                    db2.char[cfgKey].bubbles.alpha   = defBub
                    db2.char[cfgKey].highlight.alpha = defHigh
                    if db2.char[cfgKey].spark then
                        db2.char[cfgKey].spark.alpha = defSpk
                        db2.char[cfgKey].spark.matchFillColor = defSpkMatch
                        if defSpkC then
                            db2.char[cfgKey].spark.color = { r = defSpkC.r, g = defSpkC.g, b = defSpkC.b }
                        end
                    end
                    ns.GUI:ApplySparkMatchFill(cfgKey, defSpkMatch)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "galaxies",  defGal)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "bubbles",   defBub)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "highlight", defHigh)
                    ns.GUI:ApplyLayerAlpha(cfgKey, "spark",     defSpk)
                    if defSpkC then ns.GUI:ApplySparkColor(cfgKey, defSpkC.r, defSpkC.g, defSpkC.b) end
                    -- 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
                    if Panel[key] and Panel[key].sSpk  then Panel[key].sSpk:SetValue(defSpk)   end
                    if Panel[key] and Panel[key].sparkColSwatch and defSpkC then
                        Panel[key].sparkColSwatch:SetColor(defSpkC.r, defSpkC.g, defSpkC.b)
                    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(L["CHAT_EFFECTS_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

        local function RefreshSparkColorControls()
            local matched = ns.IsOrbSparkMatchFill(cfgKey)
            if Panel[key].sparkColSwatch then
                if Panel[key].sparkColSwatch.SetEnabled then
                    Panel[key].sparkColSwatch:SetEnabled(not matched)
                end
                Panel[key].sparkColSwatch:SetAlpha(matched and 0.45 or 1)
            end
            if Panel[key].sparkColLabel then
                Panel[key].sparkColLabel:SetAlpha(matched and 0.45 or 1)
            end
        end

        local cbSparkMatch = MakeCheckbox(pane, L["ORB_SPARK_MATCH_FILL"],
            L["ORB_SPARK_MATCH_FILL_TIP"],
            PAD, yo)
        cbSparkMatch:SetChecked(ns.IsOrbSparkMatchFill(cfgKey))
        cbSparkMatch:SetScript("OnClick", function(self)
            local val = self:GetChecked() == true
            ns.GUI:ApplySparkMatchFill(cfgKey, val)
            RefreshSparkColorControls()
        end)
        Panel[key].cbSparkMatch = cbSparkMatch
        yo = yo - 32

        -- SPARK COLOR (independent when matchFillColor is off)
        local sparkColLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
        Panel[key].sparkColLabel = sparkColLabel
        sparkColLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", PAD, yo)
        sparkColLabel:SetText(L["ORB_SPARK_COLOR"])
        sparkColLabel:SetTextColor(C.text[1], C.text[2], C.text[3])
        yo = yo - 22

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

        local sparkColSwatch
        sparkColSwatch = MakeColorSwatch(pane, PAD, yo,
            initSparkCol.r, initSparkCol.g, initSparkCol.b, L["ORB_SPARK_COLOR"], function()
            ensureCharCfg()
            if not ns.db or not ns.db.char[cfgKey] then return end
            local cr, cg, cb = sparkColSwatch:GetColor()
            ns.ColorPickerHelper:Open({
                r = cr, g = cg, b = cb,
                onChange = function(r, g, b)
                    sparkColSwatch:SetColor(r, g, b)
                    ns.GUI:ApplySparkColor(cfgKey, r, g, b)
                end,
                onCancel = function()
                    local c = ns.db.char[cfgKey].spark and ns.db.char[cfgKey].spark.color
                    if c then
                        sparkColSwatch:SetColor(c.r, c.g, c.b)
                        ns.GUI:ApplySparkColor(cfgKey, c.r, c.g, c.b)
                    end
                end,
            })
        end)
        Panel[key].sparkColSwatch = sparkColSwatch
        RefreshSparkColorControls()
        yo = yo - 44

        -- SPARK / SPLIT (opacity of the spark texture at the orb water-line, like
        -- oUF_Diablo's "Split Opacity"). Applies to all orbs incl. the player orb.
        local sSpk, _ = MakeSlider(pane, L["ORB_SPARK_ALPHA"], L["ORB_SPARK_ALPHA_TIP"],
            0, 1, 0.05, PAD, yo, SLIDER_W)
        SetSliderHandler(sSpk, function(self, val)
            ensureCharCfg()
            if ns.db and ns.db.char[cfgKey] and ns.db.char[cfgKey].spark then ns.db.char[cfgKey].spark.alpha = val end
            if rOrbs_DB_CHAR and rOrbs_DB_CHAR[cfgKey] and rOrbs_DB_CHAR[cfgKey].spark then rOrbs_DB_CHAR[cfgKey].spark.alpha = val end
            ns.GUI:ApplyLayerAlpha(cfgKey, "spark", val)
        end)
        Panel[key].sSpk = sSpk ; yo = yo - 52

        -- Empty vertical gap before gloss texture label (no separator line)
        yo = yo - 24

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

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

        -- Side-by-side columns made the desc labels share the same RIGHT anchor on the
        -- pane, so both descriptions stretched full width and overlapped (esp. in deDE).
        local cbGlossNew = MakeCheckbox(pane, L["GLOSS_NEW"],
            L["GLOSS_NEW_TIP"], PAD, yo)
        cbGlossNew:SetChecked(currentGloss ~= "classic")
        yo = yo - 58

        local cbGlossClassic = MakeCheckbox(pane, L["GLOSS_CLASSIC"],
            L["GLOSS_CLASSIC_TIP"], PAD, 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(string.format(L["CHAT_GLOSS_NEW"], cfgKey))
        end)
        cbGlossClassic:SetScript("OnClick", function(self)
            applyGloss("classic")
            ns.GUI:ChatMsg(string.format(L["CHAT_GLOSS_CLASSIC"], cfgKey))
        end)
        Panel[key].cbGlossNew     = cbGlossNew
        Panel[key].cbGlossClassic = cbGlossClassic
        yo = yo - 58

        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,
            L["ORB_MODEL_SHOW"],
            L["ORB_MODEL_SHOW_TIP"],
            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(val and L["CHAT_BUILTIN_MODEL_SHOWN"] or L["CHAT_BUILTIN_MODEL_HIDDEN"])
        end)
        Panel[key].cbOrbModel = cbOrbModel ; yo = yo - 50

        -- Model Opacity (applies to built-in 3D model)
        local sOrbModelAlpha, _ = MakeSlider(pane, L["ORB_MODEL_ALPHA"],
            L["ORB_MODEL_ALPHA_TIP"],
            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,
            L["TMPL_SHOW_PRESET"],
            L["TMPL_SHOW_PRESET_TIP"],
            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(string.format(L["CHAT_TEMPLATE_APPLIED"], cfgKey, entry.label))
                end
                local pt = ns.orbTemplates and ns.orbTemplates[entry.key]
                if pt and (pt.displayInfoID or 0) > 0 then
                    info.previewOrbTemplate = pt
                end
                UIDropDownMenu_AddButton(info, level)
            end
        end)
        do
            local currentKey = rOrbsDB and rOrbsDB["orbTemplate_"..cfgKey] or "none"
            local dispName = L["TMPL_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 offMin = ns.ORB_TMPL_LIVE_OFFSET_MIN or -3.0
        local offMax = ns.ORB_TMPL_LIVE_OFFSET_MAX or 3.0
        local prevOffMin = ns.ORB_TMPL_PREVIEW_OFFSET_MIN or -24.0
        local prevOffMax = ns.ORB_TMPL_PREVIEW_OFFSET_MAX or 24.0

        local sTmplScale, _ = MakeSlider(pane, L["TMPL_SCALE_SLIDER"], L["TMPL_SCALE_SLIDER_TIP"], 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)
        -- Initial values from DB (not hard 1.0): else SetValue → OnValueChanged
        -- overwrites saved tmplScale_* on first panel build.
        do
            local sv = (rOrbsDB and rOrbsDB["tmplScale_"..cfgKey]) or 1.0
            sTmplScale:SetValue(sv)
        end
        yo = yo - 52

        local sTmplOffX, _ = MakeSlider(pane, L["ANGEL_OFFX"], L["TMPL_OFFSETX_SLIDER_TIP"], offMin, offMax, 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)
        do
            local ox = (rOrbsDB and rOrbsDB["tmplOffsetX_"..cfgKey]) or 0.0
            sTmplOffX:SetValue(ox)
        end
        yo = yo - 52

        local sTmplOffZ, _ = MakeSlider(pane, L["TMPL_OFFSET_Z_LABEL"], L["TMPL_OFFSETZ_SLIDER_TIP"], offMin, offMax, 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)
        do
            local oz = (rOrbsDB and rOrbsDB["tmplOffsetZ_"..cfgKey]) or 0.0
            sTmplOffZ:SetValue(oz)
        end
        yo = yo - 52

        -- sTmplOpacity removed: same as model opacity (sOrbModelAlpha) above.
        -- 3D model opacity is controlled only via that slider.

        -- ============================================================
        --  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

        -- idWrap must anchor inside the section settingsContainer with the same
        -- Y transform as CreateFontString (pane:CreateFontString). Raw SetPoint
        -- to `pane` would place the frame in pane coords while parent is the
        -- container — clipped by SetClipsChildren, so the EditBox disappears.
        local idWrapParent, idWrapY = _SectionRedirect(pane, yo + 2)
        local idWrap = CreateFrame("Frame", nil, pane)
        idWrap:SetPoint("TOPLEFT", idWrapParent, "TOPLEFT", PAD, idWrapY)
        idWrap:SetSize(130, 26)
        local idBorder = idWrap:CreateTexture(nil, "BACKGROUND")
        idBorder:SetAllPoints()
        idBorder:SetColorTexture(C.border[1], C.border[2], C.border[3], 1)
        local idInner = idWrap:CreateTexture(nil, "ARTWORK")
        idInner:SetPoint("TOPLEFT", idWrap, "TOPLEFT", 1, -1)
        idInner:SetPoint("BOTTOMRIGHT", idWrap, "BOTTOMRIGHT", -1, 1)
        idInner:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 1)

        local idBox = CreateFrame("EditBox", nil, idWrap)
        idBox:SetPoint("TOPLEFT", idInner, "TOPLEFT", 5, -3)
        idBox:SetPoint("BOTTOMRIGHT", idInner, "BOTTOMRIGHT", -5, 3)
        idBox:SetAutoFocus(false)
        idBox:SetFontObject("ChatFontNormal")
        idBox:SetTextColor(C.text[1], C.text[2], C.text[3], 1)
        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"],
            prevOffMin, prevOffMax, 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"],
            prevOffMin, prevOffMax, 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, AccentDangerBtnRGB())
        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(string.format(L["CHAT_CUSTOM_TMPL_SELECTED"], cfgKey, entry.label))
                end
                if entry.key ~= "custom-none" then
                    local ct = rOrbsDB and rOrbsDB.customTemplates and rOrbsDB.customTemplates[entry.key]
                    if ct and (ct.displayInfoID or 0) > 0 then
                        info.previewOrbTemplate = ct
                    end
                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 = L["TMPL_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 L["TMPL_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, AccentDangerBtnRGB())
        delCustomBtn:SetScript("OnClick", function()
            local selKey = UIDropDownMenu_GetSelectedValue(ddCustomTmpl)
            if not selKey or selKey == "custom-none" then
                ns.GUI:ChatMsg(L["CUSTOM_TMPL_NONE_SELECTED"])
                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, L["TMPL_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, L["TEXT_HOVER_ONLY"],
            L["TEXT_HOVER_ONLY_TIP"], 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(val and L["CHAT_TEXT_HOVER_ON"] or L["CHAT_TEXT_HOVER_OFF"])
        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, L["TEXT_DISPLAY_MODE"],
            L["TEXT_DISPLAY_MODE_TIP"], PAD, yo, 180)
        Panel[key].ddTextMode = ddTextMode
        UIDropDownMenu_Initialize(ddTextMode, function(self, level)
            local currentMode = rOrbsDB["textMode_"..cfgKey] or "BOTH"
            local modes = {
                { text = L["TEXT_MODE_BOTH"],   value = "BOTH"    },
                { text = L["TEXT_MODE_VALUE"], value = "VALUE"   },
                { text = L["TEXT_MODE_PERCENT"], 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 = L["TEXT_MODE_BOTH"], VALUE = L["TEXT_MODE_VALUE"], PERCENT = L["TEXT_MODE_PERCENT"] }
                            UIDropDownMenu_SetText(ddTextMode, modeLabels[prev] or L["TEXT_MODE_BOTH"])
                            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 = L["TEXT_MODE_BOTH"], VALUE = L["TEXT_MODE_VALUE"], PERCENT = L["TEXT_MODE_PERCENT"] }
            UIDropDownMenu_SetText(ddTextMode, modeLabels[mode] or L["TEXT_MODE_BOTH"])
            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(L["TEXT_COLOR_LABEL"])
        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, L["TEXT_COLOR_PICKER_TIP"],
            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, L["ORB_COLOR_RESET_BTN"], 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(L["CHAT_TEXT_COLOR_RESET"])
        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 nkFont  = (cfgKey == "TARGET_HEALTH") and "targetNameFont"       or (cfgKey == "TOT_HEALTH") and "totNameFont"     or "petNameFont"
            local nkFFall = (cfgKey == "TARGET_HEALTH") and "font_TARGET_HEALTH"   or (cfgKey == "TOT_HEALTH") and "font_TOT_HEALTH" or "font_PET_HEALTH"
            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[nkFont]  = nil ; rOrbsDB[nkFSize] = nil
                rOrbsDB[nkPos]   = nil ; rOrbsDB[nkOffY]  = nil
                if nkCC then rOrbsDB[nkCC] = nil end
                if cfgKey == "TARGET_HEALTH" then
                    rOrbsDB.targetNameClassIcon = nil
                    rOrbsDB.targetNameClassIconMode = nil
                end
                ns.GUI:ApplyNameShow(cfgKey, true)
                ns.GUI:ApplyNameFont(cfgKey)
                ns.GUI:ApplyNameFontSize(cfgKey, rOrbsDB[nkFSize] or 22)
                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 L["CHAT_NAME_SHOWN"] or L["CHAT_NAME_HIDDEN"])
            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 L["TARGET_NAME_POS_ABOVE"])
                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

            if cfgKey == "TARGET_HEALTH" then
            local cbNameClassIcon = MakeCheckbox(pane, L["TARGET_NAME_CLASSICON"],
                L["TARGET_NAME_CLASSICON_TIP"],
                PAD, yo)
            cbNameClassIcon:SetScript("OnClick", function(self)
                local val = self:GetChecked()
                rOrbsDB.targetNameClassIcon = val
                if ns.UpdateTargetNameClassIcon then ns.UpdateTargetNameClassIcon() end
            end)
            cbNameClassIcon:SetChecked(rOrbsDB.targetNameClassIcon ~= false)
            Panel[key].cbNameClassIcon = cbNameClassIcon
            yo = yo - 50

            local ddNameClassIcon, _ = MakeDropdown(pane, L["TARGET_NAME_CLASSICON_MODE"],
                L["TARGET_NAME_CLASSICON_MODE_TIP"],
                PAD, yo, 240)
            Panel[key].ddNameClassIcon = ddNameClassIcon
            UIDropDownMenu_Initialize(ddNameClassIcon, function(self, level)
                local currentMode = ns.ResolveTargetNameClassIconMode and ns.ResolveTargetNameClassIconMode() or "custom"
                local modes = ns.TargetNameClassIconModes or {}
                for _, entry in ipairs(modes) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text    = L[entry.label] or entry.value
                    info.value   = entry.value
                    info.checked = (entry.value == currentMode)
                    info.func    = function()
                        UIDropDownMenu_SetSelectedValue(ddNameClassIcon, entry.value)
                        UIDropDownMenu_SetText(ddNameClassIcon, L[entry.label] or entry.value)
                        rOrbsDB.targetNameClassIconMode = entry.value
                        if ns.UpdateTargetNameClassIcon then ns.UpdateTargetNameClassIcon() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            do
                local mode = ns.ResolveTargetNameClassIconMode and ns.ResolveTargetNameClassIconMode() or "custom"
                local modeLabel = mode
                for _, entry in ipairs(ns.TargetNameClassIconModes or {}) do
                    if entry.value == mode then
                        modeLabel = L[entry.label] or entry.value
                        break
                    end
                end
                UIDropDownMenu_SetText(ddNameClassIcon, modeLabel)
                UIDropDownMenu_SetSelectedValue(ddNameClassIcon, mode)
            end
            yo = yo - 56
            end

            local ddNameFont, _ = MakeDropdown(pane, L["TARGET_NAME_FONT"],
                L["TARGET_NAME_FONT_TIP"],
                PAD, yo, 220)
            Panel[key].ddNameFont = ddNameFont
            UIDropDownMenu_Initialize(ddNameFont, function(self, level)
                local fonts = LSM:List("font")
                if not fonts then return end
                local currentFont = (rOrbsDB and rOrbsDB[nkFont]) or (rOrbsDB and rOrbsDB[nkFFall]) 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[nkFont] = fname
                            ns.GUI:ApplyNameFont(cfgKey, fname)
                            ns.GUI:ChatMsg(string.format(L["CHAT_FONT_CHANGED"], fname))
                        end
                        UIDropDownMenu_AddButton(info, level)
                    end
                end
            end)
            do
                local fname = (rOrbsDB and rOrbsDB[nkFont]) or (rOrbsDB and rOrbsDB[nkFFall]) or "Enchanted Land"
                UIDropDownMenu_SetText(ddNameFont, fname)
                UIDropDownMenu_SetSelectedValue(ddNameFont, fname)
            end
            yo = yo - 56

            -- 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

            if cfgKey == "TARGET_HEALTH" then
                yo = yo - 8
                MakeSep(pane, yo) ; yo = yo - 14

                local cbTargetQuest = MakeCheckbox(pane,
                    L["TARGET_QUEST_ICON_SHOW"],
                    L["TARGET_QUEST_ICON_SHOW_TIP"],
                    PAD, yo)
                cbTargetQuest:SetChecked(rOrbsDB.targetQuestIconShow ~= false)
                cbTargetQuest:SetScript("OnClick", function(self)
                    local val = self:GetChecked()
                    rOrbsDB.targetQuestIconShow = val
                    if ns.UpdateTargetQuestIcon then ns.UpdateTargetQuestIcon() end
                    ns.GUI:ChatMsg(val and L["TARGET_QUEST_ICON_CHAT_ON"] or L["TARGET_QUEST_ICON_CHAT_OFF"])
                end)
                Panel[key].cbTargetQuestIcon = cbTargetQuest ; yo = yo - 50

                local sTQuestSize, _ = MakeSlider(pane, L["PARTY_ICON_SIZE"], L["PARTY_ICON_SIZE_TIP"],
                    8, 48, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sTQuestSize, function(_, val)
                    rOrbsDB.targetQuestIconSize = val
                    if ns.UpdateTargetQuestIconLayout then ns.UpdateTargetQuestIconLayout() end
                end)
                sTQuestSize:SetValue(rOrbsDB.targetQuestIconSize or 18)
                Panel[key].sTQuestSize = sTQuestSize ; yo = yo - 46

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

                local sTQuestOffY, _ = MakeSlider(pane, L["PARTY_ICON_OFFY"], L["PARTY_ICON_OFFY_TIP"],
                    -20, 20, 1, PAD + 20, yo, SLIDER_W)
                SetSliderHandler(sTQuestOffY, function(_, val)
                    rOrbsDB.targetQuestIconOffY = val
                    if ns.UpdateTargetQuestIconLayout then ns.UpdateTargetQuestIconLayout() end
                end)
                sTQuestOffY:SetValue(rOrbsDB.targetQuestIconOffY or 0)
                Panel[key].sTQuestOffY = sTQuestOffY ; yo = yo - 46
            end
        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, AccentDangerBtnRGB())
        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.targetAurasMode         = nil
                        rOrbsDB.targetBuffSide            = nil
                        rOrbsDB.targetBuffOffX            = nil
                        rOrbsDB.targetBuffOffY            = nil
                        rOrbsDB.targetBuffSize            = nil
                        rOrbsDB.targetBuffScale           = nil
                        rOrbsDB.targetBuffSpacing         = nil
                        rOrbsDB.targetBuffCountFontSize   = nil
                        rOrbsDB["targetNameShow"]       = nil
                        rOrbsDB["targetNameFont"]       = nil
                        rOrbsDB["targetNameFontSize"]   = nil
                        rOrbsDB["targetNamePos"]        = nil
                        rOrbsDB["targetNameClassColor"] = nil
                        rOrbsDB["targetNameClassIcon"]  = nil
                        rOrbsDB["targetNameClassIconMode"] = nil
                        rOrbsDB["targetNameOffY"]       = nil
                        rOrbsDB["targetClassColor"]     = nil
                        rOrbsDB["targetRingShow"]       = nil
                    elseif cfgKey == "TOT_HEALTH" then
                        rOrbsDB["totNameShow"]       = nil
                        rOrbsDB["totNameFont"]       = 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["petNameFont"]     = 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()
                    if cfgKey == "TARGET_HEALTH" and ns.ApplyTargetBuffLayout then ns.ApplyTargetBuffLayout() end
                end,
                timeout = 0, whileDead = true, hideOnEscape = true,
            }
            StaticPopup_Show("RORBS_RESET_ORB_"..cfgKey)
        end)
        FinishOrbTab()  -- builds sub-nav; wire-up only after that
        -- Wire _resetAction on sub-nav reset button to the dialog
        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  (sub-tabs: layout / fonts / icons / reset)
    -- ============================================================
    do
        -- Create sub-panes
        local pLayout    = BuildSubPane("party.layout")
        local pAuras     = BuildSubPane("party.auras")
        local pFont      = BuildSubPane("party.font")
        local pIcons     = BuildSubPane("party.icons")
        local pClickCast = BuildSubPane("party.clickcast")
        local pReset     = BuildSubPane("party.reset")

        Panel.party = Panel.party or {}

        -- Switch active pane; save/restore yo per pane
        local curPane  = pLayout
        local paneYo   = {}  -- yo offset per 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

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

        -- ---- Layout H/V ----------------------------------------
        MakeSectionHeader(p, L["PARTY_LAYOUT"], PAD, yo, true, function()
            rOrbsDB.partyVertical = nil
            rOrbsDB.partyHideBlizzardCompactRaidManager = nil
            if ns.ApplyBlizzardCompactRaidFrameManagerSetting then
                ns.ApplyBlizzardCompactRaidFrameManagerSetting()
            end
            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)

        local cbHideCrm = MakeCheckbox(p,
            L["PARTY_HIDE_BLIZZARD_CRM"],
            L["PARTY_HIDE_BLIZZARD_CRM_TIP"],
            PAD, yo)
        cbHideCrm:SetChecked(rOrbsDB.partyHideBlizzardCompactRaidManager == true)
        cbHideCrm:SetScript("OnClick", function(self)
            local want = self:GetChecked() and true or false
            local prev = (rOrbsDB.partyHideBlizzardCompactRaidManager == true)
            rOrbsDB.partyHideBlizzardCompactRaidManager = want
            if ns.ApplyBlizzardCompactRaidFrameManagerSetting then
                ns.ApplyBlizzardCompactRaidFrameManagerSetting()
            end
            if not want then return end
            C_Timer.After(0.25, function()
                if not self or not rOrbsDB then return end
                if rOrbsDB.partyHideBlizzardCompactRaidManager ~= want then return end
                local f = _G.CompactRaidFrameManager
                local bad = (not f) or (f.IsShown and f:IsShown())
                if not bad then return end
                rOrbsDB.partyHideBlizzardCompactRaidManager = prev
                if self.SetChecked then self:SetChecked(prev) end
                StaticPopupDialogs["RORBS_PARTY_CRM_RELOAD"] = {
                    text = L["PARTY_CRM_RELOAD_TEXT"],
                    button1 = L["ORB_RELOAD_ENABLE_BTN"],
                    button2 = CANCEL,
                    OnAccept = function()
                        rOrbsDB.partyHideBlizzardCompactRaidManager = want
                        ReloadUI()
                    end,
                    OnCancel = function() end,
                    timeout = 0, whileDead = true, hideOnEscape = false,
                }
                StaticPopup_Show("RORBS_PARTY_CRM_RELOAD")
            end)
        end)
        Panel.party.cbHideBlizzardCompactRaidManager = cbHideCrm
        yo = yo - 38

        -- ── SEPARATOR ───────────────────────────────────────────
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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, L["PARTY_PORTRAIT_3D"], "", PAD,       yo)
        local cb2D = MakeCheckbox(p, L["PARTY_PORTRAIT_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)

        -- ── SEPARATOR ───────────────────────────────────────────
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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)

        -- Set contentH for layout pane
        pLayout._contentH = math.abs(yo) + PAD + 40
        if pLayout._updateThumb then pLayout._updateThumb() end

        -- ════════════════════════════════════════════════════════
        --  PANE: PARTY AURAS (buff / debuff under HP bar)
        -- ════════════════════════════════════════════════════════
        do
            p = pAuras
            yo = -PAD

            local function normalizePartyAuraModeGui(m)
                if m == "off" or m == "on" or m == "mouseover" then return m end
                return "mouseover"
            end

            local auraRowLabels = {
                off = L["PARTY_ROW_AURA_OFF"],
                on = L["PARTY_ROW_AURA_ON"],
                mouseover = L["PARTY_ROW_AURA_MOUSEOVER"],
            }

            MakeSectionHeader(p, L["PARTY_AURAS_SECTION"], PAD, yo, true, function()
                if ns.ApplyPartyAuraPreview then ns.ApplyPartyAuraPreview(false) end
                if Panel and Panel.party and Panel.party.cbPartyAuraPreview then
                    local b = Panel.party.cbPartyAuraPreview
                    b._auraPreviewOn = false
                    if b.fs then b.fs:SetText(L["PARTY_AURAS_PREVIEW_BTN"]) end
                    if b.innerTex then b.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1) end
                end
                rOrbsDB.partyBuffAurasMode = nil
                rOrbsDB.partyDebuffAurasMode = nil
                rOrbsDB.partyDebuffSizeMul = nil
                rOrbsDB.partyAuraSpacing = nil
                rOrbsDB.partyAuraRowGap = nil
                rOrbsDB.partyAuraHostOffX = nil
                rOrbsDB.partyAuraHostOffY = nil
                rOrbsDB.partyAuraBuffOffX = nil
                rOrbsDB.partyAuraBuffOffY = nil
                rOrbsDB.partyAuraDebuffOffX = nil
                rOrbsDB.partyAuraDebuffOffY = nil
                rOrbsDB.partyBuffIconSize = nil
                if ns.RespawnParty then ns.RespawnParty() end
                ns.GUI:ChatMsg("|cffffff00" .. L["PARTY_AURAS_SECTION"] .. "|r reset.")
                ns.GUI:RefreshValues()
            end)
            yo = yo - 44

            local lblHint = p:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            lblHint:SetPoint("TOPLEFT", p, "TOPLEFT", PAD, yo)
            lblHint:SetWidth(math.max(280, PANEL_W - SUBNAV_W - PAD * 4))
            lblHint:SetJustifyH("LEFT")
            lblHint:SetWordWrap(true)
            lblHint:SetTextColor(C.text[1] * 0.82, C.text[2] * 0.82, C.text[3] * 0.82, 1)
            lblHint:SetText(L["PARTY_AURAS_HINT"])
            yo = yo - (lblHint:GetStringHeight() or 36) - 8

            local lblLive = p:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
            lblLive:SetPoint("TOPLEFT", p, "TOPLEFT", PAD, yo)
            lblLive:SetWidth(math.max(280, PANEL_W - SUBNAV_W - PAD * 4))
            lblLive:SetJustifyH("LEFT")
            lblLive:SetWordWrap(true)
            lblLive:SetTextColor(C.text[1] * 0.78, C.text[2] * 0.78, C.text[3] * 0.78, 1)
            lblLive:SetText(L["PARTY_AURAS_LIVE_NOTE"])
            yo = yo - (lblLive:GetStringHeight() or 48) - 10

            local prevAuras = MakeButton(p, L["PARTY_AURAS_PREVIEW_BTN"], PAD + 16, yo, 200, 24, 0.08, 0.18, 0.08)
            prevAuras._auraPreviewOn = false
            prevAuras:SetScript("OnClick", function(self)
                if not (ns.fakePartyFrames and ns.fakePartyFrames[1]) then
                    if ns.GUI and ns.GUI.ChatMsg then
                        ns.GUI:ChatMsg(L["PARTY_AURAS_PREVIEW_NEED_PARTY"])
                    end
                    return
                end
                self._auraPreviewOn = not self._auraPreviewOn
                if self._auraPreviewOn then
                    self.fs:SetText(L["CUSTOM_TMPL_STOP"])
                    SetTextureAccentDanger(self.innerTex)
                    if ns.ApplyPartyAuraPreview then ns.ApplyPartyAuraPreview(true) end
                else
                    self.fs:SetText(L["PARTY_AURAS_PREVIEW_BTN"])
                    self.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1)
                    if ns.ApplyPartyAuraPreview then ns.ApplyPartyAuraPreview(false) end
                end
            end)
            prevAuras:SetScript("OnEnter", function(self)
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
                GameTooltip:AddLine(L["PARTY_AURAS_PREVIEW_TIP"], 1, 1, 1, true)
                GameTooltip:Show()
            end)
            prevAuras:SetScript("OnLeave", function()
                GameTooltip:Hide()
            end)
            Panel.party.cbPartyAuraPreview = prevAuras
            yo = yo - 36

            local ddBuffMode, _ = MakeDropdown(p,
                L["PARTY_BUFF_AURA_MODE"],
                L["PARTY_BUFF_AURA_MODE_TIP"],
                PAD + 16, yo, 260)
            Panel.party.ddPartyBuffAuras = ddBuffMode
            yo = yo - 52

            local function syncBuffAuraDd()
                local v = normalizePartyAuraModeGui(rOrbsDB.partyBuffAurasMode)
                UIDropDownMenu_SetText(ddBuffMode, auraRowLabels[v])
                UIDropDownMenu_SetSelectedValue(ddBuffMode, v)
            end
            UIDropDownMenu_Initialize(ddBuffMode, function(_, level)
                for _, opt in ipairs({
                    { v = "off",       t = auraRowLabels.off },
                    { v = "mouseover", t = auraRowLabels.mouseover },
                    { v = "on",        t = auraRowLabels.on },
                }) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = opt.t
                    info.value = opt.v
                    info.checked = (normalizePartyAuraModeGui(UIDropDownMenu_GetSelectedValue(ddBuffMode) or rOrbsDB.partyBuffAurasMode) == opt.v)
                    info.func = function()
                        rOrbsDB.partyBuffAurasMode = opt.v
                        syncBuffAuraDd()
                        if ns.ApplyPartyAuraVisibilityAll then ns.ApplyPartyAuraVisibilityAll() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            syncBuffAuraDd()

            local ddDebuffMode, _ = MakeDropdown(p,
                L["PARTY_DEBUFF_AURA_MODE"],
                L["PARTY_DEBUFF_AURA_MODE_TIP"],
                PAD + 16, yo, 260)
            Panel.party.ddPartyDebuffAuras = ddDebuffMode
            yo = yo - 52

            local function syncDebuffAuraDd()
                local v = normalizePartyAuraModeGui(rOrbsDB.partyDebuffAurasMode)
                UIDropDownMenu_SetText(ddDebuffMode, auraRowLabels[v])
                UIDropDownMenu_SetSelectedValue(ddDebuffMode, v)
            end
            UIDropDownMenu_Initialize(ddDebuffMode, function(_, level)
                for _, opt in ipairs({
                    { v = "off",       t = auraRowLabels.off },
                    { v = "mouseover", t = auraRowLabels.mouseover },
                    { v = "on",        t = auraRowLabels.on },
                }) do
                    local info = UIDropDownMenu_CreateInfo()
                    info.text = opt.t
                    info.value = opt.v
                    info.checked = (normalizePartyAuraModeGui(UIDropDownMenu_GetSelectedValue(ddDebuffMode) or rOrbsDB.partyDebuffAurasMode) == opt.v)
                    info.func = function()
                        rOrbsDB.partyDebuffAurasMode = opt.v
                        syncDebuffAuraDd()
                        if ns.ApplyPartyAuraVisibilityAll then ns.ApplyPartyAuraVisibilityAll() end
                    end
                    UIDropDownMenu_AddButton(info, level)
                end
            end)
            syncDebuffAuraDd()

            yo = yo - 8
            MakeSep(p, yo) ; yo = yo - 20

            local sBuffPx, _ = MakeSlider(p,
                L["PARTY_BUFF_ICON_SIZE"],
                L["PARTY_BUFF_ICON_SIZE_TIP"],
                8, 28, 1, PAD + 16, yo, 200)
            sBuffPx:SetValue(math.floor((rOrbsDB and rOrbsDB.partyBuffIconSize) or (rOrbsDB and rOrbsDB.partyAuraSize) or 12))
            SetSliderHandler(sBuffPx, function(self, val)
                val = math.floor(val + 0.5)
                rOrbsDB.partyBuffIconSize = val
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyBuffIconSize = sBuffPx
            yo = yo - 52

            local sDebuffMul, _ = MakeSlider(p,
                L["PARTY_DEBUFF_SIZE_MUL"],
                L["PARTY_DEBUFF_SIZE_MUL_TIP"],
                1.0, 2.0, 0.05, PAD + 16, yo, 200)
            sDebuffMul:SetValue((rOrbsDB and rOrbsDB.partyDebuffSizeMul) or 1.25)
            SetSliderHandler(sDebuffMul, function(self, val)
                rOrbsDB.partyDebuffSizeMul = val
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyDebuffSizeMul = sDebuffMul
            yo = yo - 52

            local sAuraSp, _ = MakeSlider(p,
                L["PARTY_AURA_SPACING"],
                L["PARTY_AURA_SPACING_TIP"],
                0, 8, 1, PAD + 16, yo, 200)
            sAuraSp:SetValue((rOrbsDB and rOrbsDB.partyAuraSpacing) or 3)
            SetSliderHandler(sAuraSp, function(self, val)
                rOrbsDB.partyAuraSpacing = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraSpacing = sAuraSp
            yo = yo - 52

            local sRowGap, _ = MakeSlider(p,
                L["PARTY_AURA_ROW_GAP"],
                L["PARTY_AURA_ROW_GAP_TIP"],
                0, 16, 1, PAD + 16, yo, 200)
            sRowGap:SetValue((rOrbsDB and rOrbsDB.partyAuraRowGap) or 4)
            SetSliderHandler(sRowGap, function(self, val)
                rOrbsDB.partyAuraRowGap = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraRowGap = sRowGap
            yo = yo - 52

            MakeSep(p, yo) ; yo = yo - 16

            local sHostX, _ = MakeSlider(p,
                L["PARTY_AURA_HOST_OFFX"],
                L["PARTY_AURA_HOST_OFFX_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sHostX:SetValue((rOrbsDB and rOrbsDB.partyAuraHostOffX) or 0)
            SetSliderHandler(sHostX, function(self, val)
                rOrbsDB.partyAuraHostOffX = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraHostOffX = sHostX
            yo = yo - 52

            local sHostY, _ = MakeSlider(p,
                L["PARTY_AURA_HOST_OFFY"],
                L["PARTY_AURA_HOST_OFFY_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sHostY:SetValue((rOrbsDB and rOrbsDB.partyAuraHostOffY) or -2)
            SetSliderHandler(sHostY, function(self, val)
                rOrbsDB.partyAuraHostOffY = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraHostOffY = sHostY
            yo = yo - 52

            local sBuffOX, _ = MakeSlider(p,
                L["PARTY_AURA_BUFF_OFFX"],
                L["PARTY_AURA_BUFF_OFFX_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sBuffOX:SetValue((rOrbsDB and rOrbsDB.partyAuraBuffOffX) or 0)
            SetSliderHandler(sBuffOX, function(self, val)
                rOrbsDB.partyAuraBuffOffX = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraBuffOffX = sBuffOX
            yo = yo - 52

            local sBuffOY, _ = MakeSlider(p,
                L["PARTY_AURA_BUFF_OFFY"],
                L["PARTY_AURA_BUFF_OFFY_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sBuffOY:SetValue((rOrbsDB and rOrbsDB.partyAuraBuffOffY) or 0)
            SetSliderHandler(sBuffOY, function(self, val)
                rOrbsDB.partyAuraBuffOffY = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraBuffOffY = sBuffOY
            yo = yo - 52

            local sDebOX, _ = MakeSlider(p,
                L["PARTY_AURA_DEBUFF_OFFX"],
                L["PARTY_AURA_DEBUFF_OFFX_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sDebOX:SetValue((rOrbsDB and rOrbsDB.partyAuraDebuffOffX) or 0)
            SetSliderHandler(sDebOX, function(self, val)
                rOrbsDB.partyAuraDebuffOffX = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraDebuffOffX = sDebOX
            yo = yo - 52

            local sDebOY, _ = MakeSlider(p,
                L["PARTY_AURA_DEBUFF_OFFY"],
                L["PARTY_AURA_DEBUFF_OFFY_TIP"],
                -40, 40, 1, PAD + 16, yo, 200)
            sDebOY:SetValue((rOrbsDB and rOrbsDB.partyAuraDebuffOffY) or 0)
            SetSliderHandler(sDebOY, function(self, val)
                rOrbsDB.partyAuraDebuffOffY = math.floor(val + 0.5)
                if ns.RespawnParty then ns.RespawnParty() end
            end)
            Panel.party.sPartyAuraDebuffOffY = sDebOY
            yo = yo - 52

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

        -- ════════════════════════════════════════════════════════
        --  PANE: FONTS
        -- ════════════════════════════════════════════════════════
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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"])
                        SetTextureAccentDanger(self.innerTex)
                        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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

        -- ── SEPARATOR ───────────────────────────────────────────
        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: click-cast (group frames)
        -- ════════════════════════════════════════════════════════
        p = pClickCast
        yo = -PAD
        yo = BuildClickCastPaneContent(
            p, yo,
            "clickCastPartyEnabled", "clickCastPartyLeft", "clickCastPartyMiddle", "clickCastPartyRight",
            Panel.party,
            L["CLICKCAST_PARTY_EXTRA"],
            L["CLICKCAST_ENABLE_PARTY"],
            L["CLICKCAST_ENABLE_PARTY_TIP"]
        )
        pClickCast._contentH = math.abs(yo) + PAD + 40
        if pClickCast._updateThumb then pClickCast._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_PARTY_FRAMES"],
            PAD, yo, 180, 28, AccentDangerBtnRGB())
        partyResetBtn:SetScript("OnClick", function()
            StaticPopupDialogs["RORBS_RESET_PARTY"] = {
                text = L["RESET_PARTY_FRAMES_CONFIRM"],
                button1 = OKAY,
                button2 = CANCEL,
                OnAccept = function()
                    rOrbsDB.partyEnabled       = nil
                    rOrbsDB.partyVertical       = nil
                    rOrbsDB.partyHideBlizzardCompactRaidManager = 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
                    rOrbsDB.partyBuffAurasMode = nil
                    rOrbsDB.partyDebuffAurasMode = nil
                    rOrbsDB.partyDebuffSizeMul = nil
                    rOrbsDB.partyAuraSpacing = nil
                    rOrbsDB.partyAuraRowGap = nil
                    rOrbsDB.partyAuraHostOffX = nil
                    rOrbsDB.partyAuraHostOffY = nil
                    rOrbsDB.partyAuraBuffOffX = nil
                    rOrbsDB.partyAuraBuffOffY = nil
                    rOrbsDB.partyAuraDebuffOffX = nil
                    rOrbsDB.partyAuraDebuffOffY = nil
                    rOrbsDB.partyBuffIconSize = nil
                    rOrbsDB.clickCastPartyEnabled = nil
                    rOrbsDB.clickCastPartyLeft    = nil
                    rOrbsDB.clickCastPartyMiddle  = nil
                    rOrbsDB.clickCastPartyRight   = nil
                    if ns.ApplyBlizzardCompactRaidFrameManagerSetting then
                        ns.ApplyBlizzardCompactRaidFrameManagerSetting()
                    end
                    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

        -- ── Build sub-nav and wire reset ─────────────────────────
        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 / ACKNOWLEDGEMENTS
    -- ============================================================
    -- (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 MV_ORB_TARGETS = {
            { 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"] },
        }

        local function MV_SendIdToOrb(tabKey, displayId, orbLabel)
            if not (Panel[tabKey] and Panel[tabKey].idBox) then return end
            Panel[tabKey].idBox:SetText(tostring(displayId))
            ns.GUI:SetTab(tabKey)
            ns.GUI:ChatMsg(string.format(L["MODELS_SENT_ID_ORB_MSG"], displayId, orbLabel or tabKey))
        end

        local MV_copyFrame = nil

        local function MV_CopyHotkeyLabel()
            if IsMacClient and IsMacClient() then
                return "Command+C"
            end
            return "Ctrl+C"
        end

        local function MV_ShowCopyFallback(idStr, displayId)
            if not MV_copyFrame then
                local f = CreateFrame("Frame", "rOrbsMVCopyFrame", UIParent)
                f:SetSize(260, 92)
                f:SetFrameStrata("FULLSCREEN_DIALOG")
                f:SetFrameLevel(300)
                f:SetClampedToScreen(true)
                f:EnableMouse(true)

                local bg = f:CreateTexture(nil, "BACKGROUND")
                bg:SetAllPoints()
                bg:SetColorTexture(C.bgDeep[1], C.bgDeep[2], C.bgDeep[3], 0.98)

                local function Edge(pt, rpt, w, h)
                    local e = f:CreateTexture(nil, "BORDER")
                    e:SetColorTexture(C.border[1], C.border[2], C.border[3], 0.9)
                    e:SetSize(w, h)
                    e:SetPoint(pt, f, rpt, 0, 0)
                end
                Edge("TOPLEFT", "TOPLEFT", 260, 1)
                Edge("BOTTOMLEFT", "BOTTOMLEFT", 260, 1)
                Edge("TOPLEFT", "TOPLEFT", 1, 92)
                Edge("TOPRIGHT", "TOPRIGHT", 1, 92)

                local hint = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
                hint:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -10)
                hint:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -10)
                hint:SetJustifyH("LEFT")
                f._hint = hint

                local eb = CreateFrame("EditBox", nil, f)
                eb:SetAutoFocus(true)
                eb:SetFontObject(GameFontHighlight)
                eb:SetHeight(22)
                eb:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -36)
                eb:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -36)
                eb:SetTextInsets(4, 4, 0, 0)
                eb:SetScript("OnEscapePressed", function() f:Hide() end)
                eb:SetScript("OnEnterPressed", function(self) self:ClearFocus() end)
                f._eb = eb

                local close = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
                close:SetSize(80, 22)
                close:SetPoint("BOTTOM", f, "BOTTOM", 0, 8)
                close:SetText(L["SETTINGS_CLOSE"])
                close:SetScript("OnClick", function() f:Hide() end)

                MV_copyFrame = f
            end

            MV_copyFrame._hint:SetText(string.format(L["MODELS_COPY_FALLBACK_HINT"], MV_CopyHotkeyLabel()))
            MV_copyFrame._eb:SetText(idStr)
            MV_copyFrame:ClearAllPoints()
            MV_copyFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
            MV_copyFrame:Show()
            MV_copyFrame._eb:SetFocus()
            MV_copyFrame._eb:HighlightText()
            ns.GUI:ChatMsg(string.format(L["MODELS_COPY_FALLBACK_MSG"], displayId, MV_CopyHotkeyLabel()))
        end

        local function MV_CopyDisplayId(displayId)
            MV_ShowCopyFallback(tostring(displayId), displayId)
        end

        local function MV_ShowSendIdMenu(displayId)
            if not displayId or displayId <= 0 then return end
            local menuItems = {
                { text = string.format(L["MODELS_SEND_MENU_TITLE"], displayId), disabled = true },
            }
            for _, orb in ipairs(MV_ORB_TARGETS) do
                if Panel[orb.key] and Panel[orb.key].idBox then
                    local tabKey, orbLabel = orb.key, orb.label
                    menuItems[#menuItems + 1] = {
                        text = orbLabel,
                        func = function()
                            MV_SendIdToOrb(tabKey, displayId, orbLabel)
                        end,
                    }
                end
            end
            menuItems[#menuItems + 1] = { text = " ", disabled = true }
            menuItems[#menuItems + 1] = {
                text = L["MODELS_SEND_MENU_COPY"],
                func = function()
                    MV_CopyDisplayId(displayId)
                end,
            }
            ShowCursorPopupMenu(menuItems, 220)
        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
                    local displayId = model._mvDisplayId
                    if displayId and displayId > 0 then
                        MV_ShowSendIdMenu(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(L["MODEL_INFO_TITLE"], 0, 1, 0.5)
            GameTooltip:AddLine(" ")
            GameTooltip:AddDoubleLine(L["MODEL_INFO_DISPLAY_ID"],  tostring(model._mvDisplayId or "?"),  1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine(L["MODEL_INFO_SET_POSITION"], string.format("(%.1f, %.1f, %.1f)", MV_RoundNumber(pz), MV_RoundNumber(px), MV_RoundNumber(py)), 1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine(L["MODEL_INFO_SET_ROTATION"], tostring(model._mvRotation or 0),     1,1,1, 1,1,1)
            GameTooltip:AddDoubleLine(L["MODEL_INFO_FILE_ID"],      tostring(model._mvFileId   or "?"),   1,1,1, 1,1,1)
            GameTooltip:AddLine(" ")
            GameTooltip:AddLine(L["MODEL_INFO_HINT_LEFTCLICK"],  0.8,0.8,0.8)
            GameTooltip:AddLine(L["MODEL_INFO_HINT_SHIFTDRAG"],  0.8,0.8,0.8)
            GameTooltip:AddLine(L["MODEL_INFO_HINT_RIGHTDRAG"],  0.8,0.8,0.8)
            GameTooltip:AddLine(L["MODEL_INFO_HINT_CTRLRIGHT"],  0.8,0.8,0.8)
            GameTooltip:AddLine(L["MODEL_INFO_HINT_WHEEL"],      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, AccentDangerBtnRGB())
        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, L["PROFILE_COPY_FROM_CHAR_SECTION"], PAD, yp) ; yp = yp - 44

        MakeInfoText(profPane,
            L["PROFILE_COPY_FROM_CHAR_INFO"],
            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, L["PROFILE_COPY_CHAR_LABEL"], 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 = L["PROFILE_CHAR_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 .. L["PROFILE_CHAR_THIS_SUFFIX"] .. "|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, L["PROFILE_CHAR_SELECT_PLACEHOLDER"])
            end
        end
        RefreshCharDropdown()
        yp = yp - 42

        -- Load from Character button
        local charLoadBtn = MakeButton(profPane, L["PROFILE_LOAD_FROM_CHAR"], PAD, yp, 180, 26, 0.10, 0.20, 0.10)
        charLoadBtn:SetScript("OnClick", function()
            if not selectedCharKey then ns.GUI:ChatMsg(L["PROFILE_NO_CHAR_SELECTED"]); return end
            if selectedCharKey == ns.db:GetCharKey() then
                ns.GUI:ChatMsg(L["PROFILE_CHAR_IS_CURRENT"]); return
            end
            StaticPopupDialogs["RORBS_CHAR_LOAD_CONFIRM"] = {
                text     = string.format(L["PROFILE_CHAR_LOAD_CONFIRM"], 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, L["PROFILE_REFRESH_CHAR_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(L["PROFILE_CHAR_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()
    -- Jedes Öffnen des GUIs startet mit kollabierten Sidebar-Gruppen ("Orbs",
    -- "Werkzeuge"). Gruppen expandieren nur noch, wenn der Nutzer sie
    -- explizit anklickt (oder auf einen Child-Tab via Suche/Slash springt).
    if rOrbsDB then
        rOrbsDB.guiSidebarCollapsed = rOrbsDB.guiSidebarCollapsed or {}
        if Panel and Panel.sidebarGroups then
            for gKey in pairs(Panel.sidebarGroups) do
                rOrbsDB.guiSidebarCollapsed[gKey] = true
            end
        end
    end
    -- Falls der letzte aktive Tab einem Gruppen-Container angehört, auf
    -- "general" zurücksetzen, damit die Gruppe nicht automatisch geöffnet wird.
    if Panel and Panel.tabGroupOf and Panel.tabGroupOf[activeTab] then
        activeTab = "general"
    end
    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, function()
            if ns.ApplyPartyAuraPreview then
                ns.ApplyPartyAuraPreview(false)
            end
            if Panel and Panel.party and Panel.party.cbPartyAuraPreview then
                local b = Panel.party.cbPartyAuraPreview
                b._auraPreviewOn = false
                if b.fs then b.fs:SetText(L["PARTY_AURAS_PREVIEW_BTN"]) end
                if b.innerTex then b.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1) end
            end
        end)
    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
            FadePane(sf, 0.14)
            local child = sf._sf and sf._sf:GetScrollChild()
            if child and child._updateThumb then
                local fn = child._updateThumb
                if C_Timer and C_Timer.After then
                    C_Timer.After(0, fn)
                else
                    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
    -- Auto-expand the parent group (if any) so the active child row is visible
    local ownerGroup = Panel.tabGroupOf and Panel.tabGroupOf[key]
    if ownerGroup and Panel.SetGroupExpanded then
        Panel.SetGroupExpanded(ownerGroup, true)
    elseif Panel.LayoutSidebar then
        Panel.LayoutSidebar()
    end
    for k, tab in pairs(Panel.tabs or {}) do
        if k == key then
            tab.bg:SetColorTexture(C.tabActive[1], C.tabActive[2], C.tabActive[3], 1)
            if tab.fs then tab.fs:SetTextColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
            if tab.underline then tab.underline:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 1) end
            if tab.icon then tab.icon:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
        else
            if tab._isGroupHeader then
                tab.bg:SetColorTexture(0, 0, 0, 0)
            else
                tab.bg:SetColorTexture(C.tabIdle[1], C.tabIdle[2], C.tabIdle[3], 1)
            end
            if tab.fs then tab.fs:SetTextColor(0.75, 0.73, 0.72, 1) end
            if tab.underline then tab.underline:SetColorTexture(C.accent[1], C.accent[2], C.accent[3], 0) end
            if tab.icon then tab.icon:SetVertexColor(0.55, 0.55, 0.58, 1) end
        end
    end
    -- Group-header tint: subtly highlight group that owns the active child
    if Panel.sidebarGroups then
        for gKey, hdr in pairs(Panel.sidebarGroups) do
            if gKey == ownerGroup then
                hdr.bg:SetColorTexture(C.accent[1] * 0.12, C.accent[2] * 0.12, C.accent[3] * 0.12, 1)
                if hdr.fs      then hdr.fs:SetTextColor(1, 1, 1, 1) end
                if hdr.icon    then hdr.icon:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
                if hdr.chevron then hdr.chevron:SetVertexColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1) end
            else
                hdr.bg:SetColorTexture(0, 0, 0, 0)
                if hdr.fs      then hdr.fs:SetTextColor(0.92, 0.90, 0.90, 1) end
                if hdr.icon    then hdr.icon:SetVertexColor(0.55, 0.55, 0.58, 1) end
                if hdr.chevron then hdr.chevron:SetVertexColor(0.55, 0.55, 0.60, 1) end
            end
        end
    end
end

-- ============================================================
--  APPLY FUNCTIONS (GetOrb / EnsureOrbCharCfg defined above CreateMainPanel)
-- ============================================================

GUI.GetOrb = GetOrb

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

    local function BlizzardHoldKeyLabel()
        local key = rOrbsDB and rOrbsDB.blizzardFramesHoldKey
        if not key or key == "" then
            return L["GEN_BLIZZARD_HOLDKEY_NONE"]
        end
        return GetBindingText(key, "KEY_") or key
    end

    if Panel.cbMinimap then Panel.cbMinimap:SetChecked(not (rOrbsDB.minimap and rOrbsDB.minimap.hide)) end
    if Panel.cbBlizzardFrames then Panel.cbBlizzardFrames:SetChecked(rOrbsDB.showBlizzardUnitFrames == true) end
    if Panel.lblBlizzardHoldKey then
        Panel.lblBlizzardHoldKey:SetText(L["GEN_BLIZZARD_HOLDKEY_CURRENT"]:format(BlizzardHoldKeyLabel()))
    end
    if Panel.btnSetBlizzardHoldKey then
        Panel.btnSetBlizzardHoldKey:SetEnabled(rOrbsDB.showBlizzardUnitFrames ~= true)
        Panel.btnSetBlizzardHoldKey:SetAlpha((rOrbsDB.showBlizzardUnitFrames ~= true) and 1 or 0.5)
    end
    if Panel.btnClearBlizzardHoldKey then
        local canClear = (rOrbsDB.showBlizzardUnitFrames ~= true) and (rOrbsDB.blizzardFramesHoldKey ~= nil)
        Panel.btnClearBlizzardHoldKey:SetEnabled(canClear)
        Panel.btnClearBlizzardHoldKey:SetAlpha(canClear and 1 or 0.5)
    end
    if Panel.cbOrbDragLocked then Panel.cbOrbDragLocked:SetChecked(rOrbsDB.orbDragLocked == true) end
    if Panel.cbSnapToGrid then Panel.cbSnapToGrid:SetChecked(rOrbsDB.snapToGrid == true) 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
            local ca = c.filling.colorAuto
            w.cbAuto:SetChecked(ca == true or ca == 1)
        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.clickCastEnable then
            if cfgKey == "HEALTH" then
                w.clickCastEnable:SetChecked(rOrbsDB.clickCastHealthEnabled == true)
            elseif cfgKey == "POWER" then
                w.clickCastEnable:SetChecked(rOrbsDB.clickCastPowerEnabled == true)
            elseif cfgKey == "TARGET_HEALTH" then
                w.clickCastEnable:SetChecked(rOrbsDB.clickCastTargetEnabled == true)
            end
            if w.clickCastLeftSync then w.clickCastLeftSync() end
            if w.clickCastMiddleSync then w.clickCastMiddleSync() end
            if w.clickCastRightSync then w.clickCastRightSync() end
        end
        if w.ddTextMode then
            local mode = rOrbsDB["textMode_"..cfgKey] or "BOTH"
            local modeLabels = { BOTH = L["TEXT_MODE_BOTH"], VALUE = L["TEXT_MODE_VALUE"], PERCENT = L["TEXT_MODE_PERCENT"] }
            UIDropDownMenu_SetText(w.ddTextMode, modeLabels[mode] or L["TEXT_MODE_BOTH"])
            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 rnkFont  = (cfgKey == "TARGET_HEALTH") and "targetNameFont"       or (cfgKey == "TOT_HEALTH") and "totNameFont"     or "petNameFont"
            local rnkFFall = (cfgKey == "TARGET_HEALTH") and "font_TARGET_HEALTH"   or (cfgKey == "TOT_HEALTH") and "font_TOT_HEALTH" or "font_PET_HEALTH"
            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.ddNameFont then
                local fname = rOrbsDB[rnkFont] or rOrbsDB[rnkFFall] or "Enchanted Land"
                UIDropDownMenu_SetText(w.ddNameFont, fname)
                UIDropDownMenu_SetSelectedValue(w.ddNameFont, fname)
            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.cbNameClassIcon and cfgKey == "TARGET_HEALTH" then
                w.cbNameClassIcon:SetChecked(rOrbsDB.targetNameClassIcon ~= false)
            end
            if w.ddNameClassIcon and cfgKey == "TARGET_HEALTH" then
                local mode = ns.ResolveTargetNameClassIconMode and ns.ResolveTargetNameClassIconMode() or "custom"
                local modeLabel = mode
                for _, entry in ipairs(ns.TargetNameClassIconModes or {}) do
                    if entry.value == mode then
                        modeLabel = L[entry.label] or entry.value
                        break
                    end
                end
                UIDropDownMenu_SetText(w.ddNameClassIcon, modeLabel)
                UIDropDownMenu_SetSelectedValue(w.ddNameClassIcon, mode)
            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.sSpk    then w.sSpk:SetValue((c.spark and c.spark.alpha) or 1.0) end
        if w.cbSparkMatch then w.cbSparkMatch:SetChecked(ns.IsOrbSparkMatchFill(cfgKey)) end
        if w.sparkColSwatch and c.spark and c.spark.color then
            w.sparkColSwatch:SetColor(c.spark.color.r, c.spark.color.g, c.spark.color.b)
        end
        if w.sparkColSwatch then
            local matched = ns.IsOrbSparkMatchFill(cfgKey)
            if w.sparkColSwatch.SetEnabled then w.sparkColSwatch:SetEnabled(not matched) end
            w.sparkColSwatch:SetAlpha(matched and 0.45 or 1)
        end
        if w.sparkColLabel then
            w.sparkColLabel:SetAlpha(ns.IsOrbSparkMatchFill(cfgKey) and 0.45 or 1)
        end
        if ns.ApplyOrbSparkFromConfig then
            ns.ApplyOrbSparkFromConfig(GetOrb(cfgKey), cfgKey)
        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 == "TARGET_HEALTH") and "targetRingRotate" 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 == "TARGET_HEALTH") and "targetRingSpeed" 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 = L["FIGURE_MODE_ANGEL"],
                diablo = L["FIGURE_MODE_DIABLO"],
                skull = L["FIGURE_MODE_SKULL"],
                demon = L["FIGURE_MODE_DEMON"],
                none = L["FIGURE_MODE_NONE"],
            }
            local _fm  = rOrbsDB.healthFigureMode or "angel"
            UIDropDownMenu_SetText(Panel.health.ddFigureMode, _fml[_fm] or L["FIGURE_MODE_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              = L["CLASS_POWER_STYLE_RUNES"],
                combobar           = L["CLASS_POWER_STYLE_COMBO"],
                model3d            = L["CLASS_POWER_STYLE_MODEL3D"],
                ["model3d-legion"] = L["CLASS_POWER_STYLE_MODEL3D_LEGION"],
                hidden             = L["CLASS_POWER_STYLE_HIDDEN"],
            }
            local _cur = rOrbsDB.comboPointStyle or "runes"
            UIDropDownMenu_SetText(Panel.health.ddStyle, _sl[_cur] or L["CLASS_POWER_STYLE_RUNES"])
            UIDropDownMenu_SetSelectedValue(Panel.health.ddStyle, _cur)
        end
        if Panel.health and Panel.health.cbClassPowerFillRTL then Panel.health.cbClassPowerFillRTL:SetChecked(rOrbsDB.runePointsFillRTL == true) 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 = { arc = L["RUNE_LAYOUT_ARC"], horizontal = L["RUNE_POS_HORIZONTAL"] }
            local cur = rOrbsDB.runePointsPosition or "arc"
            if cur ~= "horizontal" and cur ~= "arc" then cur = "arc" end
            UIDropDownMenu_SetText(Panel.health.ddRunePos, posLabels[cur] or L["RUNE_LAYOUT_ARC"])
            UIDropDownMenu_SetSelectedValue(Panel.health.ddRunePos, cur)
        end
        if Panel.health and Panel.health.ddArcSide and ns.GetRuneArcSide then
            local arcSideLabels = { top = L["RUNE_ARC_TOP"], bottom = L["RUNE_ARC_BOTTOM"] }
            local cur = ns.GetRuneArcSide()
            UIDropDownMenu_SetText(Panel.health.ddArcSide, arcSideLabels[cur] or L["RUNE_ARC_TOP"])
            UIDropDownMenu_SetSelectedValue(Panel.health.ddArcSide, cur)
        end
        if Panel.health and Panel.health.cbRuneArcOutside then
            Panel.health.cbRuneArcOutside:SetChecked(rOrbsDB.runeArcOutside ~= false)
        end
        if Panel.health then
            local gapV = rOrbsDB.runeHorizSpacing or 6
            if Panel.health.sRuneHorizSpacing then Panel.health.sRuneHorizSpacing:SetValue(gapV) end
            if Panel.health.sArcRuneSpacing   then Panel.health.sArcRuneSpacing:SetValue(gapV)   end
        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.ddTargetAuras then
            local v = rOrbsDB.targetAurasMode
            if v ~= "off" and v ~= "mouseover" and v ~= "always" then v = "mouseover" end
            local _al = {
                off = L["TARGET_AURA_MODE_OFF"],
                mouseover = L["TARGET_AURA_MODE_MOUSEOVER"],
                always = L["TARGET_AURA_MODE_ALWAYS"],
            }
            UIDropDownMenu_SetText(Panel.target.ddTargetAuras, _al[v])
            UIDropDownMenu_SetSelectedValue(Panel.target.ddTargetAuras, v)
            if Panel.target.lblTargetAurasHint then
                if v == "mouseover" then
                    Panel.target.lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_MOUSEOVER_INFO"])
                elseif v == "always" then
                    Panel.target.lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_ALWAYS_INFO"])
                else
                    Panel.target.lblTargetAurasHint:SetText(L["TARGET_AURA_MODE_OFF_INFO"])
                end
            end
        end
        if Panel.target and Panel.target.sTargetBuffOffY then
            Panel.target.sTargetBuffOffY:SetValue(rOrbsDB.targetBuffOffY or 8)
        end
        if Panel.target and Panel.target.sTargetBuffOffX then
            Panel.target.sTargetBuffOffX:SetValue(rOrbsDB.targetBuffOffX or 0)
        end
        if Panel.target and Panel.target.sTargetBuffSize then
            Panel.target.sTargetBuffSize:SetValue(rOrbsDB.targetBuffSize or 22)
        end
        if Panel.target and Panel.target.sTargetBuffScale then
            Panel.target.sTargetBuffScale:SetValue(rOrbsDB.targetBuffScale or 1.0)
        end
        if Panel.target and Panel.target.sTargetBuffSpacing then
            Panel.target.sTargetBuffSpacing:SetValue(rOrbsDB.targetBuffSpacing or 4)
        end
        if Panel.target and Panel.target.sTargetBuffCount then
            Panel.target.sTargetBuffCount:SetValue(rOrbsDB.targetBuffCountFontSize or 0)
        end
        if Panel.target and Panel.target.ddTargetBuffSide then
            local _bs = {
                bottom = L["TARGET_BUFF_SIDE_BOTTOM"],
                top    = L["TARGET_BUFF_SIDE_TOP"],
                left   = L["TARGET_BUFF_SIDE_LEFT"],
                right  = L["TARGET_BUFF_SIDE_RIGHT"],
            }
            local side = rOrbsDB.targetBuffSide
            if not side and rOrbsDB.targetBuffOffY and tonumber(rOrbsDB.targetBuffOffY) < 0 then
                side = "top"
            end
            side = side or "bottom"
            UIDropDownMenu_SetText(Panel.target.ddTargetBuffSide, _bs[side] or side)
            UIDropDownMenu_SetSelectedValue(Panel.target.ddTargetBuffSide, side)
        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.target and Panel.target.cbTargetQuestIcon then Panel.target.cbTargetQuestIcon:SetChecked(rOrbsDB.targetQuestIconShow ~= false) end
        if Panel.target and Panel.target.sTQuestSize  then Panel.target.sTQuestSize:SetValue(rOrbsDB.targetQuestIconSize or 18) end
        if Panel.target and Panel.target.sTQuestOffX  then Panel.target.sTQuestOffX:SetValue(rOrbsDB.targetQuestIconOffX or 0)  end
        if Panel.target and Panel.target.sTQuestOffY  then Panel.target.sTQuestOffY:SetValue(rOrbsDB.targetQuestIconOffY 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 = L["TMPL_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/unset = true (default on), false = explicitly off
        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
        if pw.cbHideBlizzardCompactRaidManager then
            pw.cbHideBlizzardCompactRaidManager:SetChecked(rOrbsDB.partyHideBlizzardCompactRaidManager == true)
        end
        if pw.clickCastEnable then pw.clickCastEnable:SetChecked(rOrbsDB.clickCastPartyEnabled == true) end
        if pw.clickCastLeftSync then
            pw.clickCastLeftSync()
            pw.clickCastMiddleSync()
            pw.clickCastRightSync()
        end
        local function pnAuraMode(m)
            if m == "off" or m == "on" or m == "mouseover" then return m end
            return "mouseover"
        end
        local prow = {
            off = L["PARTY_ROW_AURA_OFF"],
            on = L["PARTY_ROW_AURA_ON"],
            mouseover = L["PARTY_ROW_AURA_MOUSEOVER"],
        }
        if pw.ddPartyBuffAuras then
            local v = pnAuraMode(rOrbsDB and rOrbsDB.partyBuffAurasMode)
            UIDropDownMenu_SetText(pw.ddPartyBuffAuras, prow[v])
            UIDropDownMenu_SetSelectedValue(pw.ddPartyBuffAuras, v)
        end
        if pw.ddPartyDebuffAuras then
            local v = pnAuraMode(rOrbsDB and rOrbsDB.partyDebuffAurasMode)
            UIDropDownMenu_SetText(pw.ddPartyDebuffAuras, prow[v])
            UIDropDownMenu_SetSelectedValue(pw.ddPartyDebuffAuras, v)
        end
        if pw.sPartyBuffIconSize then
            pw.sPartyBuffIconSize:SetValue(math.floor((rOrbsDB and rOrbsDB.partyBuffIconSize) or (rOrbsDB and rOrbsDB.partyAuraSize) or 12))
        end
        if pw.sPartyDebuffSizeMul then pw.sPartyDebuffSizeMul:SetValue((rOrbsDB and rOrbsDB.partyDebuffSizeMul) or 1.25) end
        if pw.sPartyAuraSpacing then pw.sPartyAuraSpacing:SetValue((rOrbsDB and rOrbsDB.partyAuraSpacing) or 3) end
        if pw.sPartyAuraRowGap then pw.sPartyAuraRowGap:SetValue((rOrbsDB and rOrbsDB.partyAuraRowGap) or 4) end
        if pw.sPartyAuraHostOffX then pw.sPartyAuraHostOffX:SetValue((rOrbsDB and rOrbsDB.partyAuraHostOffX) or 0) end
        if pw.sPartyAuraHostOffY then pw.sPartyAuraHostOffY:SetValue((rOrbsDB and rOrbsDB.partyAuraHostOffY) or -2) end
        if pw.sPartyAuraBuffOffX then pw.sPartyAuraBuffOffX:SetValue((rOrbsDB and rOrbsDB.partyAuraBuffOffX) or 0) end
        if pw.sPartyAuraBuffOffY then pw.sPartyAuraBuffOffY:SetValue((rOrbsDB and rOrbsDB.partyAuraBuffOffY) or 0) end
        if pw.sPartyAuraDebuffOffX then pw.sPartyAuraDebuffOffX:SetValue((rOrbsDB and rOrbsDB.partyAuraDebuffOffX) or 0) end
        if pw.sPartyAuraDebuffOffY then pw.sPartyAuraDebuffOffY:SetValue((rOrbsDB and rOrbsDB.partyAuraDebuffOffY) or 0) end
        if pw.cbPartyAuraPreview then
            local b = pw.cbPartyAuraPreview
            local on = ns.partyAuraPreview == true
            b._auraPreviewOn = on
            if b.fs then
                b.fs:SetText(on and L["CUSTOM_TMPL_STOP"] or L["PARTY_AURAS_PREVIEW_BTN"])
            end
            if b.innerTex then
                if on then
                    SetTextureAccentDanger(b.innerTex)
                else
                    b.innerTex:SetColorTexture(0.08, 0.18, 0.08, 1)
                end
            end
        end
    end
end

-- ============================================================
--  APPLY FUNCTIONS (continued)
-- ============================================================

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
    ns.ApplyOrbFillVisualColor(orb, orbType, r, g, b)
end

function GUI:ApplySparkMatchFill(orbType, val)
    EnsureOrbCharCfg(orbType)
    val = (val == true)
    if ns.db and ns.db.char[orbType] and ns.db.char[orbType].spark then
        ns.db.char[orbType].spark.matchFillColor = val
    end
    if rOrbs_DB_CHAR and rOrbs_DB_CHAR[orbType] and rOrbs_DB_CHAR[orbType].spark then
        rOrbs_DB_CHAR[orbType].spark.matchFillColor = val
    end
    if rOrbsDB then
        rOrbsDB["sparkMatchFill_" .. orbType] = val
    end
    local orb = GetOrb(orbType)
    if not orb or not ns.ApplyOrbSparkFromConfig then return end
    if val then
        local fillCfg = ns.db and ns.db.char[orbType] and ns.db.char[orbType].filling
        if fillCfg and fillCfg.color then
            ns.ApplyOrbSparkFromConfig(orb, orbType, fillCfg.color.r, fillCfg.color.g, fillCfg.color.b)
        else
            ns.ApplyOrbSparkFromConfig(orb, orbType)
        end
        if orbType == "HEALTH" and ns.ForceHealthOrbColor then
            ns.ForceHealthOrbColor()
        elseif orbType == "POWER" and ns.ForcePowerOrbColor then
            ns.ForcePowerOrbColor()
        elseif orbType == "TARGET_HEALTH" then
            local tf = _G["rOrbsTargetHealthFrame"]
            if tf and tf.UpdateAllElements then tf:UpdateAllElements("ForceUpdate") end
        elseif orbType == "TOT_HEALTH" then
            local tf = _G["rOrbsToTHealthFrame"]
            if tf and tf.UpdateAllElements then tf:UpdateAllElements("ForceUpdate") end
        elseif orbType == "PET_HEALTH" then
            local tf = _G["rOrbsPetHealthFrame"]
            if tf and tf.UpdateAllElements then tf:UpdateAllElements("ForceUpdate") end
        end
    else
        ns.ApplyOrbSparkFromConfig(orb, orbType)
    end
end

function GUI:ApplySparkColor(orbType, r, g, b)
    EnsureOrbCharCfg(orbType)
    if ns.db and ns.db.char[orbType] and ns.db.char[orbType].spark then
        ns.db.char[orbType].spark.color = { r = r, g = g, b = b }
    end
    if rOrbs_DB_CHAR and rOrbs_DB_CHAR[orbType] and rOrbs_DB_CHAR[orbType].spark then
        rOrbs_DB_CHAR[orbType].spark.color = { r = r, g = g, b = b }
    end
    local orb = GetOrb(orbType)
    if orb then ns.ApplyOrbSparkFromConfig(orb, orbType) end
end

-- Sets oUF colorClass live for TARGET_HEALTH / TOT_HEALTH.
-- Same idea as ApplyColorAuto for the player orb:
-- colorClass=true  → oUF picks class color
-- colorClass=false → postUpdate applies configured orb color
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
    val = (val == true or val == 1)
    -- Persist setting (real boolean — GetChecked() often returns 1 not true)
    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
    -- Mirror in rOrbsDB: some WTF loads give unreliable filling.colorAuto
    if rOrbsDB and (orbType == "POWER" or orbType == "HEALTH") then
        rOrbsDB["fillingColorAuto_" .. orbType] = 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
    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 ns.SetDiabloOrbSparkAppearance(orb.spark, nil, nil, nil, 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
    if orbType == "TARGET_HEALTH" and ns.ApplyTargetBuffLayout then
        ns.ApplyTargetBuffLayout()
    end
    if orbType == "TARGET_HEALTH" and ns.UpdateTargetQuestIcon then
        ns.UpdateTargetQuestIcon()
    end
    if orbType == "TARGET_HEALTH" and ns.UpdateTargetNameClassIcon then
        ns.UpdateTargetNameClassIcon()
    end
end

local function GetNameFontDbKeys(orbType)
    if orbType == "TARGET_HEALTH" then return "targetNameFont", "font_TARGET_HEALTH"
    elseif orbType == "TOT_HEALTH" then return "totNameFont", "font_TOT_HEALTH"
    elseif orbType == "PET_HEALTH" then return "petNameFont", "font_PET_HEALTH"
    end
end

function GUI:ApplyNameFont(orbType, fontName)
    local orb = GetNameOrb(orbType)
    if not orb or not orb.nameFrame or not orb.nameFrame.text then return end
    local fontKey, fallbackKey = GetNameFontDbKeys(orbType)
    if not fontKey then return end
    fontName = fontName or (rOrbsDB and rOrbsDB[fontKey]) or (rOrbsDB and rOrbsDB[fallbackKey]) or "Enchanted Land"
    local fontPath = LSM:Fetch("font", fontName) or "Fonts\\FRIZQT__.TTF"
    local _, sz, flags = orb.nameFrame.text:GetFont()
    orb.nameFrame.text:SetFont(fontPath, sz or 22, flags or "OUTLINE")
end

function GUI:ApplyNameFontSize(orbType, size)
    local orb = GetNameOrb(orbType)
    if orb and orb.nameFrame and orb.nameFrame.text then
        local fontKey, fallbackKey = GetNameFontDbKeys(orbType)
        local fontName = (fontKey and rOrbsDB and rOrbsDB[fontKey])
            or (fallbackKey and rOrbsDB and rOrbsDB[fallbackKey])
            or "Enchanted Land"
        local fontPath = LSM:Fetch("font", fontName) or "Fonts\\FRIZQT__.TTF"
        local _, _, flags = orb.nameFrame.text:GetFont()
        orb.nameFrame.text:SetFont(fontPath, size or 22, flags or "OUTLINE")
    end
    if orbType == "TARGET_HEALTH" and ns.UpdateTargetNameClassIcon then
        ns.UpdateTargetNameClassIcon()
    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
    if orbType == "TARGET_HEALTH" and ns.ApplyTargetBuffLayout then
        ns.ApplyTargetBuffLayout()
    end
end

-- Legacy aliases
function GUI:ApplyTargetNameShow(val) GUI:ApplyNameShow("TARGET_HEALTH", val) end
function GUI:ApplyTargetNameFont(fontName) GUI:ApplyNameFont("TARGET_HEALTH", fontName) 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, orb)
    model:SetFrameLevel(orb:GetFrameLevel() + 1)
    ns.ApplyOrbModelAnchors(model, orb)
    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

    local function ModelUsesTemplate(m)
        if not m then return false end
        if m._tmpl or m._templateConfig then return true end
        local o = m.orbRef
        return o and (o._tmpl or o._templateConfig)
    end

    function model:Update()
        if ModelUsesTemplate(self) then return end
        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 ModelUsesTemplate(s) 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 ModelUsesTemplate(s) then return end
      C_Timer.After(0, function() if s and s:IsVisible() then s:Update() end end)
    end)
    model:Update()
    orb.model = model

    -- Model parented to orb (not clipFrame) so enlarged viewport is not clipped.
    -- 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 ModelUsesTemplate(orb.model) 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
            local unit = orb.unit
            if unit and UnitExists(unit) and not UnitIsDeadOrGhost(unit) then
                if not orb.model:IsShown() then orb.model:Show() end
            else
                if orb.model:IsShown() then orb.model:Hide() 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()
    ns.ApplyGuiClassTheme(C)

    local proxy = CreateFrame("Frame", "rOrbsInterfaceOptionsProxy")
    proxy.name = C_AddOns.GetAddOnMetadata(addon, "Title") or "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"])
    AttachGuiTitleVersion(proxy, t1, "rOrbs")

    -- 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(L["author_line"])

    -- 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"])

    -- Open settings (red accent 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.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
    openBtn:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 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.42, C.accent[2]*0.42, C.accent[3]*0.42, 1)
        s:SetBackdropBorderColor(C.accentHi[1], C.accentHi[2], C.accentHi[3], 1)
        openLbl:SetTextColor(1, 1, 1, 1)
    end)
    openBtn:SetScript("OnLeave", function(s)
        s:SetBackdropColor(C.accent[1]*0.24, C.accent[2]*0.24, C.accent[3]*0.24, 1)
        s:SetBackdropBorderColor(C.accent[1]*0.72, C.accent[2]*0.72, C.accent[3]*0.72, 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
        -- stuck keys; defer to next frame to release keyboard focus cleanly.
        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, proxy.name)
        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", font = "targetNameFont", fs = "targetNameFontSize", pos = "targetNamePos", offY = "targetNameOffY" },
            { orbType = "TOT_HEALTH",    show = "totNameShow",    font = "totNameFont",    fs = "totNameFontSize",    pos = "totNamePos",    offY = "totNameOffY"    },
            { orbType = "PET_HEALTH",    show = "petNameShow",    font = "petNameFont",    fs = "petNameFontSize",    pos = "petNamePos",    offY = "petNameOffY"    },
        }) do
            if rOrbsDB[pair.show] == false then
                GUI:ApplyNameShow(pair.orbType, false)
            end
            if rOrbsDB[pair.font] then
                GUI:ApplyNameFont(pair.orbType, rOrbsDB[pair.font])
            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

        -- Apply saved spark color/opacity (independent of fill color)
        for _, cfgKey in ipairs({"HEALTH","POWER","TARGET_HEALTH","TOT_HEALTH","PET_HEALTH"}) do
            local orb = GetOrb(cfgKey)
            if orb then ns.ApplyOrbSparkFromConfig(orb, cfgKey) end
        end

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