
  --get the addon namespace
  local addon, ns = ...

  --get oUF namespace (just in case needed)
  local oUF = ns.oUF or oUF
  local LSM = LibStub("LibSharedMedia-3.0")

  --get the config
  local cfg = ns.cfg

  --object container
  local func = CreateFrame("Frame")
  ns.func = func
  RothUI = {}

  ---------------------------------------------
  -- VARIABLES
  ---------------------------------------------

  local tinsert, tremove, floor, mod, format = tinsert, tremove, floor, mod, format

  ---------------------------------------------
  -- FUNCTIONS
  ---------------------------------------------

local function GetSpecializationRole_MoPClassic()
    local _, class = UnitClass("player")
    local spec = GetPrimaryTalentTree()

    if class == "DRUID" then
        if spec == 1 then return "TANK"    -- Guardian
        elseif spec == 2 then return "HEALER" -- Restoration
        elseif spec == 3 then return "DAMAGER" -- Balance
        elseif spec == 4 then return "DAMAGER" -- Feral (Cat)
        end
    elseif class == "PALADIN" then
        if spec == 1 then return "HEALER"   -- Holy
        elseif spec == 2 then return "TANK" -- Protection
        elseif spec == 3 then return "DAMAGER" -- Retribution
        end
    elseif class == "PRIEST" then
        if spec == 1 or spec == 2 then return "HEALER" -- Disc/Holy
        elseif spec == 3 then return "DAMAGER" -- Shadow
        end
    elseif class == "WARRIOR" then
        if spec == 1 then return "DAMAGER" -- Arms
        elseif spec == 2 then return "DAMAGER" -- Fury
        elseif spec == 3 then return "TANK" -- Protection
        end
    end
    -- Add mappings for the other classes here

    return "DAMAGER" -- fallback
end

local function IsHealer()
    if rOrbs:CheckClientVersion(WOW_PROJECT_CLASSIC) then return true
    elseif rOrbs:CheckClientVersion(WOW_PROJECT_MISTS_CLASSIC) or rOrbs:CheckClientVersion(WOW_PROJECT_BURNING_CRUSADE_CLASSIC) then
        return GetSpecializationRole_MoPClassic(GetActiveTalentGroup()) == "HEALER"
    else
        return GetSpecializationRole(GetSpecialization()) == 'HEALER'
    end
end
        
    function rOrbs:canDispelDebuff(type)
  	local class, _, _ = UnitClass("player")
  	if (type == 'Magic' and IsHealer()) then
  		return true
  	elseif (type == 'Curse') then
  		return class == 'Druid' or class == 'Mage' or class == 'Shaman'
  	elseif (type == 'Disease') then
  		return class == 'Monk' or class == 'Paladin' or class == 'Priest'
  	elseif (type == 'Poison') then
  		return class == 'Druid' or class == 'Monk' or class == 'Paladin'
  	else
  		return false
  	end
    end
    
  --number format func
  func.numFormat = function(v)
    -- launder secret/tainted numbers through string conversion
    v = tonumber(format("%.0f", v or 0)) or 0
    if v > 1E10 then
      return (floor(v/1E9)).."b"
    elseif v > 1E9 then
      return (floor((v/1E9)*10)/10).."b"
    elseif v > 1E7 then
      return (floor(v/1E6)).."m"
    elseif v > 1E6 then
      return (floor((v/1E6)*10)/10).."m"
    elseif v > 1E4 then
      return (floor(v/1E3)).."k"
    elseif v > 1E3 then
      return (floor((v/1E3)*10)/10).."k"
    else
      return v
    end
  end

  --round number
  func.round = function(val)
    return floor(val*1000)/1000
  end

  --format time func
  func.GetFormattedTime = function(time)
    local hr, m, s, text
    if time <= 0 then text = ""
    elseif(time < 3600 and time > 60) then
      hr = floor(time / 3600)
      m = floor(mod(time, 3600) / 60 + 1)
      text = format("%dm", m)
    elseif time < 60 then
      m = floor(time / 60)
      s = mod(time, 60)
      text = (m == 0 and format("%ds", s))
    else
      hr = floor(time / 3600 + 1)
      text = format("%dh", hr)
    end
    return text
  end

  --backdrop func
  func.createBackdrop = function(f)
    f:SetBackdrop(cfg.backdrop)
    f:SetBackdropColor(0,0,0,0.7)
    f:SetBackdropBorderColor(0,0,0,1)
  end

local function NormalizeSpellList(list)
    if not list then return nil end

    local t = {}
    for _, spellId in ipairs(list) do
        t[spellId] = true
    end
    return t
end

-- WoW 12.x: GetAuraDataBySlot().dispelName can be a secret string — no Lua == or table keys.
-- pcall(string.format, "%s", ...) yields a plain string (same pattern as rOrbs_CleanNumber in init.lua).
local function SanitizeDispelTypeName(s)
    if s == nil then return nil end
    if type(s) ~= "string" then return nil end
    local ok, plain = pcall(string.format, "%s", s)
    if not ok or not plain or plain == "" then return nil end
    return plain
