
  ---------------------------------------
  -- INIT
  ---------------------------------------

  --get the addon namespace
  local addon, ns = ...
  --get the config values
  local cfg = ns.cfg
  ns.dragFrameList = ns.dragFrameList or {}
  local dragFrameList = ns.dragFrameList
  ns.addonColor = ns.addonColor or "00AA99FF"
  ns.addonShortcut = ns.addonShortcut or "rmm"

  local LoadAddOn = (C_AddOns and C_AddOns.LoadAddOn) or LoadAddOn
  rMinimapDB = rMinimapDB or {}
  if (rMinimapDB.minimapSaveVersion or 0) < 4 then
    rMinimapDB.minimapPoint = nil
    rMinimapDB.clusterPoint = nil
    rMinimapDB.minimapSaveVersion = 4
  end
  rMinimapDB.iconRing = rMinimapDB.iconRing or {}
  rMinimapDB.ringIconScales = rMinimapDB.ringIconScales or {}
  if (rMinimapDB.iconRingDataVersion or 0) < 1 then
    rMinimapDB.iconRing = {}
    rMinimapDB.iconRingDataVersion = 1
  end
  --- Clock-Seed war falsch (Anker-Offsets statt Kreiskoordinaten); einmal neu ausrichten.
  if (rMinimapDB.iconRingDataVersion or 0) < 2 then
    if type(rMinimapDB.iconRing) == "table" then
      rMinimapDB.iconRing.Clock = nil
    end
    rMinimapDB.iconRingDataVersion = 2
  end
  --- Uhr nicht mehr auf Kreisbahn: gespeicherten Winkel entfernen.
  if (rMinimapDB.iconRingDataVersion or 0) < 3 then
    if type(rMinimapDB.iconRing) == "table" then
      rMinimapDB.iconRing.Clock = nil
    end
    rMinimapDB.iconRingDataVersion = 3
  end
  --- Wie rOrbs: SHIFT/ALT-Drag fuer Cluster, Ring-Icons & Co. sperren (Standard: an)
  if rMinimapDB.frameDragLocked == nil then
    rMinimapDB.frameDragLocked = true
  end

  --- Release-Defaults: nur bei fehlenden SavedVariables setzen.
  local function SetDefault(k, v)
    if rMinimapDB[k] == nil then
      rMinimapDB[k] = v
    end
  end

  SetDefault("frameDragLocked", true)
  SetDefault("showCoordinates", false)
  SetDefault("clockShow", true)
  SetDefault("clockFontSize", 14)
  SetDefault("clockPosX", 0)
  SetDefault("clockPosY", 0)
  SetDefault("clusterScale", 0.88)
  SetDefault("infoTextPlacement", "above")
  SetDefault("infoTextYAbove", cfg.info.posAboveY or 48)
  SetDefault("zoneFontScale", cfg.info.zoneFont.scale or 0.8)
  SetDefault("perfFontScale", cfg.info.perfFont.scale or 0.45)
  SetDefault("overlayTexture", "Interface\\AddOns\\rMinimap\\media\\map_overlay_new(withoutwings)")
  SetDefault("cogScale", 1.2)
  SetDefault("cogSpeed", 50)
  SetDefault("cogDirection", "left")
  SetDefault("portalShow", true)
  SetDefault("portalScale", 2.6)
  SetDefault("portalCamScale", 4.45)
  SetDefault("portalPosZ", -0.1)
  SetDefault("portalAlpha", 0.52)
  if type(rMinimapDB.infoZoneColor) ~= "table" then
    rMinimapDB.infoZoneColor = { unpack(cfg.info.zoneFont.color) }
  end
  if type(rMinimapDB.infoPerfColor) ~= "table" then
    rMinimapDB.infoPerfColor = { unpack(cfg.info.perfFont.color) }
  end
  SetDefault("zoneFontMediaKey", "Enchanted Land")
  SetDefault("clockMilitaryTime", "1")
  local ringDefaults = {
    Compartment = 336.1113811832097,
    Calendar = 300.9234687336729,
    Queue = 225,
    Mail = 0,
    Tracking = 318.0944831846724,
  }
  for k, v in pairs(ringDefaults) do
    if rMinimapDB.iconRing[k] == nil then
      rMinimapDB.iconRing[k] = v
    end
  end
  local ringScaleDefaults = {
    Compartment = 0.98577880859375,
    Tracking = 1.021333618164063,
    Calendar = 0.985776960849762,
  }
  for k, v in pairs(ringScaleDefaults) do
    if rMinimapDB.ringIconScales[k] == nil then
      rMinimapDB.ringIconScales[k] = v
    end
  end

  --- Groesserer Standard-Abstand Infotext „oben“ (alter Default 60 wirkte zu nah am Zierrand).
  if (rMinimapDB.infoTextAboveLayoutVersion or 0) < 1 then
    if rMinimapDB.infoTextPlacement == "above" and rMinimapDB.infoTextYAbove == 60 then
      rMinimapDB.infoTextYAbove = cfg.info.posAboveY or 110
    end
    rMinimapDB.infoTextAboveLayoutVersion = 1
  end

  --- Muss ausserhalb von ApplyDynamic leben: sonst zeigen gehookte Buttons auf eine veraltete ringDrag-Tabelle.
  local rmmRingDragState = {
    activeKey = nil,
    frame = nil,
    mode = nil,
    startMX = 0,
    startMY = 0,
    dragging = false,
    isScaling = false,
    startScale = 1,
    scaleStartY = 0,
    syncLDB = false,
    lastDeg = nil,
  }

  --- SavedVariables koennen iconRing spaeter nil/nicht-Table sein; Timer-Closures brauchen immer die echte Tabelle.
  local function GetRingDB()
    if type(rMinimapDB) ~= "table" then
      rMinimapDB = {}
    end
    local t = rMinimapDB.iconRing
    if type(t) ~= "table" then
      t = {}
      rMinimapDB.iconRing = t
    end
    return t
  end

  local function GetRingScaleDB()
    if type(rMinimapDB) ~= "table" then
      rMinimapDB = {}
    end
    local t = rMinimapDB.ringIconScales
    if type(t) ~= "table" then
      t = {}
      rMinimapDB.ringIconScales = t
    end
    return t
  end

  -- Tracking liegt fuer robustes Klicken auf UIParent; bei Cluster-Skalierung
  -- muessen die Offsets daher pro Frame in Parent-Koordinaten umgerechnet werden.
  local function RefreshTrackingScreenPoint()
    local btn = _G["rMM_TrackingButton"]
    if not btn or not Minimap then return end
    if btn:GetParent() == Minimap then return end
    local deg = GetRingDB()["Tracking"]
    if deg == nil then return end

    local ir = cfg.iconRing
    local inset = (ir and ir.inset) or 10
    local R = (Minimap:GetWidth() or 140) / 2 + inset
    local bias = (cfg.tracking and cfg.tracking.ringRadiusBias) or -8
    R = math.max(10, R + bias)

    local rad = math.rad(deg)
    local x, y = R * math.cos(rad), R * math.sin(rad)
    local mScale = (Minimap.GetEffectiveScale and Minimap:GetEffectiveScale()) or 1
    local uiScale = (UIParent and UIParent.GetEffectiveScale and UIParent:GetEffectiveScale()) or 1
    if uiScale == 0 then uiScale = 1 end
    local factor = mScale / uiScale
    local baseScale = GetRingScaleDB()["Tracking"]
    if type(baseScale) ~= "number" or baseScale <= 0 then
      baseScale = 1
    end

    btn:ClearAllPoints()
    -- Offset NICHT manuell skalieren: WoW konvertiert Koordinaten zwischen Parent-Skalen selbst.
    btn:SetPoint("CENTER", Minimap, "CENTER", x, y)
    btn:SetScale(baseScale * factor)
  end

  local function IsRmmTrackingMenuOpen()
    local bliz = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button
    if bliz then
      if bliz.IsMenuOpen then
        local ok, open = pcall(bliz.IsMenuOpen, bliz)
        if ok and open then return true end
      end
      for _, k in ipairs({ "dropdown", "DropDown", "Dropdown", "menu", "Menu" }) do
        local m = bliz[k]
        if m and m.IsShown and m:IsShown() then
          return true
        end
      end
    end
    if DropDownList1 and DropDownList1.IsShown and DropDownList1:IsShown() then
      local openMenu = UIDROPDOWNMENU_OPEN_MENU
      if openMenu == MiniMapTrackingDropDown or openMenu == ns.rmmTrackingDropDown then
        return true
      end
      if bliz and (openMenu == bliz or openMenu == bliz.Dropdown or openMenu == bliz.DropDown or openMenu == bliz.dropdown) then
        return true
      end
    end
    return false
  end

  local function CloseRmmTrackingMenu()
    local bliz = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button
    if bliz and bliz.CloseMenu then
      pcall(bliz.CloseMenu, bliz)
    end
    if Menu and Menu.CloseAllMenus then
      pcall(Menu.CloseAllMenus)
    end
    if CloseDropDownMenus then
      CloseDropDownMenus()
    end
    ns.__rmmTrackingMenuOpen = false
  end

  local function EnsureTrackingScaleHooks()
    if ns.__rmmTrackingScaleHooks then return end
    ns.__rmmTrackingScaleHooks = true
    if hooksecurefunc then
      if MinimapCluster and MinimapCluster.SetScale then
        hooksecurefunc(MinimapCluster, "SetScale", RefreshTrackingScreenPoint)
      end
      if Minimap and Minimap.SetScale then
        hooksecurefunc(Minimap, "SetScale", RefreshTrackingScreenPoint)
      end
      if Minimap and Minimap.SetSize then
        hooksecurefunc(Minimap, "SetSize", RefreshTrackingScreenPoint)
      end
      if DropDownList1 and not ns.__rmmTrackingDropdownHideHook then
        ns.__rmmTrackingDropdownHideHook = true
        DropDownList1:HookScript("OnHide", function()
          ns.__rmmTrackingMenuOpen = false
        end)
      end
    end
  end

  --- Tracking-Menue: Retail OpenMenu, sonst Blizzard-Dropdown, sonst C_Minimap-Liste.
  local function OpenRmmTrackingMenu(anchor)
    if ns.__rmmTrackingMenuOpen and not IsRmmTrackingMenuOpen() then
      ns.__rmmTrackingMenuOpen = false
    end
    if ns.__rmmTrackingMenuOpen or IsRmmTrackingMenuOpen() then
      CloseRmmTrackingMenu()
      ns.__rmmTrackingMenuOpen = false
      return
    end
    LoadAddOn("Blizzard_Minimap")
    local bliz = MinimapCluster and MinimapCluster.Tracking and MinimapCluster.Tracking.Button
    if bliz and bliz.OpenMenu then
      bliz:OpenMenu()
      ns.__rmmTrackingMenuOpen = true
      return
    end
    if MiniMapTrackingDropDown then
      ToggleDropDownMenu(1, nil, MiniMapTrackingDropDown, anchor or UIParent, 0, 0)
      ns.__rmmTrackingMenuOpen = true
      return
    end
    if C_Minimap and C_Minimap.GetNumTrackingTypes and C_Minimap.GetTrackingInfo and C_Minimap.SetTracking then
      if not ns.rmmTrackingDropDown then
        ns.rmmTrackingDropDown = CreateFrame("Frame", "rMM_TrackingDropDown", UIParent, "UIDropDownMenuTemplate")
        UIDropDownMenu_Initialize(ns.rmmTrackingDropDown, function(_, level)
          for i = 1, C_Minimap.GetNumTrackingTypes() do
            local name, tex, active = C_Minimap.GetTrackingInfo(i)
            if name then
              local idx = i
              local info = UIDropDownMenu_CreateInfo()
              info.text = name
              info.icon = tex
              info.checked = active
              info.isNotRadio = true
              info.keepShownOnClick = true
              info.func = function()
                local _, _, cur = C_Minimap.GetTrackingInfo(idx)
                C_Minimap.SetTracking(idx, not cur)
              end
              UIDropDownMenu_AddButton(info, level)
            end
          end
        end, "MENU")
      end
      ToggleDropDownMenu(1, nil, ns.rmmTrackingDropDown, anchor or UIParent, 0, 0)
      ns.__rmmTrackingMenuOpen = true
    end
  end

  --- Blizzard-Tracking sitzt oft als eigener Ast im Cluster *ueber* der Minimap-Flaeche.
  --- Der Container kann trotz alpha=0 noch Maus fangen; Klicks erreichen dann keine Kinder von Minimap.
  local function rmmDisableMouseRecursive(f)
    if not f then return end
    pcall(function()
      if f.EnableMouse then f:EnableMouse(false) end
      if f.SetMouseClickEnabled then f:SetMouseClickEnabled(false) end
    end)
    local ok, chTbl = pcall(function() return { f:GetChildren() } end)
    if not ok or type(chTbl) ~= "table" then return end
    for i = 1, #chTbl do
      rmmDisableMouseRecursive(chTbl[i])
    end
  end

  --- ALT + vertikal ziehen auf der Minimap-Karte: ganzen Cluster skalieren (wie Ring-Icons / rOrbs).
  local rmmClusterScaleDrag = {
    active = false,
    startY = 0,
    startScale = 1,
  }
  local rmmClusterScaleWatcher = _G["rMM_ClusterScaleWatcher"] or CreateFrame("Frame", "rMM_ClusterScaleWatcher", UIParent)
  rmmClusterScaleWatcher:Hide()
  rmmClusterScaleWatcher:SetScript("OnUpdate", function()
    local drag = rmmClusterScaleDrag
    if rMinimapDB and rMinimapDB.frameDragLocked == true then
      drag.active = false
      rmmClusterScaleWatcher:Hide()
      return
    end
    if not drag.active then
      rmmClusterScaleWatcher:Hide()
      return
    end
    local MC = MinimapCluster
    if not MC then
      drag.active = false
      rmmClusterScaleWatcher:Hide()
      return
    end
    if not IsMouseButtonDown("LeftButton") then
      rMinimapDB.clusterScale = MC:GetScale()
      drag.active = false
      rmmClusterScaleWatcher:Hide()
      return
    end
    local _, y = GetCursorPosition()
    local diffY = (y - drag.startY) / 100
    local newScale = math.max(0.5, math.min(3.0, drag.startScale + diffY))
    MC:SetScale(newScale)
    RefreshTrackingScreenPoint()
  end)

  --- CVar timeMgrUseMilitaryTime (24h) wird vom Client/TimeManager-Laden oft zurueckgesetzt; in DB halten und wieder anwenden.
  local function SaveClockMilitaryTimeCVarToDB()
    local v = GetCVar("timeMgrUseMilitaryTime")
    if v and v ~= "" then
      rMinimapDB.clockMilitaryTime = v
    end
  end

  local function RestoreClockMilitaryTimeCVarFromDB()
    local v = rMinimapDB.clockMilitaryTime
    if type(v) ~= "string" or v == "" then return end
    if GetCVar("timeMgrUseMilitaryTime") == v then return end
    pcall(function()
      SetCVar("timeMgrUseMilitaryTime", v)
    end)
  end

  --- Speichert die Cluster-Position (ganze Minimap auf dem Bildschirm).
  local function SaveClusterPosition()
    local cluster = _G["rMM_MinimapClusterDragFrame"]
    if not cluster then return end
    local a1, af, a2, x, y = cluster:GetPoint()
    if not a1 then return end
    local afName = (af and af.GetName and af:GetName()) and af:GetName() or "UIParent"
    if afName == "UIParent" or afName == "UIParent_ManualPositioning" then
      afName = "UIParent"
    end
    rMinimapDB.clusterPoint = {
      a1 = a1,
      af = afName,
      a2 = a2 or a1,
      x = x or 0,
      y = y or 0,
    }
  end

  local function RestoreClusterPosition(cluster)
    local p = rMinimapDB.clusterPoint
    if not (p and p.a1) or not cluster then return false end
    local af = (p.af == "UIParent") and UIParent or _G[p.af]
    if not af then return false end
    cluster:ClearAllPoints()
    cluster:SetPoint(p.a1, af, p.a2, p.x or 0, p.y or 0)
    return true
  end

  local function SafeHide(f)
    if f and f.Hide then
      f:Hide()
    end
  end

  local function ClearButtonTextureStates(btn)
    if not btn then return end
    local normal = btn.GetNormalTexture and btn:GetNormalTexture()
    if normal and normal.SetTexture then normal:SetTexture(nil) end
    local pushed = btn.GetPushedTexture and btn:GetPushedTexture()
    if pushed and pushed.SetTexture then pushed:SetTexture(nil) end
    local highlight = btn.GetHighlightTexture and btn:GetHighlightTexture()
    if highlight and highlight.SetTexture then highlight:SetTexture(nil) end
  end

  local function FindFirstTexture(frame)
    if not frame then return nil end
    local regions = { frame:GetRegions() }
    for i = 1, #regions do
      local r = regions[i]
      if r and r.GetObjectType and r:GetObjectType() == "Texture" then
        return r
      end
    end
    return nil
  end

  local function SkinSmallOverlayButton(btn, iconTex)
    if not btn then return end
    if not btn.rmmOverlayBg then
      local bg = btn:CreateTexture(nil, "BACKGROUND", nil, -6)
      bg:SetAllPoints(btn)
      btn.rmmOverlayBg = bg
    end
    btn.rmmOverlayBg:SetColorTexture(20/255, 15/255, 10/255, 1)
    btn.rmmOverlayBg:SetAlpha(1)
    btn.rmmOverlayBg:Show()
    if not btn.rmmOverlayBorder then
      local border = btn:CreateTexture(nil, "OVERLAY", nil, 6)
      border:SetPoint("TOPLEFT", btn, "TOPLEFT", -5, 5)
      border:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 5, -5)
      btn.rmmOverlayBorder = border
    end
    btn.rmmOverlayBorder:SetTexture("Interface\\AddOns\\rMinimap\\media\\button_overlay")
    btn.rmmOverlayBorder:SetAlpha(1)
    btn.rmmOverlayBorder:Show()
    if iconTex then
      iconTex:ClearAllPoints()
      iconTex:SetPoint("TOPLEFT", btn, "TOPLEFT", 1, -1)
      iconTex:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -1, 1)
      iconTex:SetTexCoord(0.1, 0.9, 0.1, 0.9)
    end
  end

  local function HideFrameTextures(frame)
    if not frame then return end
    local regions = { frame:GetRegions() }
    for i = 1, #regions do
      local r = regions[i]
      if r and r.GetObjectType and r:GetObjectType() == "Texture"
        and r ~= frame.rmmOverlayBg and r ~= frame.rmmOverlayBorder
        and r ~= frame.rmmBg and r ~= frame.rmmBorder then
        r:SetTexture(nil)
        r:SetAlpha(0)
        r:Hide()
      end
    end
  end

  local infoZoneText, infoPerfText, infoZoneClick, calendarDayText
  local infoTicker, calendarTicker

  local function GetMapPositionPercent()
    if not C_Map then return nil, nil end
    local mapID = C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
    if not mapID then return nil, nil end
    local pos = C_Map.GetPlayerMapPosition and C_Map.GetPlayerMapPosition(mapID, "player")
    if not pos then return nil, nil end
    local x, y = pos:GetXY()
    if not x or not y then return nil, nil end
    return math.floor(x * 100 + 0.5), math.floor(y * 100 + 0.5)
  end

  local function GetCurrentZoneName()
    if C_Map and C_Map.GetBestMapForUnit and C_Map.GetMapInfo then
      local mapID = C_Map.GetBestMapForUnit("player")
      if mapID then
        local info = C_Map.GetMapInfo(mapID)
        if info and info.name and info.name ~= "" then
          return info.name
        end
      end
    end
    return GetMinimapZoneText and GetMinimapZoneText() or ""
  end

  local function UpdateCalendarDayText()
    if not calendarDayText then return end
    local day = date("%d")
    if day then
      day = tostring(tonumber(day) or day)
      calendarDayText:SetText(day)
    end
  end

  local function InfoRgbToWoWHex(c)
    if not c then return "ffffff" end
    return string.format(
      "%02x%02x%02x",
      math.floor((c[1] or 1) * 255 + 0.5),
      math.floor((c[2] or 1) * 255 + 0.5),
      math.floor((c[3] or 1) * 255 + 0.5)
    )
  end

  --- Gespeicherte RGB (0–1) oder cfg-Defaults fuer Infotext.
  local function GetInfoZoneColorRGB()
    local c = rMinimapDB and rMinimapDB.infoZoneColor
    if type(c) == "table" and tonumber(c[1]) and tonumber(c[2]) and tonumber(c[3]) then
      return c[1], c[2], c[3]
    end
    return unpack(cfg.info.zoneFont.color)
  end

  local function GetInfoPerfColorRGB()
    local c = rMinimapDB and rMinimapDB.infoPerfColor
    if type(c) == "table" and tonumber(c[1]) and tonumber(c[2]) and tonumber(c[3]) then
      return c[1], c[2], c[3]
    end
    return unpack(cfg.info.perfFont.color)
  end

  local function GetZoneFontFamily()
    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
    local mediaKey = rMinimapDB and rMinimapDB.zoneFontMediaKey
    if type(mediaKey) == "string" and mediaKey ~= "" and LSM then
      local p = LSM:Fetch("font", mediaKey)
      if type(p) == "string" and p ~= "" then
        return p
      end
    end
    local db = rMinimapDB and rMinimapDB.zoneFontFamily
    if type(db) == "string" and db ~= "" then
      return db
    end
    return cfg.info.zoneFont.family
  end

  local function UpdateMinimapInfoText()
    if not infoZoneText or not infoPerfText then return end

    local zoneName = GetCurrentZoneName() or ""
    local x, y = GetMapPositionPercent()
    local zr, zg, zb = GetInfoZoneColorRGB()
    local pr, pg, pb = GetInfoPerfColorRGB()
    local wantCoords = (rMinimapDB.showCoordinates ~= false)
    if wantCoords and x and y then
      local zh = InfoRgbToWoWHex({ zr, zg, zb })
      local ch = InfoRgbToWoWHex({ pr, pg, pb })
      infoZoneText:SetText(string.format("|cff%s%s|r |cff%s[%d/%d]|r", zh, zoneName, ch, x, y))
    else
      infoZoneText:SetText(zoneName)
    end

    local _, _, home, world = GetNetStats()
    local ping = world or home or 0
    local fps = math.floor((GetFramerate and GetFramerate() or 0) + 0.5)
    infoPerfText:SetText(string.format("%dms %dfps", ping, fps))

    if infoZoneClick and infoZoneText then
      local w = infoZoneText:GetStringWidth() or 0
      local h = infoZoneText:GetStringHeight() or 12
      if w < 50 then w = 80 end
      infoZoneClick:SetSize(w + 28, math.max(h + 10, 22))
      infoZoneClick:ClearAllPoints()
      infoZoneClick:SetPoint("CENTER", infoZoneText, "CENTER", 0, 0)
    end
  end

  local function ApplyZoneFontFromDB()
    if not infoZoneText then return end
    local zf = cfg.info.zoneFont
    infoZoneText:SetFont(GetZoneFontFamily(), zf.size, zf.outline)
    UpdateMinimapInfoText()
  end
  ns.ApplyZoneFontFromDB = ApplyZoneFontFromDB

  local function ApplyInfoTextColorsFromDB()
    if not infoZoneText or not infoPerfText then return end
    local zr, zg, zb = GetInfoZoneColorRGB()
    local pr, pg, pb = GetInfoPerfColorRGB()
    infoZoneText:SetTextColor(zr, zg, zb)
    infoPerfText:SetTextColor(pr, pg, pb)
    UpdateMinimapInfoText()
  end
  ns.ApplyInfoTextColorsFromDB = ApplyInfoTextColorsFromDB

  --- Zonentext+Koords und Ping/FPS: unterhalb (Standard) oder oberhalb der Minimap.
  local function ApplyInfoTextLayoutFromDB()
    if not infoZoneText or not infoPerfText then return end
    local infoCfg = cfg.info
    local x = infoCfg.pos.x or 0
    local sp = infoCfg.spacing or -3
    local placement = rMinimapDB.infoTextPlacement
    if placement ~= "above" then
      placement = "below"
    end
    infoZoneText:ClearAllPoints()
    infoPerfText:ClearAllPoints()
    if placement == "above" then
      --- Oben: eigener Y-Wert (infoTextY ist fuer UNTEN oft stark negativ und wuerde mit TOP-Anker in die Karte ziehen).
      local y = rMinimapDB.infoTextYAbove
      if y == nil then
        y = infoCfg.posAboveY or 110
      end
      infoZoneText:SetPoint("BOTTOM", infoCfg.pos.af or Minimap, "TOP", x, y)
      infoPerfText:SetPoint("BOTTOM", infoZoneText, "TOP", 0, -sp)
    else
      local y = rMinimapDB.infoTextY
      if y == nil then
        y = infoCfg.pos.y
      end
      infoZoneText:SetPoint("TOP", infoCfg.pos.af or Minimap, "BOTTOM", x, y)
      infoPerfText:SetPoint("TOP", infoZoneText, "BOTTOM", 0, sp)
    end
    UpdateMinimapInfoText()
  end
  ns.ApplyInfoTextLayoutFromDB = ApplyInfoTextLayoutFromDB

  local function SetupMinimapInfoText()
    if infoZoneText and infoPerfText then return end

    local infoCfg = cfg.info
    infoZoneText = Minimap:CreateFontString(nil, "OVERLAY")
    infoZoneText:SetFont(GetZoneFontFamily(), infoCfg.zoneFont.size, infoCfg.zoneFont.outline)
    infoZoneText:SetScale(rMinimapDB.zoneFontScale or infoCfg.zoneFont.scale)
    do
      local zr, zg, zb = GetInfoZoneColorRGB()
      infoZoneText:SetTextColor(zr, zg, zb)
    end
    infoZoneText:SetJustifyH("CENTER")
    ns.infoZoneText = infoZoneText

    infoPerfText = Minimap:CreateFontString(nil, "OVERLAY")
    infoPerfText:SetFont(infoCfg.perfFont.family, infoCfg.perfFont.size, infoCfg.perfFont.outline)
    infoPerfText:SetScale(rMinimapDB.perfFontScale or infoCfg.perfFont.scale)
    do
      local pr, pg, pb = GetInfoPerfColorRGB()
      infoPerfText:SetTextColor(pr, pg, pb)
    end
    infoPerfText:SetJustifyH("CENTER")
    ns.infoPerfText = infoPerfText

    ApplyInfoTextLayoutFromDB()

    infoZoneClick = CreateFrame("Button", "rMM_InfoZoneClick", Minimap)
    infoZoneClick:SetFrameLevel((Minimap:GetFrameLevel() or 0) + 25)
    infoZoneClick:EnableMouse(true)
    infoZoneClick:RegisterForClicks("LeftButtonUp")
    infoZoneClick:SetScript("OnClick", function()
      pcall(function()
        if not WorldMapFrame then
          LoadAddOn("Blizzard_WorldMap")
        end
        if ToggleWorldMap then
          ToggleWorldMap()
        end
      end)
    end)
    infoZoneClick:SetScript("OnEnter", function(self)
      GameTooltip:SetOwner(self, "ANCHOR_TOP")
      local z = GetCurrentZoneName()
      if z and z ~= "" then
        GameTooltip:AddLine(z, 1, 1, 1)
      end
      GameTooltip:AddLine("Linksklick: Weltkarte", 0.55, 0.55, 0.55, true)
      GameTooltip:Show()
    end)
    infoZoneClick:SetScript("OnLeave", function()
      GameTooltip:Hide()
    end)

    UpdateMinimapInfoText()
    if infoTicker then infoTicker:Cancel() end
    infoTicker = C_Timer.NewTicker(0.5, UpdateMinimapInfoText)
  end

  --- Retail 11+: Zoom-Buttons sind Kinder von Minimap; Globals können fehlen.
  local function DoMinimapZoomIn()
    if Minimap_ZoomIn then
      Minimap_ZoomIn()
    elseif Minimap and Minimap.ZoomIn then
      pcall(function() Minimap.ZoomIn:Click() end)
    end
  end

  local function DoMinimapZoomOut()
    if Minimap_ZoomOut then
      Minimap_ZoomOut()
    elseif Minimap and Minimap.ZoomOut then
      pcall(function() Minimap.ZoomOut:Click() end)
    end
  end

  -- Blizzard_Minimap: ExpansionLandingPageMinimapButton kann SetTooltip vor tooltipText crashen
  local function PatchExpansionLandingTooltip(btn)
    if not btn or btn._rmmTooltipGuard then return end
    btn._rmmTooltipGuard = true
    local origSetTooltip = btn.SetTooltip
    if origSetTooltip then
      btn.SetTooltip = function(self, ...)
        if self.tooltipText ~= nil then
          pcall(origSetTooltip, self, ...)
        end
      end
    end
    local origOnEnter = btn:GetScript("OnEnter")
    if origOnEnter then
      btn:SetScript("OnEnter", function(self, ...)
        if self.tooltipText ~= nil then
          pcall(origOnEnter, self, ...)
        end
      end)
    end
  end

  --- Uhr: Sichtbarkeit, Position (Offsets zu cfg.clock.pos), Schrift aus rMinimapDB.
  local function ApplyMinimapClockFromDB()
    LoadAddOn("Blizzard_TimeManager")
    if not TimeManagerClockButton then return end
    local show = rMinimapDB.clockShow ~= false
    local ox = rMinimapDB.clockPosX or 0
    local oy = rMinimapDB.clockPosY or 0
    if TimeManagerClockTicker then
      local clockSize = rMinimapDB.clockFontSize or cfg.clock.font.size
      TimeManagerClockTicker:SetFont(cfg.clock.font.family, clockSize, cfg.clock.font.outline)
      TimeManagerClockTicker:SetTextColor(1, 1, 1)
    end
    TimeManagerClockButton:ClearAllPoints()
    TimeManagerClockButton:SetPoint(
      cfg.clock.pos.a1,
      cfg.clock.pos.af or Minimap,
      cfg.clock.pos.a2,
      (cfg.clock.pos.x or 0) + ox,
      (cfg.clock.pos.y or 0) + oy
    )
    if show then
      if TimeManagerClockTicker then TimeManagerClockTicker:Show() end
      TimeManagerClockButton:SetAlpha(1)
      TimeManagerClockButton:Show()
      TimeManagerClockButton:EnableMouse(true)
    else
      if TimeManagerClockTicker then TimeManagerClockTicker:Hide() end
      TimeManagerClockButton:Hide()
      TimeManagerClockButton:SetAlpha(0)
      TimeManagerClockButton:EnableMouse(false)
    end
  end
  ns.ApplyMinimapClockFromDB = ApplyMinimapClockFromDB

  --- Einmal: Cluster, Texturen, Mail, Queue. Wiederholt: Kalender, Uhr, Tracking, gespeicherte Minimap-Pos.
  local function ApplyDynamicMinimapWidgets()
    if not Minimap or not MinimapCluster then return end

    -- Clusterskalierung: nur einmal im Heavy-Block gesetzt -> Blizzard_Minimap/Layout setzt sie oft zurueck.
    -- Bei jedem ApplySkin wieder anwenden (nicht waehrend ALT+Zieh-Skalierung).
    if ns.__rmmHeavyDone and not rmmClusterScaleDrag.active then
      MinimapCluster:SetScale(rMinimapDB.clusterScale or cfg.mapcluster.scale)
    end

    if ExpansionLandingPageMinimapButton then
      PatchExpansionLandingTooltip(ExpansionLandingPageMinimapButton)
    end

    LoadAddOn("Blizzard_Minimap")
    LoadAddOn("Blizzard_TimeManager")
    ApplyMinimapClockFromDB()
    RestoreClockMilitaryTimeCVarFromDB()
    C_Timer.After(0, RestoreClockMilitaryTimeCVarFromDB)
    C_Timer.After(0.2, RestoreClockMilitaryTimeCVarFromDB)

    Minimap:SetParent(MinimapCluster)
    Minimap:ClearAllPoints()
    Minimap:SetPoint(cfg.map.pos.a1, cfg.map.pos.x, cfg.map.pos.y)
    EnsureTrackingScaleHooks()

    --- Eigenes Tracking-Icon (wie rMM_CalendarButton): Blizzard-Original ausblenden.
    local trkBtn = _G["rMM_TrackingButton"]
    if not trkBtn then
      trkBtn = CreateFrame("Button", "rMM_TrackingButton", Minimap)
    end
    --- Immer neu setzen: named Frames ueberleben /reload mit altem State.
    trkBtn.__rmmRingDrag = nil
    trkBtn.__rmmRingClickWrap = nil
    trkBtn:SetScript("OnClick", function(self, btn)
      if self.__rmmSuppressNextClick then
        self.__rmmSuppressNextClick = false
        return
      end
      OpenRmmTrackingMenu(self)
    end)
    trkBtn:SetScript("OnEnter", function(self)
      GameTooltip:SetOwner(self, "ANCHOR_LEFT")
      GameTooltip:SetText(MINIMAP_TRACKING_BUTTON or "Tracking", 1, 1, 1)
      GameTooltip:Show()
    end)
    trkBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    trkBtn:SetScript("OnMouseDown", nil)
    trkBtn:SetScript("OnMouseUp", nil)
    trkBtn:SetParent(Minimap)
    trkBtn:SetFrameLevel(Minimap:GetFrameLevel() + 10)
    trkBtn:SetSize(cfg.tracking.size, cfg.tracking.size)
    trkBtn:SetHitRectInsets(-5, -5, -5, -5)
    if not trkBtn.rmmBg then
      trkBtn.rmmBg = trkBtn:CreateTexture(nil, "BACKGROUND", nil, -6)
      trkBtn.rmmBg:SetAllPoints(trkBtn)
    end
    trkBtn.rmmBg:SetColorTexture(20/255, 15/255, 10/255, 1)
    trkBtn.rmmBg:SetAlpha(1)
    trkBtn.rmmBg:Show()
    if not trkBtn.rmmBorder then
      trkBtn.rmmBorder = trkBtn:CreateTexture(nil, "OVERLAY", nil, 7)
      trkBtn.rmmBorder:SetPoint("TOPLEFT", trkBtn, "TOPLEFT", -5, 5)
      trkBtn.rmmBorder:SetPoint("BOTTOMRIGHT", trkBtn, "BOTTOMRIGHT", 5, -5)
    end
    trkBtn.rmmBorder:SetTexture("Interface\\AddOns\\rMinimap\\media\\button_overlay")
    trkBtn.rmmBorder:SetAlpha(1)
    trkBtn.rmmBorder:Show()
    if not trkBtn.rmmIcon then
      trkBtn.rmmIcon = trkBtn:CreateTexture(nil, "ARTWORK", nil, 0)
      trkBtn.rmmIcon:SetPoint("TOPLEFT", trkBtn, "TOPLEFT", 3, -3)
      trkBtn.rmmIcon:SetPoint("BOTTOMRIGHT", trkBtn, "BOTTOMRIGHT", -3, 3)
      --- Blizzard Lupen-Symbol (Auktionshaus / Suche, Common UI)
      trkBtn.rmmIcon:SetTexture("Interface\\Common\\UI-SearchBox-Icon")
    end
    trkBtn.rmmIcon:Show()
    trkBtn:SetAlpha(1)
    trkBtn:Show()

    -- Blizzard's Original-Tracking unsichtbar und mauslos machen
    if MiniMapTracking then
      MiniMapTracking:EnableMouse(false)
      MiniMapTracking:SetAlpha(0)
    end
    if MiniMapTrackingButton then
      MiniMapTrackingButton:EnableMouse(false)
      MiniMapTrackingButton:SetAlpha(0)
    end
    if MiniMapTrackingBackground then
      MiniMapTrackingBackground:SetTexture(nil)
      MiniMapTrackingBackground:SetAlpha(0)
    end
    if MiniMapTrackingIconOverlay then
      MiniMapTrackingIconOverlay:SetTexture(nil)
    end
    -- Retail: MinimapCluster.Tracking neutralisieren (gesamter Zweig: sonst keine Klicks auf Minimap-Kinder)
    local retailTrk = MinimapCluster and MinimapCluster.Tracking
    if retailTrk then
      if not retailTrk.__rmmMouseNeuterHook then
        retailTrk.__rmmMouseNeuterHook = true
        retailTrk:HookScript("OnShow", function(self)
          rmmDisableMouseRecursive(self)
        end)
      end
      rmmDisableMouseRecursive(retailTrk)
      if retailTrk.Background then retailTrk.Background:Hide() end
      if retailTrk.Button then
        retailTrk.Button:SetAlpha(0)
        if retailTrk.Button.EnableMouse then
          retailTrk.Button:EnableMouse(false)
        end
        if retailTrk.Button.SetMouseClickEnabled then
          retailTrk.Button:SetMouseClickEnabled(false)
        end
      end
    end

    for _, blizCal in ipairs({
      GameTimeFrame,
      MinimapCluster and MinimapCluster.Calendar,
      MinimapCluster and MinimapCluster.Calendar and MinimapCluster.Calendar.Button,
    }) do
      if blizCal then
        blizCal:Hide()
        blizCal:SetAlpha(0)
        blizCal:EnableMouse(false)
        if not blizCal.__rmmCalHidden then
          blizCal.__rmmCalHidden = true
          blizCal:SetScript("OnShow", function(self) self:Hide() end)
        end
      end
    end

    local calBtn = _G["rMM_CalendarButton"]
    if not calBtn then
      calBtn = CreateFrame("Button", "rMM_CalendarButton", Minimap)
    end
    calBtn.__rmmRingDrag = nil
    calBtn.__rmmRingClickWrap = nil
    calBtn:SetScript("OnClick", function()
      LoadAddOn("Blizzard_Calendar")
      if ToggleCalendar then
        ToggleCalendar()
      elseif CalendarFrame then
        if CalendarFrame:IsShown() then CalendarFrame:Hide() else CalendarFrame:Show() end
      end
    end)
    calBtn:SetScript("OnEnter", function(self)
      GameTooltip:SetOwner(self, "ANCHOR_LEFT")
      GameTooltip:SetText(CALENDAR or "Calendar")
      GameTooltip:Show()
    end)
    calBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
    calBtn:SetScript("OnMouseDown", nil)
    calBtn:SetScript("OnMouseUp", nil)
    calBtn:SetParent(Minimap)
    calBtn:SetFrameLevel(Minimap:GetFrameLevel() + 10)
    calBtn:SetSize(cfg.calendar.size, cfg.calendar.size)
    -- Position: Kreisbahn (ApplyRingForKey "Calendar")
    calBtn:SetHitRectInsets(-5, -5, -5, -5)
    if not calBtn.rmmBg then
      calBtn.rmmBg = calBtn:CreateTexture(nil, "BACKGROUND", nil, -6)
      calBtn.rmmBg:SetAllPoints(calBtn)
    end
    calBtn.rmmBg:SetColorTexture(20/255, 15/255, 10/255, 1)
    calBtn.rmmBg:SetAlpha(1)
    calBtn.rmmBg:Show()
    if not calBtn.rmmBorder then
      calBtn.rmmBorder = calBtn:CreateTexture(nil, "OVERLAY", nil, 7)
      calBtn.rmmBorder:SetPoint("TOPLEFT", calBtn, "TOPLEFT", -5, 5)
      calBtn.rmmBorder:SetPoint("BOTTOMRIGHT", calBtn, "BOTTOMRIGHT", 5, -5)
    end
    calBtn.rmmBorder:SetTexture("Interface\\AddOns\\rMinimap\\media\\button_overlay")
    calBtn.rmmBorder:SetAlpha(1)
    calBtn.rmmBorder:Show()
    if not calendarDayText then
      calendarDayText = calBtn:CreateFontString(nil, "OVERLAY")
      calendarDayText:SetPoint("CENTER", 1, 1)
      calendarDayText:SetFont(cfg.calendar.font.family, cfg.calendar.font.size, cfg.calendar.font.outline)
      calendarDayText:SetTextColor(195/255, 186/255, 140/255)
    end
    calendarDayText:Show()
    UpdateCalendarDayText()
    if calendarTicker then calendarTicker:Cancel() end
    calendarTicker = C_Timer.NewTicker(60, UpdateCalendarDayText)
    calBtn:SetAlpha(1)
    calBtn:Show()

    ---------------------------------------
    -- Blizzard Addon Compartment + Kreis-Layout fuer Minimap-Buttons
    ---------------------------------------
    if Minimap then
      Minimap:EnableMouse(true)
      if Minimap.SetMouseClickEnabled then
        Minimap:SetMouseClickEnabled(true)
      end
    end

    --- Blizzard sortiert das Fach nach Registrierungs-Reihenfolge (nMainbar laeuft frueh -> oben). Alphabetisch nach jedem Register.
    do
      local acf = _G.AddonCompartmentFrame
      if acf and not ns.__rmmAcfSortHook and hooksecurefunc then
        ns.__rmmAcfSortHook = true
        local function acfSortKey(e)
          if not e or e.text == nil then return "" end
          return (tostring(e.text):gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")):lower()
        end
        hooksecurefunc(acf, "RegisterAddon", function()
          local list = acf.registeredAddons
          if type(list) == "table" and #list > 1 then
            table.sort(list, function(a, b)
              return acfSortKey(a) < acfSortKey(b)
            end)
          end
        end)
      end
    end

    local ts = cfg.tracking.size
    --- Ueber AddonCompartment (+11) und andere Minimap-Kinder; gleiche Strata wie Minimap noetig.
    local function GetMinimapIconFrameLevel()
      return (Minimap and Minimap.GetFrameLevel and Minimap:GetFrameLevel() or 0) + 50
    end

    local function GetRingRadius()
      if not Minimap then return 80 end
      local ir = cfg.iconRing
      if ir and ir.radius and ir.radius > 0 then return ir.radius end
      local inset = (ir and ir.inset) or 10
      return (Minimap:GetWidth() or 140) / 2 + inset
    end

    local function DegFromOffsets(x, y)
      if not x or not y then return nil end
      return math.deg(math.atan2(y, x)) % 360
    end

    local function CursorDegOnMinimap()
      if not Minimap then return 0 end
      local mx, my = Minimap:GetCenter()
      local px, py = GetCursorPosition()
      local scale = Minimap:GetEffectiveScale()
      px, py = px / scale, py / scale
      return math.deg(math.atan2(py - my, px - mx)) % 360
    end

    local function PlaceOnRing(frame, deg)
      if not frame or not Minimap or deg == nil then return end
      local rad = math.rad(deg)
      local R = GetRingRadius()
      local x, y = R * math.cos(rad), R * math.sin(rad)
      frame:ClearAllPoints()
      frame:SetPoint("CENTER", Minimap, "CENTER", x, y)
    end

    local function PlaceOnRingForKey(frame, key, deg)
      if not frame or not Minimap or deg == nil then return end
      local rad = math.rad(deg)
      local R = GetRingRadius()
      -- Tracking etwas weiter innen, damit es optisch naeher bei Mail/Queue sitzt.
      if key == "Tracking" then
        local bias = (cfg.tracking and cfg.tracking.ringRadiusBias) or -13
        R = math.max(10, R + bias)
      end
      local x, y = R * math.cos(rad), R * math.sin(rad)
      if key == "Tracking" and frame.GetParent and frame:GetParent() ~= Minimap then
        frame:ClearAllPoints()
        frame:SetPoint("CENTER", Minimap, "CENTER", x, y)
        return
      end
      frame:ClearAllPoints()
      frame:SetPoint("CENTER", Minimap, "CENTER", x, y)
    end

    local function SeedRingAnglesFromConfig()
      local db = GetRingDB()
      local function seed(key, px, py)
        if db[key] ~= nil then return end
        local d = DegFromOffsets(px, py)
        if d then db[key] = d end
      end
      seed("Tracking", cfg.tracking.pos.x, cfg.tracking.pos.y)
      seed("Calendar", cfg.calendar.pos.x, cfg.calendar.pos.y)
      seed("Mail", cfg.mail.pos.x, cfg.mail.pos.y)
      seed("Queue", cfg.queue.pos.x, cfg.queue.pos.y)
      if cfg.compartment and cfg.compartment.pos then
        seed("Compartment", cfg.compartment.pos.x, cfg.compartment.pos.y)
      end
    end

    local rmmRingDragWatcher = _G["rMM_IconRingDragWatcher"] or CreateFrame("Frame", "rMM_IconRingDragWatcher", UIParent)
    rmmRingDragWatcher:Hide()

    rmmRingDragWatcher:SetScript("OnUpdate", function()
      local ringDrag = rmmRingDragState
      if rMinimapDB and rMinimapDB.frameDragLocked == true then
        ringDrag.frame = nil
        ringDrag.activeKey = nil
        ringDrag.dragging = false
        ringDrag.isScaling = false
        ringDrag.mode = nil
        rmmRingDragWatcher:Hide()
        return
      end
      if not ringDrag.frame or not ringDrag.activeKey then
        rmmRingDragWatcher:Hide()
        return
      end
      --- Nicht frame:IsMouseButtonDown — z. B. AddonCompartmentFrame (DropdownButton) hat diese Methode nicht.
      if not IsMouseButtonDown("LeftButton") then
        if ringDrag.mode == "scale" and ringDrag.isScaling and ringDrag.frame then
          if ringDrag.activeKey == "Tracking"
             and ringDrag.frame.GetParent
             and ringDrag.frame:GetParent() ~= Minimap then
            -- Bei UIParent-Tracking ist frame:GetScale() bereits mit Minimap-Faktor multipliziert.
            -- DB enthaelt immer die Basis-Skalierung (ohne Parent-Faktor).
            if type(GetRingScaleDB()["Tracking"]) ~= "number" or GetRingScaleDB()["Tracking"] <= 0 then
              GetRingScaleDB()["Tracking"] = 1
            end
          else
            GetRingScaleDB()[ringDrag.activeKey] = ringDrag.frame:GetScale()
          end
        elseif ringDrag.mode == "ring" and ringDrag.dragging then
          GetRingDB()[ringDrag.activeKey] = ringDrag.lastDeg
        end
        ringDrag.frame = nil
        ringDrag.activeKey = nil
        ringDrag.dragging = false
        ringDrag.isScaling = false
        ringDrag.mode = nil
        rmmRingDragWatcher:Hide()
        return
      end
      --- ALT + Ziehen: Skalierung wie rOrbs (diffY/100, Clamp 0.5–3.0).
      if ringDrag.mode == "scale" and ringDrag.isScaling and ringDrag.frame then
        local _, y = GetCursorPosition()
        local diffY = (y - ringDrag.scaleStartY) / 100
        local newScale = math.max(0.5, math.min(3.0, ringDrag.startScale + diffY))
        if ringDrag.activeKey == "Tracking"
           and ringDrag.frame.GetParent
           and ringDrag.frame:GetParent() ~= Minimap then
          GetRingScaleDB()["Tracking"] = newScale
          RefreshTrackingScreenPoint()
        else
          ringDrag.frame:SetScale(newScale)
        end
        return
      end
      local mx, my = GetCursorPosition()
      local dist = math.sqrt((mx - ringDrag.startMX) ^ 2 + (my - ringDrag.startMY) ^ 2)
      if dist > 10 then ringDrag.dragging = true end
      if ringDrag.dragging then
        ringDrag.lastDeg = CursorDegOnMinimap()
        PlaceOnRingForKey(ringDrag.frame, ringDrag.activeKey, ringDrag.lastDeg)
        if ringDrag.syncLDB and ringDrag.frame.db then
          ringDrag.frame.db.minimapPos = ringDrag.lastDeg
        end
      end
    end)

    local function RegisterRingDrag(frame, key, syncLDB)
      if not frame or frame.__rmmRingDrag or not key then return end
      frame.__rmmRingDrag = true

      if frame.EnableMouse then frame:EnableMouse(true) end
      if frame.SetMouseClickEnabled then frame:SetMouseClickEnabled(true) end

      frame:HookScript("OnMouseDown", function(self, btn)
        if btn ~= "LeftButton" then return end
        if rMinimapDB and rMinimapDB.frameDragLocked == true then return end
        local ringDrag = rmmRingDragState
        if IsAltKeyDown() then
          ringDrag.mode = "scale"
          ringDrag.isScaling = true
          ringDrag.dragging = false
          ringDrag.frame = self
          ringDrag.activeKey = key
          if key == "Tracking" and self.GetParent and self:GetParent() ~= Minimap then
            local b = GetRingScaleDB()["Tracking"]
            ringDrag.startScale = (type(b) == "number" and b > 0) and b or 1
          else
            ringDrag.startScale = self:GetScale() or 1
          end
          _, ringDrag.scaleStartY = GetCursorPosition()
          ringDrag.syncLDB = false
        else
          ringDrag.mode = "ring"
          ringDrag.isScaling = false
          ringDrag.startMX, ringDrag.startMY = GetCursorPosition()
          ringDrag.dragging = false
          ringDrag.frame = self
          ringDrag.activeKey = key
          ringDrag.syncLDB = syncLDB and true or false
          ringDrag.lastDeg = GetRingDB()[key]
        end
        rmmRingDragWatcher:Show()
      end)

      frame:HookScript("OnMouseUp", function(self, btn)
        if btn ~= "LeftButton" then return end
        local ringDrag = rmmRingDragState
        if ringDrag.frame ~= self then return end
        if (ringDrag.mode == "scale" and ringDrag.isScaling)
            or (ringDrag.mode == "ring" and ringDrag.dragging) then
          self.__rmmSuppressNextClick = true
        end
      end)

      -- Protected/Secure Frames (z.B. MiniMapTrackingButton) duerfen NICHT per SetScript
      -- von Addon-Code angefasst werden: der Handler wuerde getaintet und der Button
      -- vollstaendig unklickbar. Solche Frames einfach ueberspringen – nach einem Drag
      -- oeffnet sich dann das Dropdown kurz, das ist akzeptabel.
      local isProtected = frame.IsProtected and frame:IsProtected()
      local oc = not isProtected and frame:GetScript("OnClick")
      if oc and not frame.__rmmRingClickWrap then
        frame.__rmmRingClickWrap = true
        frame:SetScript("OnClick", function(s, b, ...)
          if s.__rmmSuppressNextClick then
            s.__rmmSuppressNextClick = false
            return
          end
          return oc(s, b, ...)
        end)
      end
    end

    local function ApplyRingForKey(frame, key, syncLDB, noResize)
      if not frame or not key then return end
      SeedRingAnglesFromConfig()
      local deg = GetRingDB()[key]
      if deg == nil then return end
      frame:SetParent(Minimap)
      if Minimap.GetFrameStrata and frame.SetFrameStrata then
        frame:SetFrameStrata(Minimap:GetFrameStrata())
      end
      frame:SetFrameLevel(GetMinimapIconFrameLevel())
      if not noResize and frame.SetSize and ts then
        frame:SetSize(ts, ts)
      end
      PlaceOnRingForKey(frame, key, deg)
      local sc = GetRingScaleDB()[key]
      if type(sc) == "number" and sc > 0 then
        frame:SetScale(sc)
      else
        frame:SetScale(1)
      end
      RegisterRingDrag(frame, key, syncLDB)
    end

    local function SetupBlizzardCompartmentStyled()
      local comp = _G.AddonCompartmentFrame
      if not comp then return end
      if not comp.__rmmStyled then
        comp.__rmmStyled = true
        comp:SetParent(Minimap)
        --- NICHT MEDIUM erzwingen: sonst liegt das Fach ueber allen LOW-Kindern (Tracking, Mail, …) und frisst Klicks.
        local strata = (Minimap and Minimap.GetFrameStrata and Minimap:GetFrameStrata()) or "LOW"
        comp:SetFrameStrata(strata)
        comp:SetFrameLevel(Minimap:GetFrameLevel() + 11)
        comp:SetSize(ts, ts)
        local icon = comp.Icon or comp.icon or FindFirstTexture(comp)
        if icon and icon.SetTexCoord then
          icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
        end
        SkinSmallOverlayButton(comp, icon)
        comp:SetAlpha(1)
        comp:Show()
      end
      SeedRingAnglesFromConfig()
      ApplyRingForKey(comp, "Compartment", false)
    end

    --- Ohne LibDBIcon: eigene Minimap-Buttons (EasyFind, KwikTip, WIM, …) ausblenden — Zugang nur uebers Addon-Fach.
    local function StashNonLibMinimapButtons()
      local t = {
        "EasyFindMinimapButton",
        "KwikTipMinimapButton",
        "WIM3MinimapButton",
      }
      for i = 1, #t do
        local f = _G[t[i]]
        if f and f.Hide then
          if not f.__rmmStashCustom then
            f.__rmmStashCustom = true
            f:SetScript("OnDragStart", nil)
            f:SetScript("OnDragStop", nil)
          end
          f:Hide()
          if not f.__rmmHideOnShowHook then
            f.__rmmHideOnShowHook = true
            f:HookScript("OnShow", function(self)
              self:Hide()
            end)
          end
        end
      end
    end

    --- LibDataBroker-Minimapbuttons: nicht auf der Karte, nur ueber Blizzard Addon-Fach (LibDBIcon + AddonCompartmentFrame).
    local function SetupLibDBIconCompartmentOnly()
      StashNonLibMinimapButtons()

      local ldb = LibStub and LibStub("LibDBIcon-1.0", true)
      local acf = _G.AddonCompartmentFrame
      if not ldb or not acf then
        if not ns.__rmmStashOnlyDelays then
          ns.__rmmStashOnlyDelays = true
          C_Timer.After(0.75, StashNonLibMinimapButtons)
          C_Timer.After(2, StashNonLibMinimapButtons)
          C_Timer.After(5, StashNonLibMinimapButtons)
        end
        return
      end

      --- Basis: Farbcodes weg, trim, lower.
      local function compartmentEntryKey(raw)
        if raw == nil then return nil end
        local s = type(raw) == "string" and raw or tostring(raw)
        s = s:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("^%s+", ""):gsub("%s+$", "")
        if s == "" then return nil end
        return string.lower(s)
      end

      --- Dedupe / „schon im Fach?“: Leerzeichen & Unterstriche egal; typisches *_LDBIcon-Suffix (z. B. Delve).
      local function compartmentDedupeKey(raw)
        local s = compartmentEntryKey(raw)
        if not s then return nil end
        s = s:gsub("[%s_%-]", "")
        s = s:gsub("ldbicon$", "")
        if s == "" then return nil end
        return s
      end

      local function compartmentListHasBrokerLabel(name)
        local want = compartmentDedupeKey(name)
        if not want then return false end
        local list = acf.registeredAddons
        if type(list) ~= "table" then return false end
        for i = 1, #list do
          local e = list[i]
          local k = compartmentDedupeKey(e and (e.text or e.label))
          if k and k == want then
            return true
          end
        end
        return false
      end

      --- Kein zweites Register, wenn schon ein passender Listeneintrag existiert oder LibDBIcon schon ums Fach kuemmert.
      local function shouldAddLDBToCompartment(name)
        if not name then return false end
        if ldb.IsButtonInCompartment and ldb:IsButtonInCompartment(name) then
          return false
        end
        return not compartmentListHasBrokerLabel(name)
      end

      --- Doppelte Zeilen (gleicher logischer Name): bevorzugt LibDBIcon-Eintrag entfernen.
      local function dedupeAddonCompartmentList()
        local list = acf.registeredAddons
        if type(list) ~= "table" or #list < 2 then return end
        local again = true
        while again do
          again = false
          local seen = {}
          for i = 1, #list do
            local e = list[i]
            local k = compartmentDedupeKey(e and (e.text or e.label))
            if not k then
              -- leer
            elseif seen[k] then
              again = true
              local libName
              for j = 1, #list do
                local ej = list[j]
                local kj = compartmentDedupeKey(ej and (ej.text or ej.label))
                if kj == k and ldb.objects then
                  for n, btn in pairs(ldb.objects) do
                    if btn and btn.compartmentData == ej then
                      libName = n
                      break
                    end
                  end
                  if libName then break end
                end
              end
              if libName and ldb.RemoveButtonFromCompartment then
                pcall(function()
                  ldb:RemoveButtonFromCompartment(libName)
                end)
              else
                table.remove(list, i)
              end
              if acf.UpdateDisplay then
                pcall(acf.UpdateDisplay, acf)
              end
              break
            else
              seen[k] = true
            end
          end
        end
      end

      --- Erst nach anderen Addons: fehlende Broker ins Fach (sonst Race: wir zuerst, Addon legt zweites an).
      local function ensureBrokersInCompartment()
        StashNonLibMinimapButtons()
        if not ldb.GetButtonList then return end
        for _, bName in ipairs(ldb:GetButtonList()) do
          if shouldAddLDBToCompartment(bName) then
            pcall(function()
              ldb:AddButtonToCompartment(bName)
            end)
          end
        end
        dedupeAddonCompartmentList()
      end

      local function stashBrokerButton(button, name)
        if not button or not name or button.__rmmStashLDB then return end
        button.__rmmStashLDB = true
        if button.db then
          button.db.hide = true
          button.db.lock = true
        end
        if ldb.Refresh then
          pcall(ldb.Refresh, ldb, name, button.db)
        end
        button:SetScript("OnDragStart", nil)
        button:SetScript("OnDragStop", nil)
        button:Hide()
        if not button.__rmmHideOnShowHook then
          button.__rmmHideOnShowHook = true
          button:HookScript("OnShow", function(self)
            self:Hide()
          end)
        end
      end

      if not ns.__rmmLDBCompartmentCB then
        ns.__rmmLDBCompartmentCB = true
        if ldb.RegisterCallback then
          ldb:RegisterCallback("LibDBIcon_IconCreated", function(_, button, name)
            C_Timer.After(0, function() stashBrokerButton(button, name) end)
          end)
        end
      end

      if ldb.GetButtonList then
        for _, bName in ipairs(ldb:GetButtonList()) do
          local btn = ldb:GetMinimapButton(bName)
          if btn then stashBrokerButton(btn, bName) end
        end
      end

      ensureBrokersInCompartment()
      if not ns.__rmmLDBCompartmentDelaysScheduled then
        ns.__rmmLDBCompartmentDelaysScheduled = true
        C_Timer.After(0.75, ensureBrokersInCompartment)
        C_Timer.After(2, ensureBrokersInCompartment)
        C_Timer.After(5, ensureBrokersInCompartment)
        C_Timer.After(10, ensureBrokersInCompartment)
      end
    end

    SeedRingAnglesFromConfig()
    SetupBlizzardCompartmentStyled()

    if MiniMapMailFrame then
      ApplyRingForKey(MiniMapMailFrame, "Mail", false, true)
    end
    if QueueStatusMinimapButton then
      ApplyRingForKey(QueueStatusMinimapButton, "Queue", false, true)
    end
    local calRing = _G["rMM_CalendarButton"]
    if calRing then
      ApplyRingForKey(calRing, "Calendar", false, true)
    end
    local trkRing = _G["rMM_TrackingButton"]
    if trkRing then
      -- Funktionierenden Klick-Fix beibehalten: Tracking liegt ueber potenziellen Klick-Faengern.
      SeedRingAnglesFromConfig()
      local tDeg = GetRingDB()["Tracking"]
      if tDeg ~= nil then
        trkRing.__rmmRingPlaceFrame = nil
        trkRing:SetParent(UIParent)
        trkRing:SetFrameStrata("TOOLTIP")
        trkRing:SetFrameLevel(1)
        if trkRing.SetSize and ts then
          trkRing:SetSize(ts, ts)
        end
        PlaceOnRingForKey(trkRing, "Tracking", tDeg)
        RefreshTrackingScreenPoint()
        RegisterRingDrag(trkRing, "Tracking", false)
      end
    end

    SetupLibDBIconCompartmentOnly()
    C_Timer.After(1.5, SetupLibDBIconCompartmentOnly)
    C_Timer.After(4, SetupLibDBIconCompartmentOnly)

    SetupMinimapInfoText()
  end

  local function ApplySkin()
    if ExpansionLandingPageMinimapButton then
      PatchExpansionLandingTooltip(ExpansionLandingPageMinimapButton)
    end

    if ns.__rmmHeavyDone or not Minimap or not MinimapCluster then
      ApplyDynamicMinimapWidgets()
      return
    end

  ---------------------------------------
  -- HIDE STUFF
  ---------------------------------------

  -- Cluster: Maus anlassen, sonst erhalten Kinder (Tracking-Button, Minimap) in manchen Clients keine Klicks.
  MinimapCluster:EnableMouse(true)

  -- hide border (klassische Globals + moderne Kinder)
  SafeHide(MinimapBorder)
  SafeHide(MinimapBorderTop)
  SafeHide(Minimap.BorderTop)
  SafeHide(MinimapCluster.BorderTop)
  SafeHide(MinimapBackdrop)
  if MinimapCluster.MinimapContainer then
    local c = MinimapCluster.MinimapContainer
    if c.BorderTop then c.BorderTop:Hide() end
  end
  -- hide worldmap button
  SafeHide(MiniMapWorldMapButton)
  -- hide zoom
  SafeHide(MinimapZoomIn)
  SafeHide(MinimapZoomOut)
  SafeHide(Minimap.ZoomIn)
  SafeHide(Minimap.ZoomOut)
  -- hide voice frame (entfernt in neueren Clients)
  SafeHide(MiniMapVoiceChatFrame)
  -- hide zone text
  SafeHide(MinimapZoneTextButton)
  SafeHide(Minimap.ZoneTextButton)
  if MinimapCluster and MinimapCluster.ZoneTextButton then
    MinimapCluster.ZoneTextButton:Hide()
  end
  -- hide calendar button
  --GameTimeFrame:Hide()

  ---------------------------------------
  -- SCALE / POSITION
  ---------------------------------------

  --scale minimap (gespeicherte ALT-Skalierung schlaegt cfg.mapcluster.scale)
  MinimapCluster:SetScale(rMinimapDB.clusterScale or cfg.mapcluster.scale)

  --position the minimap cluster
  local clusterDrag = CreateFrame("Frame", "rMM_MinimapClusterDragFrame", UIParent)
  clusterDrag:SetSize(MinimapCluster:GetWidth(),MinimapCluster:GetHeight())
  local defPos = cfg.mapcluster.pos
  if not RestoreClusterPosition(clusterDrag) then
    clusterDrag:SetPoint(defPos.a1, defPos.af, defPos.a2, defPos.x, defPos.y)
  end
  if cfg.mapcluster.userplaced then
    rCreateDragFrame(clusterDrag, dragFrameList, -2 , true) --frame, dragFrameList, inset, clamp
    if not clusterDrag.__rmmAltOverlay then
      local altOv = CreateFrame("Frame", "rMM_MinimapClusterAltOverlay", clusterDrag)
      altOv:SetAllPoints(clusterDrag)
      altOv:SetFrameStrata("HIGH")
      altOv:SetFrameLevel(900)
      altOv:EnableMouse(false)
      local tt = altOv:CreateTexture(nil, "ARTWORK", nil, 0)
      tt:SetAllPoints(altOv)
      tt:SetColorTexture(1, 1, 1)
      tt:SetVertexColor(0.9, 0.15, 0.15, 0.28)
      tt:SetBlendMode("BLEND")
      altOv:Hide()
      clusterDrag.__rmmAltOverlay = altOv
      if clusterDrag.dragFrame then
        clusterDrag.dragFrame:SetFrameLevel(altOv:GetFrameLevel() + 10)
      end
    end
  end
  MinimapCluster:SetParent(clusterDrag)
  MinimapCluster:ClearAllPoints()
  MinimapCluster:SetPoint("TOP",0,0)
  hooksecurefunc(MinimapCluster, "SetPoint", function()
    MinimapCluster:SetAllPoints(clusterDrag)
  end)

  --vehicle seat indicator
  if VehicleSeatIndicator then
    local seatDrag = CreateFrame("Frame", "rMM_VehicleSeatIndicatorDragFrame", UIParent)
    seatDrag:SetSize(VehicleSeatIndicator:GetWidth(),VehicleSeatIndicator:GetHeight())
    --seatDrag:SetPoint(VehicleSeatIndicator:GetPoint())
    seatDrag:SetPoint("TOPRIGHT",rMM_MinimapClusterDragFrame,"BOTTOMRIGHT",0,-4)
    rCreateDragFrame(seatDrag, dragFrameList, -2 , true) --frame, dragFrameList, inset, clamp
    VehicleSeatIndicator:SetParent(seatDrag)
    VehicleSeatIndicator:SetAllPoints(seatDrag)
    hooksecurefunc(VehicleSeatIndicator, "SetPoint", function(...)
      VehicleSeatIndicator:SetAllPoints(seatDrag)
    end)
  end

  --durability frame
  if DurabilityFrame then
    local duraDrag = CreateFrame("Frame", "rMM_DurabilityDragFrame", UIParent)
    duraDrag:SetSize(DurabilityFrame:GetWidth(),DurabilityFrame:GetHeight())
    duraDrag:SetPoint("TOPRIGHT",_G["rMM_VehicleSeatIndicatorDragFrame"] or clusterDrag,"TOPLEFT",-4,0)
    rCreateDragFrame(duraDrag, dragFrameList, -2 , true) --frame, dragFrameList, inset, clamp
    DurabilityFrame:SetParent(duraDrag)
    DurabilityFrame:SetAllPoints(duraDrag)
    hooksecurefunc(DurabilityFrame, "SetPoint", function(...)
      DurabilityFrame:SetAllPoints(duraDrag)
    end)
  end

  --button positions

  ---------------------------------------
  -- TEXTURES
  ---------------------------------------

  --create rotating cogwheel texture
  local cogScale = rMinimapDB.cogScale or 1.22
  local cogSpeed = rMinimapDB.cogSpeed or 60
  local cogDir   = rMinimapDB.cogDirection or "left"
  local t = MinimapCluster:CreateTexture(nil,"ARTWORK",nil,-6)
  t:SetTexture("Interface\\AddOns\\rMinimap\\media\\zahnrad")
  t:SetPoint("CENTER", Minimap, 0, 0)
  t:SetWidth(Minimap:GetHeight() * cogScale)
  t:SetHeight(Minimap:GetHeight() * cogScale)
  t:SetVertexColor(48/255,44/255,35/255,0.95)
  t:SetBlendMode("BLEND")
  t.ag = t:CreateAnimationGroup()
  t.ag.a1 = t.ag:CreateAnimation("Rotation")
  t.ag.a1:SetDegrees(cogDir == "right" and 360 or -360)
  t.ag.a1:SetDuration(cogSpeed)
  t.ag:SetLooping("REPEAT")
  t.ag:Play()
  ns.cogTex = t

  --minimap gloss
  t = Minimap:CreateTexture(nil,"ARTWORK",nil,-3)
  t:SetTexture("Interface\\AddOns\\rMinimap\\media\\map_gloss")
  adjust = 1.06
  t:SetPoint("CENTER", 0, 0)
  t:SetWidth(Minimap:GetHeight()*adjust)
  t:SetHeight(Minimap:GetHeight()*adjust)
  t:SetVertexColor(0.9,0.95,1,1)
  t:SetBlendMode("ADD")

  --minimap border texture
  t = Minimap:CreateTexture(nil,"ARTWORK",nil,-2)
  local overlayPath = rMinimapDB.overlayTexture or "Interface\\AddOns\\rMinimap\\media\\map_overlay"
  t:SetTexture(overlayPath)
  local overlayDef = ns.OVERLAY_DEFS and ns.OVERLAY_DEFS[overlayPath]
  adjust = (overlayDef and overlayDef.adjust) or 1.3
  local overlayWMul = (overlayDef and overlayDef.wMul) or 2
  local overlayOfsX = (overlayDef and overlayDef.ofsX) or 0
  local overlayOfsY = (overlayDef and overlayDef.ofsY) or 0
  t:SetPoint("CENTER", overlayOfsX, overlayOfsY)
  t:SetWidth(Minimap:GetHeight() * overlayWMul * adjust)
  t:SetHeight(Minimap:GetHeight() * adjust)
  ns.overlayTex = t

  --create rotating ring texture
  --[[
  local t = Minimap:CreateTexture(nil,"ARTWORK",nil,1)
  t:SetTexture("Interface\\AddOns\\rMinimap\\media\\ring")
  local adjust = 1.10
  t:SetPoint("CENTER", 0, 0)
  t:SetWidth(Minimap:GetHeight()*adjust)
  t:SetHeight(Minimap:GetHeight()*adjust)
  t:SetVertexColor(115,96,63,0.2)
  t:SetBlendMode("BLEND")
  --create animation (rotation)
  t.ag = t:CreateAnimationGroup()
  t.ag.a1 = t.ag:CreateAnimation("Rotation")
  t.ag.a1:SetDegrees(360)
  t.ag.a1:SetDuration(60)
  t.ag:SetLooping("REPEAT")
  t.ag:Play()
  ]]--

  --minimap inner shadow
  t = Minimap:CreateTexture(nil,"ARTWORK",nil,2)
  t:SetTexture("Interface\\AddOns\\rMinimap\\media\\map_innershadow")
  adjust = 1.06
  t:SetPoint("CENTER", 0, 0)
  t:SetWidth(Minimap:GetHeight()*adjust)
  t:SetHeight(Minimap:GetHeight()*adjust)
  t:SetVertexColor(0,0,0,1)

  ---------------------------------------
  -- PORTAL 3D MODEL (Fire Effect)
  ---------------------------------------
  -- PlayerModel hinter der Minimap. Muss UNTER Minimap:GetFrameLevel() liegen.
  -- EnableMouse(false) ist zwingend: PlayerModel-Frames leiten sonst alle Klicks
  -- an WorldFrame weiter (WoW 12.x Verhalten) und blockieren Icons darüber.
  do
    -- Portal-Holder und PlayerModel werden in BACKGROUND-Strata platziert.
    -- PlayerModel-Frames ignorieren FrameLevel-Ordering in WoW und rendern immer
    -- oben wenn sie in derselben Strata wie Minimap (LOW) liegen. BACKGROUND
    -- garantiert, dass das Modell vor der gesamten LOW/MEDIUM UI gerendert wird.
    local portalHolder = CreateFrame("Frame", "rMM_PortalHolder", UIParent)
    portalHolder:SetFrameStrata("BACKGROUND")
    portalHolder:SetFrameLevel(1)
    portalHolder:EnableMouse(false)
    ns.portalHolder = portalHolder
    ns.portalModel  = nil

    local portalModel = CreateFrame("PlayerModel", "rMM_PortalModel", portalHolder)
    portalModel:SetAllPoints(portalHolder)
    portalModel:SetFrameStrata("BACKGROUND")
    portalModel:SetFrameLevel(1)
    portalModel:EnableMouse(false)

    local function rmmPortalModelLooksReady(pm)
      if not pm then return false end
      local ok, fid = pcall(function() return pm:GetModelFileID() end)
      if ok and type(fid) == "number" and fid > 0 then
        return true
      end
      return false
    end

    -- SetDisplayInfo laedt asynchron; in 12.x feuert OnModelLoaded hierzu oft nicht.
    -- Solange das Modell nicht bereit ist, greifen SetCamDistanceScale/SetPosition nicht dauerhaft.
    local function rmmStartPortalModelLayoutWatch(pm)
      if not pm then return end
      pm.__rmmPortalWatchFrames = 0
      pm.__rmmPortalApplyPass = nil
      pm:SetScript("OnUpdate", function(self)
        self.__rmmPortalWatchFrames = (self.__rmmPortalWatchFrames or 0) + 1
        if self.__rmmPortalWatchFrames > 300 then
          self:SetScript("OnUpdate", nil)
          if ns.ApplyPortalLayoutFromDB then
            ns.ApplyPortalLayoutFromDB()
            ns.ApplyPortalLayoutFromDB()
          end
          return
        end
        if self.__rmmPortalApplyPass == 2 then
          self.__rmmPortalApplyPass = nil
          self:SetScript("OnUpdate", nil)
          if ns.ApplyPortalLayoutFromDB then
            ns.ApplyPortalLayoutFromDB()
          end
          return
        end
        if self.__rmmPortalApplyPass == 1 then
          self.__rmmPortalApplyPass = 2
          if ns.ApplyPortalLayoutFromDB then
            ns.ApplyPortalLayoutFromDB()
          end
          return
        end
        if rmmPortalModelLooksReady(self) then
          self.__rmmPortalApplyPass = 1
          if ns.ApplyPortalLayoutFromDB then
            ns.ApplyPortalLayoutFromDB()
          end
        end
      end)
    end

    portalModel:SetScript("OnModelLoaded", function()
      if ns.ApplyPortalLayoutFromDB then
        ns.ApplyPortalLayoutFromDB()
      end
    end)
    portalModel:SetScript("OnShow", function()
      rmmStartPortalModelLayoutWatch(portalModel)
      C_Timer.After(0, function()
        if ns.ApplyPortalLayoutFromDB then
          ns.ApplyPortalLayoutFromDB()
        end
      end)
    end)

    portalModel:SetDisplayInfo(64562)   -- Red Portal (aus rOrbs T["red-portal"].displayInfoID)
    portalModel:SetPortraitZoom(0)
    ns.portalModel = portalModel
    rmmStartPortalModelLayoutWatch(portalModel)

    -- Sichtbarkeit: Standard an (wie clockShow), nur explizit false blendet aus.
    function ns.UpdatePortalModelVisibility()
      if not ns.portalHolder then return end
      local path = rMinimapDB.overlayTexture or ""
      local isNewBorder = path:find("map_overlay_new") ~= nil
      local show = (rMinimapDB.portalShow ~= false) and isNewBorder
      if show then
        ns.portalHolder:Show()
      else
        ns.portalHolder:Hide()
      end
    end

    --- Groesse vom Minimap-Radius (wechselt nach Layout/Reload); Werte immer aus rMinimapDB.
    function ns.ApplyPortalLayoutFromDB()
      local ph = ns.portalHolder
      local pm = ns.portalModel
      if not ph or not Minimap then return end
      local mapH = Minimap:GetHeight()
      if not mapH or mapH < 1 then mapH = 140 end
      local pScale = tonumber(rMinimapDB.portalScale) or 2.6
      ph:ClearAllPoints()
      ph:SetPoint("CENTER", Minimap, "CENTER", 0, 0)
      ph:SetSize(mapH * pScale, mapH * pScale)
      if pm then
        pm:SetCamDistanceScale(tonumber(rMinimapDB.portalCamScale) or 4.45)
        pm:SetPosition(0, 0, tonumber(rMinimapDB.portalPosZ) or -0.1)
        pm:SetAlpha(tonumber(rMinimapDB.portalAlpha) or 0.52)
        if pm.RefreshCamera then
          pcall(pm.RefreshCamera, pm)
        end
      end
      ns.UpdatePortalModelVisibility()
    end

    ns.ApplyPortalLayoutFromDB()
  end

  ---------------------------------------
  -- BUTTONS
  ---------------------------------------

  --MAIL ICON (Position: Kreisbahn in ApplyDynamicMinimapWidgets)
  if MiniMapMailFrame then
    MiniMapMailFrame:SetSize(cfg.mail.size,cfg.mail.size)
    --mail icon border
    if MiniMapMailBorder then
      MiniMapMailBorder:SetTexture("Interface\\AddOns\\rMinimap\\media\\button_overlay")
      MiniMapMailBorder:SetPoint("TOPLEFT", MiniMapMailFrame, "TOPLEFT", -5, 5)
      MiniMapMailBorder:SetPoint("BOTTOMRIGHT", MiniMapMailFrame, "BOTTOMRIGHT", 5, -5)
    end
    --mail icon
    if MiniMapMailIcon then
      MiniMapMailIcon:ClearAllPoints()
      MiniMapMailIcon:SetPoint("TOPLEFT", MiniMapMailFrame, "TOPLEFT", 1, -1)
      MiniMapMailIcon:SetPoint("BOTTOMRIGHT", MiniMapMailFrame, "BOTTOMRIGHT", -1, 1)
    end
  end

  --QUEUE STATUS ICON (Position: Kreisbahn in ApplyDynamicMinimapWidgets)
  if QueueStatusMinimapButton then
    QueueStatusMinimapButton:SetSize(cfg.queue.size,cfg.queue.size)
    --border
    if QueueStatusMinimapButtonBorder then
      QueueStatusMinimapButtonBorder:SetTexture("Interface\\AddOns\\rMinimap\\media\\button_overlay")
      QueueStatusMinimapButtonBorder:SetPoint("TOPLEFT", QueueStatusMinimapButton, "TOPLEFT", -5, 5)
      QueueStatusMinimapButtonBorder:SetPoint("BOTTOMRIGHT", QueueStatusMinimapButton, "BOTTOMRIGHT", 5, -5)
    end
  end


  ---------------------------------------
  -- SCRIPTS
  ---------------------------------------

  if not ns.__rmmHooksDone then
    ns.__rmmHooksDone = true
    Minimap:EnableMouseWheel(true)
    Minimap:SetScript("OnMouseWheel", function(self, direction)
      if(direction > 0) then
        DoMinimapZoomIn()
      else
        DoMinimapZoomOut()
      end
    end)

    local cluster = _G["rMM_MinimapClusterDragFrame"]
    if cluster then
      hooksecurefunc(cluster, "StopMovingOrSizing", function()
        SaveClusterPosition()
      end)
      local modifierWatcher = CreateFrame("Frame")
      modifierWatcher:SetScript("OnUpdate", function()
        if not cluster then return end
        local altOv = cluster.__rmmAltOverlay
        local df = cluster.dragFrame
        if rMinimapDB and rMinimapDB.frameDragLocked == true then
          if df then df:Hide() end
          if altOv then altOv:Hide() end
          return
        end
        if IsShiftKeyDown() and df then
          df:Show()
          if altOv then altOv:Hide() end
        elseif IsAltKeyDown() and ns.cfg.mapcluster.userplaced and altOv then
          if df then df:Hide() end
          altOv:Show()
        else
          if df then df:Hide() end
          if altOv then altOv:Hide() end
        end
      end)
    end
    if not Minimap.__rmmClusterMapAltScale then
      Minimap.__rmmClusterMapAltScale = true
      Minimap:HookScript("OnMouseDown", function(_, btn)
        if btn ~= "LeftButton" or not IsAltKeyDown() then return end
        if rMinimapDB and rMinimapDB.frameDragLocked == true then return end
        if not ns.cfg.mapcluster.userplaced then return end
        local MC = MinimapCluster
        if not MC then return end
        rmmClusterScaleDrag.active = true
        rmmClusterScaleDrag.startScale = MC:GetScale()
        _, rmmClusterScaleDrag.startY = GetCursorPosition()
        rmmClusterScaleWatcher:Show()
      end)
    end
    --- Nach Reload/Layout ist Minimap:GetHeight() oft erst spaeter final — Portal neu anlegen.
    if Minimap and hooksecurefunc and not ns.__rmmPortalMinimapHooks then
      ns.__rmmPortalMinimapHooks = true
      local function schedulePortalLayout()
        C_Timer.After(0, function()
          if ns.ApplyPortalLayoutFromDB then
            ns.ApplyPortalLayoutFromDB()
          end
        end)
      end
      hooksecurefunc(Minimap, "SetSize", schedulePortalLayout)
      Minimap:HookScript("OnSizeChanged", schedulePortalLayout)
      if MinimapCluster and MinimapCluster.SetScale then
        hooksecurefunc(MinimapCluster, "SetScale", schedulePortalLayout)
      end
    end
  end

  ns.__rmmHeavyDone = true
  ApplyDynamicMinimapWidgets()

  ---------------------------------------
  -- CALLS // HOOKS
  ---------------------------------------

  end -- ApplySkin

  local boot = CreateFrame("Frame")
  boot:RegisterEvent("PLAYER_ENTERING_WORLD")
  boot:RegisterEvent("ADDON_LOADED")
  boot:RegisterEvent("PLAYER_LOGOUT")
  boot:RegisterEvent("CVAR_UPDATE")
  boot:SetScript("OnEvent", function(_, event, name, value)
    if event == "PLAYER_LOGOUT" then
      SaveClusterPosition()
      SaveClockMilitaryTimeCVarToDB()
      if MinimapCluster and not rmmClusterScaleDrag.active then
        rMinimapDB.clusterScale = MinimapCluster:GetScale()
      end
      return
    end
    if event == "CVAR_UPDATE" then
      if name == "timeMgrUseMilitaryTime" and value and value ~= "" then
        rMinimapDB.clockMilitaryTime = value
      end
      return
    end
    if event == "ADDON_LOADED" then
      if name ~= "Blizzard_Minimap" and name ~= "Blizzard_TimeManager" and name ~= "rMinimap" then
        return
      end
    end
    if event == "PLAYER_ENTERING_WORLD" then
      RestoreClockMilitaryTimeCVarFromDB()
      C_Timer.After(0.5, RestoreClockMilitaryTimeCVarFromDB)
      C_Timer.After(2, RestoreClockMilitaryTimeCVarFromDB)
      C_Timer.After(0.5, function()
        if ns.ApplyPortalLayoutFromDB then ns.ApplyPortalLayoutFromDB() end
      end)
      C_Timer.After(2, function()
        if ns.ApplyPortalLayoutFromDB then ns.ApplyPortalLayoutFromDB() end
      end)
    end
    ApplySkin()
    C_Timer.After(0, ApplySkin)
    C_Timer.After(0.15, ApplySkin)
    C_Timer.After(0.5, ApplySkin)
    C_Timer.After(0.75, ApplySkin)
    C_Timer.After(2, ApplySkin)
  end)
  ApplySkin()
  C_Timer.After(0, ApplySkin)
  C_Timer.After(0.15, ApplySkin)
  C_Timer.After(0.5, ApplySkin)
  C_Timer.After(0.75, ApplySkin)
  C_Timer.After(2, ApplySkin)

  --- DEBUG: /rmmtrk – dumpt alles zum Tracking-Button ins Chat
  SLASH_RMMTRK1 = "/rmmtrk"
  SlashCmdList["RMMTRK"] = function()
    local p = print
    p("=== rMM Tracking Debug ===")
    local btn = _G["rMM_TrackingButton"]
    if not btn then
      p("rMM_TrackingButton: NICHT GEFUNDEN")
      return
    end
    p(("  Exists: yes | Shown: %s | Alpha: %.2f"):format(tostring(btn:IsShown()), btn:GetAlpha()))
    p(("  Size: %.1f x %.1f"):format(btn:GetWidth(), btn:GetHeight()))
    p(("  Strata: %s | Level: %d"):format(btn:GetFrameStrata(), btn:GetFrameLevel()))
    p(("  MouseEnabled: %s | MouseClickEnabled: %s"):format(
        tostring(btn:IsMouseEnabled()),
        tostring(btn.IsMouseClickEnabled and btn:IsMouseClickEnabled() or "N/A")))
    local pt = btn:GetNumPoints()
    for i = 1, pt do
      local a1, rel, a2, x, y = btn:GetPoint(i)
      p(("  Point %d: %s -> %s (%s) @ %.1f, %.1f"):format(i, a1, tostring(rel and rel:GetName()), a2, x, y))
    end
    local par = btn:GetParent()
    p(("  Parent: %s (Strata: %s, Level: %d, MouseEnabled: %s)"):format(
        par and par:GetName() or "?",
        par and par:GetFrameStrata() or "?",
        par and par:GetFrameLevel() or -1,
        par and tostring(par:IsMouseEnabled()) or "?"))
    p("--- Frames at button center ---")
    local cx, cy = btn:GetCenter()
    if cx and cy then
      local scale = btn:GetEffectiveScale()
      p(("  Button center (screen): %.1f, %.1f  scale: %.3f"):format(cx * scale, cy * scale, scale))
      local found = {}
      local function scan(f)
        if not f then return end
        local okVis, vis = pcall(function() return f:IsVisible() end)
        local okMouse, mouse = pcall(function() return f:IsMouseEnabled() end)
        if okVis and vis and okMouse and mouse then
          local okR, l, r, t2, b = pcall(function() return f:GetLeft(), f:GetRight(), f:GetTop(), f:GetBottom() end)
          if okR and l and r and t2 and b then
            local fs = f:GetEffectiveScale()
            local fl, fr, ft, fb = l * fs, r * fs, t2 * fs, b * fs
            local sx, sy = cx * scale, cy * scale
            if sx >= fl and sx <= fr and sy >= fb and sy <= ft then
              local nm = pcall(function() return f:GetName() end) and f:GetName() or tostring(f)
              table.insert(found, {
                name = nm or tostring(f),
                strata = f:GetFrameStrata(),
                level = f:GetFrameLevel(),
                mouse = true,
                click = pcall(function() return f:IsMouseClickEnabled() end) and f:IsMouseClickEnabled(),
                type = pcall(function() return f:GetObjectType() end) and f:GetObjectType() or "?",
              })
            end
          end
        end
        local ok, ch = pcall(function() return { f:GetChildren() } end)
        if ok and ch then
          for _, c in ipairs(ch) do scan(c) end
        end
      end
      scan(UIParent)
      table.sort(found, function(a, b)
        local sa = GetFrameStrataValue and GetFrameStrataValue(a.strata) or 0
        local sb = GetFrameStrataValue and GetFrameStrataValue(b.strata) or 0
        if sa ~= sb then return sa > sb end
        return a.level > b.level
      end)
      for i, f in ipairs(found) do
        p(("  %d. %s [%s L%d] mouse=%s click=%s (%s)"):format(
            i, f.name, f.strata, f.level,
            tostring(f.mouse), tostring(f.click), f.type))
        if i >= 20 then p("  ... (truncated)") break end
      end
    else
      p("  Could not get button center!")
    end
    p("--- MinimapCluster.Tracking ---")
    local rt = MinimapCluster and MinimapCluster.Tracking
    if rt then
      p(("  Visible: %s | Mouse: %s | Alpha: %.2f"):format(
          tostring(rt:IsVisible()), tostring(rt:IsMouseEnabled()), rt:GetAlpha()))
      if rt.Button then
        p(("  .Button visible: %s | mouse: %s | click: %s | alpha: %.2f | strata: %s L%d"):format(
            tostring(rt.Button:IsVisible()),
            tostring(rt.Button:IsMouseEnabled()),
            tostring(rt.Button.IsMouseClickEnabled and rt.Button:IsMouseClickEnabled()),
            rt.Button:GetAlpha(),
            rt.Button:GetFrameStrata(),
            rt.Button:GetFrameLevel()))
      end
      local ok, ch = pcall(function() return { rt:GetChildren() } end)
      if ok and ch then
        for _, c in ipairs(ch) do
          p(("  child: %s visible=%s mouse=%s strata=%s L%d"):format(
              c:GetName() or tostring(c),
              tostring(c:IsVisible()),
              tostring(c:IsMouseEnabled()),
              c:GetFrameStrata(),
              c:GetFrameLevel()))
        end
      end
    else
      p("  MinimapCluster.Tracking: nil")
    end
    p("=== END ===")
  end