end

  --create debuff func
  func.createDebuffs = function(self)
  if self.cfg.vertical then
	local f = CreateFrame("Frame", nil, self)
	f.disableCooldownText = self.cfg.auras.disableCooldownText
    f.size = self.cfg.auras.size
    if self.cfg.style == "targettarget" then
      f.num = 8
    else
      f.num = self.cfg.auras.number
    end
    f:SetHeight((f.size+5)*(f.num/4))
    f:SetWidth((f.size+5)*4)
	if self.cfg.style == "targettarget" then
		f:SetPoint("BOTTOM", self, "RIGHT", 0, 0)
	elseif self.cfg.style == "party" then
		f:SetPoint("BOTTOM", self.Health, "RIGHT", 22,-19)
	else
		f:SetPoint("TOP", self, "RIGHT", 50, -5)

	end
    f.initialAnchor = "TOPLEFT"
    f["growth-x"] = "RIGHT"
    f["growth-y"] = "DOWN"
    f.spacing = 5
    f.showDebuffType = self.cfg.auras.showDebuffType
    f.onlyShowPlayer = self.cfg.auras.onlyShowPlayerDebuffs
    f.onlyDispellable = self.cfg.auras.onlyDispellable
    f.whitelist = NormalizeSpellList(self.cfg.auras.whitelist or {})
    f.blacklist = NormalizeSpellList(self.cfg.auras.blacklist or {})
    self.Debuffs = f
  else
	local f = CreateFrame("Frame", nil, self)
	f.disableCooldownText = self.cfg.auras.disableCooldownText
    f.size = self.cfg.auras.size
    if self.cfg.style == "targettarget" then
      f.num = 8
    else
      f.num = self.cfg.auras.number
    end
    f:SetHeight((f.size+5)*(f.num/9))
    f:SetWidth((f.size+5)*4)
	if self.cfg.style == "targettarget" then
		f:SetPoint("BOTTOM", self, "RIGHT", -90, -40)
	else
		f:SetPoint("TOP", self, "RIGHT", -60, -67)
	end
    f.initialAnchor = "TOPLEFT"
    f["growth-x"] = "RIGHT"
    f["growth-y"] = "DOWN"
    f.spacing = 5
    f.showDebuffType = self.cfg.auras.showDebuffType
    f.onlyShowPlayer = self.cfg.auras.onlyShowPlayerDebuffs
    f.onlyDispellable = self.cfg.auras.onlyDispellable
    f.whitelist = NormalizeSpellList(self.cfg.auras.whitelist or {})
    f.blacklist = NormalizeSpellList(self.cfg.auras.blacklist or {})
    self.Debuffs = f
  end
  end

  --create buff func
  func.createBuffs = function(self)
  if self.cfg.auras.hideBuffs == true then return end
  if self.cfg.vertical == false then
    local f = CreateFrame("Frame", nil, self)
    f.size = self.cfg.auras.size
    if self.cfg.style == "targettarget" then
      f.num = 8
    else
      f.num = self.cfg.auras.number
    end
    f:SetHeight((f.size+5)*(f.num/9))
    f:SetWidth((f.size+5)*4)
    f:SetPoint("TOP", self, "RIGHT", -60, -27)
    f.initialAnchor = "TOPLEFT"
    f["growth-x"] = "RIGHT"
    f["growth-y"] = "DOWN"
    f.spacing = 5
    f.showBuffType = self.cfg.auras.showBuffType
    f.onlyShowPlayer = self.cfg.auras.onlyShowPlayerBuffs
    f.disableCooldownText = self.cfg.auras.disableCooldownText
    self.Buffs = f
  else
	local f = CreateFrame("Frame", nil, self)
    f.size = self.cfg.auras.size
    if self.cfg.style == "targettarget" then
      f.num = 8
    else
      f.num = self.cfg.auras.number
    end
    f:SetHeight((f.size+5)*(f.num/9))
    f:SetWidth((f.size+5)*9)
    f:SetPoint("TOP", self, "RIGHT", 117.5, 30)
    f.initialAnchor = "TOPLEFT"
    f["growth-x"] = "RIGHT"
    f["growth-y"] = "UP"
    f.spacing = 5
    f.showBuffType = self.cfg.auras.showBuffType
    f.onlyShowPlayer = self.cfg.auras.onlyShowPlayerBuffs
    f.disableCooldownText = self.cfg.auras.disableCooldownText
    self.Buffs = f
  end
  end
    
  -- DebuffTypeColor was removed from Retail in a recent patch.
  -- Mirror Blizzard's canonical values as a module-local constant.
  local kDebuffTypeColor = DebuffTypeColor or {
      Magic   = { r = 0.20, g = 0.60, b = 1.00 },
      Curse   = { r = 0.60, g = 0.00, b = 1.00 },
      Disease = { r = 0.60, g = 0.40, b = 0.00 },
      Poison  = { r = 0.00, g = 0.60, b = 0.00 },
      none    = { r = 0.80, g = 0.00, b = 0.00 },
  }

  func.checkColors = function(self, event, unit, full, auras)
      local hasDebuff = false
  
      local function applyDispelGlow(debuffType)
          debuffType = SanitizeDispelTypeName(debuffType)
          if not debuffType then return end
          if rOrbs:canDispelDebuff(debuffType) then
              local color = kDebuffTypeColor[debuffType] or kDebuffTypeColor.none
              if color ~= kDebuffTypeColor.none then
                  hasDebuff = true
                  self.Health.buffOverride = true
                  if self.Health.glow then
                      self.Health.glow:SetVertexColor(color.r, color.g, color.b)
                  end
              end
          end
      end
  
      -- AuraUtil.ForEachAura -> UnpackAuraData bricht mit "secret"-Feldern ab (Retail Restriction).
      -- C_UnitAuras.GetAuraDataBySlot liefert dispelName zuverlässig (vgl. oUF elements/auras.lua).
      if C_UnitAuras and C_UnitAuras.GetAuraSlots and C_UnitAuras.GetAuraDataBySlot then
          local slots = { C_UnitAuras.GetAuraSlots(unit, "HARMFUL") }
          for i = 2, #slots do
              local data = C_UnitAuras.GetAuraDataBySlot(unit, slots[i])
              if data and type(data.dispelName) == "string" then
                  applyDispelGlow(data.dispelName)
              end
          end
      else
          for i = 1, 40 do
              local name, _, _, debuffType = UnitAura(unit, i, "HARMFUL")
              if not name then break end
              applyDispelGlow(debuffType)
          end
      end

    -- reset if no debuff found
	if (not hasDebuff) then
		self.Health.buffOverride = false
		if (self.Health.glow) then
			self.Health.glow:SetVertexColor(0, 0, 0)
		end
	end
  end
  

	

  --Desaturated and Button CD
  func.postUpdateDebuff = function(element, unit, button, index, duration, expirationTime)
    if(UnitIsFriend("player", unit) or button.isPlayer) then
      button.icon:SetDesaturated(false)
      --button.cd:Show()
    else
      button.icon:SetDesaturated(true)
      --button.cd:Hide()
    end
    button.icon.duration = duration
    button.icon.timeLeft = expirationTime
    button.icon.first = true
  end

  --aura icon func
  func.createAuraIcon = function(icons, button)
    cfg = ns.cfg  -- refresh
    local font = LSM:Fetch("font", cfg.fonts.textFont)
    --button:SetSize(icons.size,icons.size)
    --button.cd:SetReverse()
    local size = icons.size or button:GetWidth()
	button.Cooldown:SetFrameStrata("MEDIUM")
    button.Cooldown:SetPoint("TOPLEFT", 1, -1)
    button.Cooldown:SetPoint("BOTTOMRIGHT", -1, 1)
    button.Icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
    --count helper frame, this push the count fontstring over the cooldown spiral
    button.countFrame = CreateFrame("Frame",nil,button)
    button.countFrame:SetAllPoints()
	button.countFrame:SetFrameStrata("MEDIUM")
    button.countFrame:SetFrameLevel(button.Cooldown:GetFrameLevel()+2)
    --button count
    button.Count:SetParent(button.countFrame)
    button.Count:ClearAllPoints()
    button.Count:SetPoint("TOPRIGHT", 4, 4)
    button.Count:SetTextColor(0.9,0.9,0.9)
    --fix fontsize to be based on button size
    button.Count:SetFont(font,size/1.8,"THINOUTLINE")
    button.Overlay:SetTexture(ns.mediapath.."gloss2")
    button.Overlay:SetTexCoord(0,1,0,1)
    button.Overlay:SetPoint("TOPLEFT", -1, 1)
    button.Overlay:SetPoint("BOTTOMRIGHT", 1, -1)
    button.Overlay:SetVertexColor(0.4,0.35,0.35,1)
    button.Overlay:Show()
    button.Overlay.Hide = function() end
    local back = button:CreateTexture(nil, "BACKGROUND", nil, 0)
    back:SetPoint("TOPLEFT",button.Icon,"TOPLEFT",-0.18*size,0.18*size)
    back:SetPoint("BOTTOMRIGHT",button.Icon,"BOTTOMRIGHT",0.18*size,-0.18*size)
    back:SetTexture(ns.mediapath.."simplesquare_glow")
    back:SetVertexColor(0, 0, 0, 1)
  end

    --create aura watch func
func.createAuraWatch = function(self)
    local auras = {}
    local spellIDs = {}
    
    if self.cfg.aurawatch.spellIds.ALL then
        for _, id in ipairs(self.cfg.aurawatch.spellIds.ALL) do
            table.insert(spellIDs, id)
        end
    end
    
    if self.cfg.aurawatch.spellIds[ns.player.class] then
        for _, id in ipairs(self.cfg.aurawatch.spellIds[ns.player.class]) do
            table.insert(spellIDs, id)
        end
    end
    
    auras.onlyShowPresent = self.cfg.aurawatch.onlyShowPresent
    auras.presentAlpha = self.cfg.aurawatch.alpha
        
    auras.PostCreateIcon = function(self, icon, sid)
        if icon.cd then
            icon.cd:SetPoint("TOPLEFT", 1, -1)
            icon.cd:SetPoint("BOTTOMRIGHT", -1, 1)
        end
        --count hack for lifebloom
        if sid == 33763 and icon.count then
            icon.count:SetFont("Interface\\AddOns\\rOrbs\\media\\visitor1.ttf",8,"THINOUTLINE, MONOCHROME")
            icon.count:ClearAllPoints()
            icon.count:SetPoint("CENTER", 3, 3)
            icon.count:SetParent(icon.cd)
        end
    end
        
    -- Set any other AuraWatch settings
    auras.icons = {}
    for i, sid in pairs(spellIDs) do
        local icon = CreateFrame("Frame", nil, self)
        icon:SetFrameStrata("TOOLTIP")
        icon.spellID = sid
                    
        local setup = {size = self.cfg.aurawatch.size, pos = self.cfg.aurawatch.pos.a1, x = self.cfg.aurawatch.pos.x * i, y = self.cfg.aurawatch.pos.y}
        -- set the dimensions and positions
        icon.size = setup.size
        icon:SetSize(setup.size,setup.size)
        --position icon
        icon:SetPoint("CENTER", self, self.cfg.aurawatch.pos.a1, self.cfg.aurawatch.pos.x + ((i-1) * self.cfg.aurawatch.size), self.cfg.aurawatch.pos.y)
        --make indicator
        --if dir[i].indicator then
        local tex = icon:CreateTexture(nil, "BACKGROUND", nil, 1)
            tex:SetAllPoints()
            tex:SetTexture(C_Spell.GetSpellTexture(sid))
            --tex:SetVertexColor(dir[i].color.r,dir[i].color.g,dir[i].color.b)
            icon.icon = tex
        
            auras.icons[sid] = icon
    -- Set any other AuraWatch icon settings
    end
    --call aurawatch
    self.AuraWatch = auras
end


  --update health func
  func.updateHealth = function(bar, unit, min, max)
    cfg = ns.cfg  -- refresh: cfg was captured empty at load time

    local color
    local dead
    local offline
    local isLowHP = false

    if unit and UnitIsTapDenied(unit) then
      color = {r = 0.65, g = 0.65, b = 0.65}
    elseif UnitIsDeadOrGhost(unit) then
      color = {r = 0.4, g = 0.4, b = 0.4}
      dead = true
      isLowHP = true
    elseif not UnitIsConnected(unit) then
      color = {r = 0.4, g = 0.4, b = 0.4}
      offline = true
    elseif not cfg.colorswitcher.classcolored then
      color = cfg.colorswitcher.bright
    elseif UnitIsPlayer(unit) then
      color = RAID_CLASS_COLORS[select(2, UnitClass(unit))]
    else
      color = FACTION_BAR_COLORS[UnitReaction(unit, "player")]
    end
    if not color then color = { r = 0.5, g = 0.5, b = 0.5 } end

    -- Secret-safe low-HP check:
    -- UnitHealthPercent returns a secret value -> comparison forbidden.
    -- Pipe it through SetFormattedText (C-side) and read back as plain string.
    if not dead and not offline then
      local scratch = ns._lowHPScratch
      if not scratch then
        local f = CreateFrame("Frame", nil, UIParent)
        f:SetSize(1,1) f:Hide()
        scratch = f:CreateFontString(nil, "ARTWORK")
        scratch:SetFont("Fonts\\FRIZQT__.TTF", 10)
        ns._lowHPScratch = scratch
      end
      local pct
      if type(UnitHealthPercent) == "function" then
        if CurveConstants and CurveConstants.ScaleTo100 then
          pct = UnitHealthPercent(unit, true, CurveConstants.ScaleTo100)
        else
          pct = UnitHealthPercent(unit, true, true)
        end
      end
      if type(pct) == "number" then
        scratch:SetFormattedText("%.1f", pct)
        local pctNum = tonumber(scratch:GetText())
        if pctNum and pctNum <= 25 then
          isLowHP = true
        end
      end
    end

    if offline then
      bar:SetStatusBarColor(0.4, 0.4, 0.4, 0.4)
      if bar.glow then bar.glow:SetVertexColor(0, 0, 0, 0) end
    else
      if cfg.colorswitcher.useBrightForeground then
        bar:SetStatusBarColor(color.r, color.g, color.b, color.a or 1)
        bar.bg:SetVertexColor(cfg.colorswitcher.dark.r, cfg.colorswitcher.dark.g, cfg.colorswitcher.dark.b, cfg.colorswitcher.dark.a)
      else
        bar:SetStatusBarColor(cfg.colorswitcher.dark.r, cfg.colorswitcher.dark.g, cfg.colorswitcher.dark.b, cfg.colorswitcher.dark.a)
        bar.bg:SetVertexColor(color.r, color.g, color.b, color.a or 1)
      end
    end

    -- Low HP glow
    if isLowHP then
      if bar.highlight then bar.highlight:SetAlpha(0) end
      if cfg.colorswitcher.useBrightForeground then
        if bar.glow then bar.glow:SetVertexColor(1, 0, 0, 0.6) end
        bar:SetStatusBarColor(1, 0, 0, 1)
        bar.bg:SetVertexColor(0.2, 0, 0, 0.9)
      else
        if bar.glow then bar.glow:SetVertexColor(1, 0, 0, 1) end
      end
    elseif not bar.buffOverride then
      if bar.glow then bar.glow:SetVertexColor(0, 0, 0, 0.7) end
      if bar.highlight then bar.highlight:SetAlpha(cfg.highlightMultiplier) end
    end
  end

  --update power func
  func.updatePower = function(bar, unit, min, max)
    cfg = ns.cfg  -- refresh: cfg was captured empty at load time
    local color = cfg.powercolors[select(2, UnitPowerType(unit))]
    if not color then
      --prevent powertype from bugging out on certain encounters.
      color = {r=1,g=0.5,b=0.25}
    end
      bar:SetStatusBarColor(color.r, color.g, color.b,1) 
      bar.bg:SetVertexColor(color.r, color.g, color.b,0.2)
  end

  --debuffglow
  func.createDebuffGlow = function(self)
    local t = self:CreateTexture(nil,"BACKGROUND",nil,-5)
    if self.cfg.style == "target" then
      t:SetTexture(ns.mediapath.."target_debuffglow")
    else
      t:SetTexture(ns.mediapath.."targettarget_debuffglow")
    end
	if self.cfg.style == "party" then
		if self.cfg.vertical == true then
			t:SetPoint("TOP",0,29)
			t:SetPoint("LEFT",0,0)
			t:SetPoint("RIGHT",0,0)
			t:SetPoint("BOTTOM",0,-10)
		else
			t:SetPoint("TOP",0,19)
			t:SetPoint("LEFT",0,0)
			t:SetPoint("RIGHT",0,0)
			t:SetPoint("BOTTOM",0,-15)
		end
	elseif self.cfg.style == "target" then
		t:SetPoint("TOP",0,25)
		t:SetPoint("LEFT",-60,0)
		t:SetPoint("RIGHT",60,0)
		t:SetPoint("BOTTOM",0,-15)
	else
		t:SetAllPoints()
	end
    t:SetBlendMode("BLEND")
    t:SetVertexColor(0, 1, 1, 0) -- set alpha to 0 to hide the texture
    self.DebuffHighlight = t
    self.DebuffHighlightAlpha = 1
    self.DebuffHighlightFilter = false
  end

  --check threat
  func.checkThreat = function(self,event,unit)
    if unit then
      if self.unit ~= unit then return end
      local threat = UnitThreatSituation(unit)
      if(threat and threat > 0) then
        local r, g, b = GetThreatStatusColor(threat)
        if self.Border then
          self.Border:SetVertexColor(r,g,b)
          self.PortraitBack:SetVertexColor(r,g,b,1)
        end
      else
        if self.Border then
          self.Border:SetVertexColor(0.6,0.5,0.5)
          self.PortraitBack:SetVertexColor(0.1,0.1,0.1,0.9)
        end
      end
    end
  end

  --create portrait func
  func.createPortrait = function(self)
  

    local back = CreateFrame("Frame",nil,self)
    back:SetSize(self.cfg.width,self.cfg.width)
	
	if self.cfg.style == "party" then
		if self.cfg.vertical == false then
			back:SetPoint("BOTTOM", self, "TOP", 0, -35)
		else
			back:SetPoint("BOTTOM", self, "LEFT", 10, -38)
		end
	else 
		back:SetPoint("BOTTOM", self, "TOP", 0, -35)
	end
    self.PortraitHolder = back

    local t = back:CreateTexture(nil,"BACKGROUND",nil,-8)
    t:SetAllPoints(back)
    t:SetTexture(ns.mediapath.."portrait_back")
    t:SetVertexColor(0.1,0.1,0.1,0.9)
    self.PortraitBack = t

	if self.cfg.style == "party" then
		if self.cfg.portrait.use3D then
			self.Portrait = CreateFrame("PlayerModel", nil, back)
			self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",22,-20)
			self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-19,21)

			local borderholder = CreateFrame("Frame", nil, self.Portrait)
				borderholder:SetAllPoints(back)
				self.BorderHolder = borderholder

			local border = borderholder:CreateTexture(nil,"BACKGROUND",nil,-6)
				border:SetAllPoints(borderholder)
				border:SetTexture(ns.mediapath.."portrait_border")
				border:SetVertexColor(0.6,0.5,0.5)
				--border:SetVertexColor(1,0,0,1) --threat test
				self.Border = border

			local gloss = borderholder:CreateTexture(nil,"BACKGROUND",nil,-5)
				gloss:SetAllPoints(borderholder)
				gloss:SetTexture(ns.mediapath.."portrait_gloss")
				gloss:SetVertexColor(0.9,0.95,1,0.6)

		else
			self.Portrait = back:CreateTexture(nil,"BACKGROUND",nil,-7)
			self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",21,-21)
			self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-21,21)
			self.Portrait:SetTexCoord(0.15,0.85,0.15,0.85)

			local border = back:CreateTexture(nil,"BACKGROUND",nil,-6)
				border:SetAllPoints(back)
				border:SetTexture(ns.mediapath.."portrait_border")
				border:SetVertexColor(0.6,0.5,0.5)
				self.Border = border

			local gloss = back:CreateTexture(nil,"BACKGROUND",nil,-5)
				gloss:SetAllPoints(back)
				gloss:SetTexture(ns.mediapath.."portrait_gloss")
				gloss:SetVertexColor(0.9,0.95,1,0.6)

		end
	else
		if self.cfg.portrait.use3D then
			self.Portrait = CreateFrame("PlayerModel", nil, back)
			self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",27,-27)
			self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-27,27)

			local borderholder = CreateFrame("Frame", nil, self.Portrait)
				borderholder:SetAllPoints(back)
				self.BorderHolder = borderholder

			local border = borderholder:CreateTexture(nil,"BACKGROUND",nil,-6)
				border:SetAllPoints(borderholder)
				border:SetTexture(ns.mediapath.."portrait_border")
				border:SetVertexColor(0.6,0.5,0.5)
				--border:SetVertexColor(1,0,0,1) --threat test
				self.Border = border

			local gloss = borderholder:CreateTexture(nil,"BACKGROUND",nil,-5)
				gloss:SetAllPoints(borderholder)
				gloss:SetTexture(ns.mediapath.."portrait_gloss")
				gloss:SetVertexColor(0.9,0.95,1,0.6)

		else
			self.Portrait = back:CreateTexture(nil,"BACKGROUND",nil,-7)
			self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",27,-27)
			self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-27,27)
			self.Portrait:SetTexCoord(0.15,0.85,0.15,0.85)

			local border = back:CreateTexture(nil,"BACKGROUND",nil,-6)
				border:SetAllPoints(back)
				border:SetTexture(ns.mediapath.."portrait_border")
				border:SetVertexColor(0.6,0.5,0.5)
				self.Border = border

			local gloss = back:CreateTexture(nil,"BACKGROUND",nil,-5)
				gloss:SetAllPoints(back)
				gloss:SetTexture(ns.mediapath.."portrait_gloss")
				gloss:SetVertexColor(0.9,0.95,1,0.6)

		end	
	end
		
	if self.cfg.vertical == true then
    self.Name:SetPoint("CENTER", 0, 0)
	else
	self.Name:SetPoint("BOTTOM", self, "TOP", 0, self.cfg.width-53)
	end

  end

  --create standalone portrait func
  func.createStandAlonePortrait = function(self)

    local fname
    if self.cfg.style == "player" then
      fname = "rOrbsPlayerPortrait"
    elseif self.cfg.style == "target" then
      fname = "rOrbsTargetPortrait"
    end

    local pcfg = self.cfg.portrait

    local back = CreateFrame("Frame",fname,self)
    back:SetSize(pcfg.size,pcfg.size)
    back:SetPoint(pcfg.pos.a1,pcfg.pos.af,pcfg.pos.a2,pcfg.pos.x,pcfg.pos.y)

    func.applyDragFunctionality(back)

    local t = back:CreateTexture(nil,"BACKGROUND",nil,-8)
    t:SetAllPoints(back)
    t:SetTexture(ns.mediapath.."portrait_back")
    t:SetVertexColor(0.1,0.1,0.1,0.9)

    if pcfg.use3D then
      self.Portrait = CreateFrame("PlayerModel", nil, back)
      self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",pcfg.size*27/128,-pcfg.size*27/128)
      self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-pcfg.size*27/128,pcfg.size*27/128)

      local borderholder = CreateFrame("Frame", nil, self.Portrait)
      borderholder:SetAllPoints(back)

      local border = borderholder:CreateTexture(nil,"BACKGROUND",nil,-6)
      border:SetAllPoints(borderholder)
      border:SetTexture(ns.mediapath.."portrait_border")
      border:SetVertexColor(0.6,0.5,0.5)

      local gloss = borderholder:CreateTexture(nil,"BACKGROUND",nil,-5)
      gloss:SetAllPoints(borderholder)
      gloss:SetTexture(ns.mediapath.."portrait_gloss")
      gloss:SetVertexColor(0.9,0.95,1,0.6)

    else
      self.Portrait = back:CreateTexture(nil,"BACKGROUND",nil,-7)
      self.Portrait:SetPoint("TOPLEFT",back,"TOPLEFT",pcfg.size*27/128,-pcfg.size*27/128)
      self.Portrait:SetPoint("BOTTOMRIGHT",back,"BOTTOMRIGHT",-pcfg.size*27/128,pcfg.size*27/128)
      self.Portrait:SetTexCoord(0.15,0.85,0.15,0.85)

      local border = back:CreateTexture(nil,"BACKGROUND",nil,-6)
      border:SetAllPoints(back)
      border:SetTexture(ns.mediapath.."portrait_border")
      border:SetVertexColor(0.6,0.5,0.5)

      local gloss = back:CreateTexture(nil,"BACKGROUND",nil,-5)
      gloss:SetAllPoints(back)
      gloss:SetTexture(ns.mediapath.."portrait_gloss")
      gloss:SetVertexColor(0.9,0.95,1,0.6)

    end

  end

  --create castbar func
  func.createCastbar = function(f)
	local frame = CreateFrame("Frame", "$parentCastBarDragFrame", f)
	frame:SetPoint(f.cfg.castbar.pos.a1, f.cfg.castbar.pos.af, f.cfg.castbar.pos.a2, f.cfg.castbar.pos.x+8, f.cfg.castbar.pos.y)
	frame:SetSize(100,10)
    local c = CreateFrame("StatusBar", "$parentCastbar", frame)
    --wow is this outdated...man I really need to rewrite how the drag stuff is handled
    tinsert(rOrbs_Bars,frame:GetName())
    c:SetSize(265,15)
    c:SetStatusBarTexture(f.cfg.castbar.texture)
    c:SetScale(f.cfg.castbar.scale)
    c:SetPoint("CENTER", frame, 0,0)
	c:SetFrameStrata("HIGH")
    c:SetStatusBarColor(f.cfg.castbar.color.bar.r,f.cfg.castbar.color.bar.g,f.cfg.castbar.color.bar.b,f.cfg.castbar.color.bar.a)
    --c:SetStatusBarColor(0,0,0,1)

    c.background = c:CreateTexture(nil,"OVERLAY",nil,-8)
    c.background:SetTexture(ns.mediapath.."castbar")
    c.background:SetPoint("TOP",0,24.9)
    c.background:SetPoint("LEFT",-170,0)
    c.background:SetPoint("RIGHT",170,0)
    c.background:SetPoint("BOTTOM",0,-24.2)

    c.bg = c:CreateTexture(nil,"BACKGROUND",nil,-6)
    c.bg:SetTexture(f.cfg.castbar.texture)
    c.bg:SetAllPoints(c)
    c.bg:SetVertexColor(f.cfg.castbar.color.bg.r,f.cfg.castbar.color.bg.g,f.cfg.castbar.color.bg.b,f.cfg.castbar.color.bg.a)

    c.Text =  func.createFontString(c, cfg.fonts.textFont, f.cfg.castbar.fontSize, "THINOUTLINE")
    c.Text:SetPoint("LEFT", 5, 0)
    c.Text:SetJustifyH("LEFT")

    c.Time =  func.createFontString(c, cfg.fonts.textFont, f.cfg.castbar.fontSize, "THINOUTLINE")
    c.Time:SetPoint("RIGHT", -2, 0)

    c.Text:SetPoint("RIGHT", -50, 0)
    --c.Text:SetPoint("RIGHT", c.Time, "LEFT", -10, 0) --right point of text will anchor left point of time


    c.Spark = c:CreateTexture(nil,"OVERLAY",nil,-7)
    c.Spark:SetBlendMode("ADD")
    c.Spark:SetVertexColor(0.8,0.6,0,1)

    c.glow = c:CreateTexture(nil, "OVERLAY",nil,-4)
    c.glow:SetTexture(ns.mediapath.."castbar_glow")
    c.glow:SetPoint("TOP",0,26.5)
    c.glow:SetPoint("LEFT",-35,0)
    c.glow:SetPoint("RIGHT",35,0)
    c.glow:SetPoint("BOTTOM",0,-24.2)
    c.glow:SetVertexColor(0,0,0,1)

    c.highlight = c:CreateTexture(nil,"OVERLAY",nil,-3)
    c.highlight:SetTexture(ns.mediapath.."castbar_highlight")
    c.highlight:SetPoint("TOP",0,26.5)
    c.highlight:SetPoint("LEFT",-35,0)
    c.highlight:SetPoint("RIGHT",35,0)
    c.highlight:SetPoint("BOTTOM",0,-24.2)
	

    if f.cfg.style == "target" then
      c.Shield = c:CreateTexture(nil,"BACKGROUND",nil,-8)
      c.Shield:SetColorTexture(0,0,0,0)
    end

    --safezone
    if f.cfg.style == "player" and f.cfg.castbar.latency then
      c.SafeZone = c:CreateTexture(nil,"OVERLAY")
      c.SafeZone:SetTexture(f.cfg.castbar.texture)
      c.SafeZone:SetVertexColor(0.6,0,0,0.6)
      c.SafeZone:SetPoint("TOPRIGHT")
      c.SafeZone:SetPoint("BOTTOMRIGHT")
    end

    if f.cfg.style == "player" then
      c.PostUpdateStage = func.PostUpdateStage
      c.cfg = f.cfg
    end
    func.applyDragFunctionality(frame)

    f.Castbar = c

  end

  func.PostUpdateStage = function(self, stage)
    if (stage == 1) then
      self:SetStatusBarColor(1, 0, 0)
    elseif (stage == 2) then
      self:SetStatusBarColor(0, 0, 1)
    elseif (stage == 3) then
      self:SetStatusBarColor(self.cfg.castbar.color.bar.r,self.cfg.castbar.color.bar.g,self.cfg.castbar.color.bar.b,self.cfg.castbar.color.bar.a)
    end
  end
  
  --fontstring func
  func.createFontString = function(f, font, size, outline,layer)
    local fs = f:CreateFontString(nil, layer or "OVERLAY")
    local fontPath = LSM:Fetch("font",font)
    fs:SetFont(fontPath, size, outline)
    fs:SetShadowColor(0,0,0,1)
    return fs
  end

local function CheckFont()
	if (rOrbs.db == nil) then 
		return false
	elseif (LSM:Fetch("font", rOrbs.db.profile.chatFont) == nil) then
		return false
	else
		return true
	end
end

  --allows frames to become movable but frames can be locked or set to default positions
  func.applyDragFunctionality = function(f,special)
    --save the default position
    local getPoint = function(self)
      local pos = {}
      pos.a1, pos.af, pos.a2, pos.x, pos.y = self:GetPoint()
      if pos.af and pos.af:GetName() then pos.af = pos.af:GetName() end
      return pos
    end
    f.defaultPosition = getPoint(f)
    local config = ns.cfg or cfg

    local savedScale = nil
    if rOrbsDB and rOrbsDB.frameScales then
      local frameName = f:GetName()
      if frameName and rOrbsDB.frameScales[frameName] then
        savedScale = rOrbsDB.frameScales[frameName]
      end
    end
    
    --new form of dragframe
    local df = CreateFrame("Frame",nil,f)
    df:SetAllPoints(f)
    -- Must match parent strata (orbs use LOW). Forcing HIGH made this overlay steal
    -- Shift-clicks from MEDIUM-strata action bars (e.g. nMainbar) in overlap zones
    -- near the square orb bounds.
    df:SetFrameStrata(f:GetFrameStrata())
    df:EnableMouse(false)
    
    local isScaling = false
    local isMoving = false
    -- Snap-to-Grid: StartMoving() setzt GetPoint oft erst verzoegert → manuelles Ziehen mit Raster
    local dragMoveActive = false
    local moveStartCX, moveStartCY = 0, 0
    local moveStartAX, moveStartAY = 0, 0
    local movePoint, moveRel, moveRelPoint = nil, nil, nil
    local startScale = 1
    local startX, startY = 0, 0

    -- ----------------------------------------------------------------
    -- Scale label: zeigt aktuellen Scale-Wert in der Mitte des Orbs
    -- ----------------------------------------------------------------
    -- Sublevel 7 > Modifier-Tint (6), sonst liegt die weiße Fläche über dem Text.
    local scaleLabel = df:CreateFontString(nil, "OVERLAY", nil, 7)
    scaleLabel:SetFont("Fonts\\FRIZQT__.TTF", 18, "OUTLINE")
    scaleLabel:SetPoint("CENTER", df, "CENTER", 0, 0)
    scaleLabel:SetTextColor(1, 1, 0.3, 1)
    scaleLabel:SetText("")
    scaleLabel:SetAlpha(0)
    df:EnableMouse(false)  -- reset after CreateFontString

    local t = df:CreateTexture(nil, "OVERLAY", nil, 6)
    t:SetAllPoints(df)
    t:SetColorTexture(1, 1, 1)
    t:SetAlpha(0.2)
    df.texture = t

    local isPortraitFrame = (f:GetName() == "rOrbsTargetFrame")

    -- X/Y unter dem Orb (gelb), wie rCastbar Edit-Mode — nur wenn Modifier oder aktiv gezogen/skalieren
    local coordFs = nil
    if not isPortraitFrame then
      coordFs = f:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall", 7)
      coordFs:SetPoint("TOP", f, "BOTTOM", 0, -4)
      coordFs:SetFont("Fonts\\FRIZQT__.TTF", 12, "OUTLINE")
      coordFs:SetTextColor(1, 1, 0, 1)
      coordFs:SetAlpha(0)
    end

    -- Vor UpdateCoordDisplay deklarieren: Lua 5.1 bindet sonst IsOrbDragLocked als globale Referenz.
    local function IsOrbDragLocked()
      return rOrbsDB and rOrbsDB.orbDragLocked == true
    end

    local function SnapCoordToGrid(v, grid)
      grid = grid or 10
      return math.floor(v / grid + 0.5) * grid
    end

    local function UpdateCoordDisplay()
      if not coordFs then return end
      if not config.framesUserplaced or IsOrbDragLocked() then
        coordFs:SetAlpha(0)
        return
      end
      local show = IsShiftKeyDown() or IsAltKeyDown() or isMoving or isScaling
      if not show then
        coordFs:SetAlpha(0)
        return
      end
      local px, rt, rp, x, y = f:GetPoint(1)
      if not px then
        coordFs:SetAlpha(0)
        return
      end
      coordFs:SetFormattedText("X = %.0f\nY = %.0f", x or 0, y or 0)
      coordFs:SetAlpha(1)
    end

    --- SHIFT = gruener Hinweis (wie rMinimap), ALT = roter Hinweis fuer Skalierung.
    local function updateModifierOverlay()
      local tex = df.texture
      if not tex or not config.framesUserplaced then return end
      if IsOrbDragLocked() then
        tex:Hide()
        return
      end
      local locked = config.framesLocked
      local shift, al = IsShiftKeyDown(), IsAltKeyDown()
      if locked then
        if al then
          tex:SetVertexColor(0.95, 0.2, 0.2)
          tex:SetAlpha(0.36)
          tex:Show()
        elseif shift then
          tex:SetVertexColor(0, 1, 0)
          tex:SetAlpha(0.24)
          tex:Show()
        else
          tex:Hide()
        end
      else
        tex:Show()
        if al then
          tex:SetVertexColor(0.95, 0.2, 0.2)
          tex:SetAlpha(0.3)
        else
          tex:SetVertexColor(0, 1, 0)
          tex:SetAlpha(0.2)
        end
      end
    end

    local function ShowScaleLabel(scaleVal)
      scaleLabel:SetText(string.format("%.2fx", scaleVal))
      scaleLabel:SetAlpha(1)
    end

    local function HideScaleLabel()
      local elapsed = 0
      local totalTime = 0.6
      local fadeFrame = CreateFrame("Frame")
      fadeFrame:SetScript("OnUpdate", function(self, dt)
        elapsed = elapsed + dt
        local alpha = math.max(0, 1 - (elapsed / totalTime))
        scaleLabel:SetAlpha(alpha)
        if elapsed >= totalTime then
          scaleLabel:SetAlpha(0)
          scaleLabel:SetText("")
          self:SetScript("OnUpdate", nil)
          self:Hide()
        end
      end)
      fadeFrame:Show()
    end

    -- ----------------------------------------------------------------
    -- Hilfsfunktion: Model nach Scale/Move neu berechnen
    -- f._orbType wird von engine:Apply() gesetzt (nach frame creation)
    -- ----------------------------------------------------------------
    local function RefreshOrbModel()
      if f._orbType and ns and ns.OrbTemplateEngine then
        ns.OrbTemplateEngine:Refresh(f._orbType)
      end
    end

    -- ----------------------------------------------------------------
    -- Grid: Alignment-Raster bei Verschieben (SHIFT) und Skalieren (ALT)
    -- ----------------------------------------------------------------
    local dragGrid = nil

    local function ShowGrid()
      if dragGrid then return end
      dragGrid = CreateFrame("Frame", nil, UIParent)
      dragGrid:SetAllPoints(UIParent)
      dragGrid:SetFrameStrata("BACKGROUND")
      local scale = 64
      local screenW = GetScreenWidth()
      local screenH = GetScreenHeight()
      local numV = scale
      local numH = math.floor(screenH / screenW * scale)
      local offV = screenW / numV
      local offH = screenH / numH
      for i = 1, numV - 1 do
        local line = dragGrid:CreateLine()
        line:SetThickness(1)
        line:SetColorTexture(1, 1, 1, 0.3)
        line:SetStartPoint("TOPLEFT",   i * offV, 0)
        line:SetEndPoint("BOTTOMLEFT",  i * offV, 0)
      end
      for i = 1, numH - 1 do
        local line = dragGrid:CreateLine()
        line:SetThickness(1)
        line:SetColorTexture(1, 1, 1, 0.3)
        line:SetStartPoint("BOTTOMLEFT", 0, i * offH)
        line:SetEndPoint("BOTTOMRIGHT",  0, i * offH)
      end
      dragGrid:Show()
    end

    local function HideGrid()
      if dragGrid then
        dragGrid:Hide()
        dragGrid = nil
      end
    end

    -- Enable mouse only when modifier keys are pressed (for dragging/scaling)
    -- Fuer den Portrait-Frame (rOrbsTargetFrame): dragframe bleibt immer MouseDisabled
    -- damit Klicks zum Portrait-Button durchdringen koennen.

    df:SetScript("OnUpdate", function(self)
      if isPortraitFrame then
        -- Portrait dragframe niemals EnableMouse(true) setzen
        if self:IsMouseEnabled() then self:EnableMouse(false) end
        updateModifierOverlay()
        return
      end

      local dragLocked = IsOrbDragLocked()
      local modActive = (not dragLocked) and (IsShiftKeyDown() or IsAltKeyDown())
      -- isMoving: Maus waehrend Blizzard-Drag oder Snap-Drag aktiv lassen (auch wenn Shift kurz losgelassen)
      local shouldBeActive = modActive or isScaling or isMoving
      if shouldBeActive and not self:IsMouseEnabled() then
        self:EnableMouse(true)
      elseif not shouldBeActive and self:IsMouseEnabled() and not isScaling and not isMoving then
        self:EnableMouse(false)
      end

      -- Snap-to-Grid: Position jedes Frame mit Raster folgen (kein StartMoving)
      if dragMoveActive and rOrbsDB and rOrbsDB.snapToGrid and movePoint and moveRel then
        local uisc = UIParent:GetEffectiveScale()
        local cx, cy = GetCursorPosition()
        cx, cy = cx / uisc, cy / uisc
        local nx = SnapCoordToGrid(moveStartAX + (cx - moveStartCX), 10)
        local ny = SnapCoordToGrid(moveStartAY + (cy - moveStartCY), 10)
        f:ClearAllPoints()
        f:SetPoint(movePoint, moveRel, moveRelPoint, nx, ny)
      end

      -- Handle scaling: update scale + model + label live
      if isScaling then
        local x, y = GetCursorPosition()
        local diffY = (y - startY) / 100
        local newScale = math.max(0.5, math.min(3.0, startScale + diffY))
        f:SetScale(newScale)
        -- Modell-Positionen neu berechnen (ApplyParams nutzt orb:GetScale())
        RefreshOrbModel()
        ShowScaleLabel(newScale)
      end

      updateModifierOverlay()
      UpdateCoordDisplay()
    end)

    df:SetScript("OnMouseDown", function(self, button)
      if isPortraitFrame then return end  -- Portrait dragframe: niemals Klicks verarbeiten
      if IsOrbDragLocked() then return end
      if button == "LeftButton" then
        if IsShiftKeyDown() then
          isMoving = true
          ShowGrid()
          if rOrbsDB and rOrbsDB.snapToGrid then
            local p, rt, rp, ax, ay = f:GetPoint(1)
            if p and ax ~= nil and ay ~= nil then
              dragMoveActive = true
              moveRel = rt or UIParent
              movePoint = p
              moveRelPoint = rp
              moveStartAX, moveStartAY = ax, ay
              local uisc = UIParent:GetEffectiveScale()
              local cx, cy = GetCursorPosition()
              moveStartCX = cx / uisc
              moveStartCY = cy / uisc
            else
              f:StartMoving()
            end
          else
            f:StartMoving()
          end
        elseif IsAltKeyDown() then
          isScaling = true
          ShowGrid()
          startScale = f:GetScale()
          startX, startY = GetCursorPosition()
          ShowScaleLabel(startScale)
        end
      end
    end)

    df:SetScript("OnMouseUp", function(self, button)
      if button == "LeftButton" then
        if dragMoveActive then
          dragMoveActive = false
          f:StopMovingOrSizing()
          HideGrid()
          RefreshOrbModel()
          if not rOrbsDB then rOrbsDB = {} end
          if not rOrbsDB.framePositions then rOrbsDB.framePositions = {} end
          local frameName = f:GetName()
          if frameName then
            local point, relativeTo, relativePoint, x, y = f:GetPoint(1)
            if point then
              rOrbsDB.framePositions[frameName] = {
                point = point,
                relativeTo = relativeTo and relativeTo:GetName() or "UIParent",
                relativePoint = relativePoint,
                x = x,
                y = y
              }
            end
          end
          isMoving = false
          UpdateCoordDisplay()
          return
        end

        f:StopMovingOrSizing()

        if isScaling then
          HideGrid()
          if not rOrbsDB then rOrbsDB = {} end
          if not rOrbsDB.frameScales then rOrbsDB.frameScales = {} end
          local frameName = f:GetName()
          if frameName then
            rOrbsDB.frameScales[frameName] = f:GetScale()
          end
          -- Finales Model-Refresh nach Skalierung
          RefreshOrbModel()
          isScaling = false
          HideScaleLabel()
        elseif isMoving then
          HideGrid()
          if not rOrbsDB then rOrbsDB = {} end
          if not rOrbsDB.framePositions then rOrbsDB.framePositions = {} end
          local frameName = f:GetName()
          -- StartMoving-Fallback wenn Snap aktiv aber GetPoint beim Mousedown fehlte: naechstes Frame einrasten
          if rOrbsDB.snapToGrid == true then
            C_Timer.After(0, function()
              if not f or not f.GetPoint then return end
              local p, rt, rp, x, y = f:GetPoint(1)
              if not (p and x ~= nil and y ~= nil) then return end
              local rel = rt or UIParent
              local nx, ny = SnapCoordToGrid(x, 10), SnapCoordToGrid(y, 10)
              if nx ~= x or ny ~= y then
                f:ClearAllPoints()
                f:SetPoint(p, rel, rp, nx, ny)
              end
              RefreshOrbModel()
              local fn = f:GetName()
              if fn and rOrbsDB.framePositions then
                local pt, rto, rpt, ox, oy = f:GetPoint(1)
                if pt then
                  rOrbsDB.framePositions[fn] = {
                    point = pt,
                    relativeTo = rto and rto:GetName() or "UIParent",
                    relativePoint = rpt,
                    x = ox,
                    y = oy
                  }
                end
              end
            end)
          else
            RefreshOrbModel()
            if frameName then
              local point, relativeTo, relativePoint, x, y = f:GetPoint(1)
              if point then
                rOrbsDB.framePositions[frameName] = {
                  point = point,
                  relativeTo = relativeTo and relativeTo:GetName() or "UIParent",
                  relativePoint = relativePoint,
                  x = x,
                  y = y
                }
              end
            end
          end
          isMoving = false
        end
        UpdateCoordDisplay()
      end
    end)

    f.dragframe = df
    f.dragframe:Hide()
    if not special then
      f:SetClampedToScreen(true)
    end

    if not config.framesUserplaced then
      f:SetMovable(false)
    else
      f:SetMovable(true)
      f:SetUserPlaced(true)
      
      f.dragframe:Show()
      f.dragframe:SetScript("OnEnter", function(s)
        -- Nur im Modifier-Modus Tooltip zeigen; kein Tooltip-Hijack für rOrbsTargetFrame
        local parent = s:GetParent()
        local name = parent:GetName()

        if name == "rOrbsTargetFrame" then
          -- Portrait-Frame: dragframe niemals Tooltip übernehmen lassen
          -- Der Portrait-Button selbst hat OnEnter und kümmert sich darum
          return
        end

        if name == "rOrbsHealthOrb" then
          UnitFrame_OnEnter(parent)
        elseif name == "rOrbsTargetHealthOrb" and UnitExists("target") then
          GameTooltip:SetOwner(parent, "ANCHOR_RIGHT")
          GameTooltip:SetUnit("target")
          GameTooltip:Show()
        end

        if IsShiftKeyDown() or IsAltKeyDown() then
          GameTooltip:SetOwner(s, "ANCHOR_TOP")
          GameTooltip:AddLine(parent:GetName() or "?", 0, 1, 0.5, 1, 1, 1)
          GameTooltip:AddLine("SHIFT + Click & Drag = Move", 1, 1, 1, 1, 1, 1)
          GameTooltip:AddLine("ALT + Click & Drag = Scale", 1, 1, 1, 1, 1, 1)
          GameTooltip:Show()
        end
      end)
      f.dragframe:SetScript("OnLeave", function(s)
        local parent = s:GetParent()
        local name = parent:GetName()
        if name == "rOrbsTargetFrame" then return end
        if name == "rOrbsHealthOrb" then
          UnitFrame_OnLeave(parent)
        end
        GameTooltip:Hide()
      end)
    end
  end

  --simple frame movement
  func.simpleDragFunc = function(f)

    f:SetHitRectInsets(-15,-15,-15,-15)
    f:SetClampedToScreen(true)
    f:SetMovable(true)
    f:SetUserPlaced(true)

    f:EnableMouse(true)

    f:RegisterForDrag("LeftButton")
    --[[
    f:SetScript("OnEnter", function(s)
      GameTooltip:SetOwner(s, "ANCHOR_CURSOR")
      GameTooltip:AddLine(s:GetName(), 0, 1, 0.5, 1, 1, 1)
      GameTooltip:AddLine("Hold down ALT+SHIFT to drag!", 1, 1, 1, 1, 1, 1)
      GameTooltip:Show()
    end)
    f:SetScript("OnLeave", function(s) GameTooltip:Hide() end)
    ]]--
    --f:SetScript("OnDragStart", function(s) if IsAltKeyDown() and IsShiftKeyDown() then s:StartMoving() end end)
    f:SetScript("OnDragStart", function(s) s:StartMoving() end)
    f:SetScript("OnDragStop", function(s) s:StopMovingOrSizing() end)

  end

  --create icon func
  func.createIcon = function(f,layer,size,anchorframe,anchorpoint1,anchorpoint2,posx,posy,sublevel)
    local icon = f:CreateTexture(nil,layer,nil,sublevel)
    icon:SetSize(size,size)
    icon:SetPoint(anchorpoint1,anchorframe,anchorpoint2,posx,posy)
    return icon
  end

  --heal prediction
  func.healPrediction = function(self)
    if not self.cfg.healprediction or (self.cfg.healprediction and not self.cfg.healprediction.show) or rOrbs:CheckClientVersion(WOW_PROJECT_CLASSIC) then return end
    local w = self.Health:GetWidth()
    if w == 0 then
      w = self:GetWidth()-24.5-24.5 --raids and party have no width on the health frame for whatever reason, thus use self and subtract the setpoint values
    end
    -- my heals
    local mhpb = CreateFrame("StatusBar", nil, self.Health)
    mhpb:SetFrameLevel(self.Health:GetFrameLevel())
    mhpb:SetPoint("TOPLEFT", self.Health, "TOPRIGHT", 0, 0)
    mhpb:SetPoint("BOTTOMLEFT", self.Health, "BOTTOMRIGHT", 0, 0)
    mhpb:SetWidth(w)
    mhpb:SetStatusBarTexture(self.cfg.healprediction.texture)
    mhpb:SetStatusBarColor(self.cfg.healprediction.color.myself.r,self.cfg.healprediction.color.myself.g,self.cfg.healprediction.color.myself.b,self.cfg.healprediction.color.myself.a)
    -- other heals
    local ohpb = CreateFrame("StatusBar", nil, self.Health)
    ohpb:SetFrameLevel(self.Health:GetFrameLevel())
    ohpb:SetPoint("TOPLEFT", mhpb:GetStatusBarTexture(), "TOPRIGHT", 0, 0)
    ohpb:SetPoint("BOTTOMLEFT", mhpb:GetStatusBarTexture(), "BOTTOMRIGHT", 0, 0)
    ohpb:SetWidth(w)
    ohpb:SetStatusBarTexture(self.cfg.healprediction.texture)
    ohpb:SetStatusBarColor(self.cfg.healprediction.color.other.r,self.cfg.healprediction.color.other.g,self.cfg.healprediction.color.other.b,self.cfg.healprediction.color.other.a)
    -- Register it with oUF
    self.HealthPrediction = {
      myBar = mhpb,
      otherBar = ohpb,
      maxOverflow = self.cfg.healprediction.maxoverflow,
    }
  end

  --total absorb
  func.totalAbsorb = function(self)
    if not self.cfg.totalabsorb or (self.cfg.totalabsorb and not self.cfg.totalabsorb.show) or rOrbs:CheckClientVersion(WOW_PROJECT_CLASSIC) then return end
	
    local w = self.Health:GetWidth()
	
	if self.cfg.style == "party" then
		if self.cfg.vertical == false then
			if w == 0 then
				w = self:GetWidth()-24.5-24.5 --raids and party have no width on the health frame for whatever reason, thus use self and subtract the setpoint values
			end
		else
			if w == 0 then
				w = self:GetWidth()-60-24.5
			end
		end
	else
		if w == 0 then
			w = self:GetWidth()-24.5-24.5 --raids and party have no width on the health frame for whatever reason, thus use self and subtract the setpoint values
		end
	end
    local absorbBar = CreateFrame("StatusBar", nil, self.Health)
    --new anchorpoint, absorb will now overlay the healthbar from right to left
    absorbBar:SetFrameLevel(self.Health:GetFrameLevel()+1)
    absorbBar:SetPoint("TOPRIGHT", self.Health, 0, 0)
    absorbBar:SetPoint("BOTTOMRIGHT", self.Health, 0, 0)
    absorbBar:SetWidth(w)
    absorbBar:SetStatusBarTexture(self.cfg.totalabsorb.texture)
    absorbBar:SetStatusBarColor(self.cfg.totalabsorb.color.bar.r,self.cfg.totalabsorb.color.bar.g,self.cfg.totalabsorb.color.bar.b,self.cfg.totalabsorb.color.bar.a)    
    absorbBar:SetReverseFill(true)
    -- Register with oUF
    self.TotalAbsorb = absorbBar    
  end
  
--Register LSM media
LSM:Register("border", "RB border", "Interface\\AddOns\\rOrbs\\media\\5.tga")
LSM:Register("statusbar", "Solid", "Interface\\AddOns\\rOrbs\\media\\Solid.tga")
LSM:Register("statusbar", "Roth_Statusbar1", "Interface\\AddOns\\rOrbs\\media\\statusbar")
LSM:Register("statusbar", "Roth_Statusbar2", "Interface\\AddOns\\rOrbs\\media\\statusbar2")
LSM:Register("statusbar", "Roth_Statusbar3", "Interface\\AddOns\\rOrbs\\media\\statusbar3")
LSM:Register("statusbar", "Roth_Statusbar4", "Interface\\AddOns\\rOrbs\\media\\statusbar4")
LSM:Register("statusbar", "Roth_Statusbar5", "Interface\\AddOns\\rOrbs\\media\\statusbar5")
LSM:Register("statusbar", "Roth_Statusbar6", "Interface\\AddOns\\rOrbs\\media\\statusbar128")
LSM:Register("statusbar", "Roth_Statusbar7", "Interface\\AddOns\\rOrbs\\media\\statusbar128_3")
LSM:Register("statusbar", "Roth_Statusbar8", "Interface\\AddOns\\rOrbs\\media\\statusbar256")
LSM:Register("statusbar", "Roth_Statusbar9", "Interface\\AddOns\\rOrbs\\media\\statusbar256_2")
LSM:Register("statusbar", "Roth_Statusbar10", "Interface\\AddOns\\rOrbs\\media\\statusbar256_3")
LSM:Register("background", "Solid", "Interface\\AddOns\\rOrbs\\media\\Solid.tga")
LSM:Register("font", "Cracked", "Interface\\AddOns\\rOrbs\\media\\Cracked-Narrow.ttf")
LSM:Register("font", "Expressway", "Interface\\AddOns\\rOrbs\\media\\Expressway.ttf")
