Quantcast

Lots of work

Andrew Mordecai [07-16-16 - 20:31]
Lots of work
Filename
.gitignore
Auras.lua
Config.lua
Elements/AFK.lua
Elements/AuraStack.lua
Elements/BurningEmbers.lua
Elements/DemonicFury.lua
Elements/DispelIcon.lua
Fonts/LatoBold.ttf
Functions.lua
Layout.lua
Textures/statusbar4.tga
oUF_Stardust.toc
diff --git a/.gitignore b/.gitignore
index 586299e..5cd0e19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 Libs/
+Textures/
+!Textures/statusbar4.tga
diff --git a/Auras.lua b/Auras.lua
new file mode 100644
index 0000000..1bc2212
--- /dev/null
+++ b/Auras.lua
@@ -0,0 +1,243 @@
+--[[-----------------------------------------------------------------------
+	oUF: Stardust - a layout for the oUF framework
+	Copyright (c) 2016 Andrew Mordecai <armordecai@protonmail.ch>
+	This code is released under the zlib license; see LICENSE for details.
+-------------------------------------------------------------------------]]
+
+local _, Stardust = ...
+
+local _, class = UnitClass("player")
+
+local b = {
+	ALL = 0,
+	SELF = 1,
+	MINE = 2,
+	FRIEND = 4,
+	FRIEND_OOC = 8, -- for long term buffs and non-combat effects
+	ENEMY = 16,
+}
+
+local t = {
+	[  2825] = b.ALL, -- Bloodlust
+	[ 32182] = b.ALL, -- Heroism
+	-- Crowd Control >= 10 seconds
+	[   710] = b.ALL, -- Banish
+	[  2094] = b.ALL, -- Blind
+	[ 33796] = b.ALL, -- Cyclone
+	[   605] = b.ALL, -- Dominate Mind
+	[   339] = b.ALL, -- Entangling Roots
+	[118699] = b.ALL, -- Fear
+	[  3355] = b.ALL, -- Freezing Trap
+	[ 51514] = b.ALL, -- Hex
+							-- TODO: find spell ID for Hex with Glyph of the Compy
+	[  5484] = b.ALL, -- Howl of Terror
+	[115078] = b.ALL, -- Paralysis
+	[   118] = b.ALL, -- Polymorph
+	[161353] = b.ALL, -- Polymorph (bear cub)
+	[ 61305] = b.ALL, -- Polymorph (cat)
+	[161354] = b.ALL, -- Polymorph (monkey)
+	[161372] = b.ALL, -- Polymorph (peacock)
+	[161355] = b.ALL, -- Polymorph (penguin)
+	[ 28272] = b.ALL, -- Polymorph (pig)
+	[126819] = b.ALL, -- Polymorph (porcupine)
+	[ 61721] = b.ALL, -- Polymorph (rabbit)
+	[ 61780] = b.ALL, -- Polymorph (turkey)
+	[ 28271] = b.ALL, -- Polymorph (turtle)
+	[ 20066] = b.ALL, -- Repentance
+	[ 82691] = b.ALL, -- Ring of Frost
+	[  6770] = b.ALL, -- Sap
+	[  9484] = b.ALL, -- Shackle Undead
+	[ 10326] = b.ALL, -- Turn Evil
+	[ 19386] = b.ALL, -- Wyvern Sting
+	-- Racial Abilities
+	[ 25046] = b.MINE, -- Arcane Torrent (energy)
+	[ 28730] = b.MINE, -- Arcane Torrent (mana)
+	[ 50613] = b.MINE, -- Arcane Torrent (runic power)
+	[ 69179] = b.MINE, -- Arcane Torrent (rage)
+	[ 80483] = b.MINE, -- Arcane Torrent (focus)
+	[129597] = b.MINE, -- Arcane Torrent (chi)
+	[155145] = b.MINE, -- Arcane Torrent (holy power)
+	[ 26297] = b.MINE, -- Berserking
+	[ 20572] = b.MINE, -- Blood Fury (attack power)
+	[ 33697] = b.MINE, -- Blood Fury (melee attack power + spell power)
+	[ 33702] = b.MINE, -- Blood Fury (spell power)
+	[ 68992] = b.MINE, -- Darkflight
+	[107079] = b.MINE, -- Quaking Palm
+	[ 58984] = b.MINE, -- Shadowmeld
+	[ 20594] = b.MINE, -- Stoneform
+	[ 20549] = b.MINE, -- War Stomp
+}
+if class == "DEATHKNIGHT" then
+elseif class == "DEMONHUNTER" then
+elseif class == "DRUID" then
+	t[ 22812] = b.MINE -- Barkskin
+	t[ 50334] = b.MINE -- Berserk (bear)
+	t[106951] = b.MINE -- Berserk (cat)
+	t[155835] = b.MINE -- Bristling Fur
+	t[112071] = b.MINE -- Celestial Alignment
+	t[102351] = b.MINE -- Cenarion Ward (buff)
+	t[102352] = b.MINE -- Cenarion Ward (heal)
+	t[ 16870] = b.MINE -- Clearcasting
+	t[135700] = b.MINE -- Clearcasting (from Omen of Clarity)
+	t[  1850] = b.MINE -- Dash
+	t[137452] = b.MINE -- Displacer Beast
+	t[145162] = b.MINE -- Dream of Cenarius
+	t[157228] = b.MINE -- Empowered Moonkin
+	t[  6795] = b.MINE -- Growl
+	t[108291] = b.MINE -- Heart of the Wild (balance)
+	t[108292] = b.MINE -- Heart of the Wild (feral)
+	t[108293] = b.MINE -- Heart of the Wild (guardian)
+	t[108294] = b.MINE -- Heart of the Wild (restoration)
+	t[    99] = b.MINE -- Incapacitating Roar
+	t[102560] = b.MINE -- Incarnation: Chosen of Elune
+	t[102543] = b.MINE -- Incarnation: King of the Jungle
+	t[102558] = b.MINE -- Incarnation: Son of Ursoc
+	t[ 33891] = b.MINE -- Incarnation: Tree of Life
+	t[102342] = b.MINE -- Ironbark
+	t[ 33745] = b.MINE -- Lacerate
+	t[ 33763] = b.MINE -- Lifebloom
+	t[164547] = b.MINE -- Lunar Empowerment (from Starsurge)
+	t[ 22570] = b.MINE -- Maim
+	-- TODO: find spell ID for mangle slow debuff
+	t[  1126] = b.FRIEND_OOC -- Mark of the Wild
+	t[102359] = b.MINE -- Mass Entanglement
+	t[  5211] = b.MINE -- Mighty Bash
+	t[164812] = b.MINE -- Moonfire (TODO?)
+	t[132158] = b.MINE -- Nature's Swiftness
+	t[124974] = b.MINE -- Nature's Vigil
+	t[ 69369] = b.MINE -- Predatory Swiftness
+	t[  5215] = b.MINE -- Prowl
+	t[158792] = b.MINE -- Pulverize
+	t[155722] = b.MINE -- Rake
+	t[  8936] = b.MINE -- Regrowth
+	t[   774] = b.MINE -- Rejuvenation
+	t[  1079] = b.MINE -- Rip
+	t[132402] = b.MINE -- Savage Defense
+	t[ 52610] = b.MINE -- Savage Roar
+	t[164545] = b.MINE -- Solar Empowerment (from Starsurge)
+	t[114108] = b.MINE -- Soul of the Forest
+	t[106989] = b.MINE -- Stampeding Roar
+	t[184989] = b.MINE -- Starfall
+	t[152221] = b.MINE -- Stellar Flare
+	t[164815] = b.MINE -- Sunfire
+	t[ 61336] = b.MINE -- Survival Instincts
+	t[ 77758] = b.MINE -- Thrash (bear)
+	t[106830] = b.MINE -- Thrash (cat)
+	t[  5217] = b.MINE -- Tiger's Fury
+	t[102416] = b.MINE -- Wild Charge (aquatic)
+	t[ 16979] = b.MINE -- Wild Charge (bear)
+	t[ 49376] = b.MINE -- Wild Charge (cat)
+	t[ 48438] = b.MINE -- Wild Growth
+elseif class == "HUNTER" then
+elseif class == "MAGE" then
+elseif class == "MONK" then
+elseif class == "PALADIN" then
+elseif class == "PRIEST" then
+elseif class == "ROGUE" then
+elseif class == "SHAMAN" then
+	t[108281] = b.MINE -- Ancestral Guidance
+	t[ 16188] = b.MINE -- Ancestral Swiftness
+	t[114050] = b.MINE -- Ascendance (elemental)
+	t[114051] = b.MINE -- Ascendance (enhancement)
+	t[114052] = b.MINE -- Ascendance (restoration)
+	t[108271] = b.MINE -- Astral Shift
+	t[   974] = b.MINE -- Earth Shield
+	t[  3600] = b.MINE -- Earthbind (Totem)
+	t[ 64695] = b.MINE -- Earthgrab (Totem)
+	t[157174] = b.MINE -- Elemental Fusion
+	t[ 16166] = b.MINE -- Elemental Mastery
+--	t[162557] = b.MINE -- Enhanced Unleash
+	t[  8050] = b.MINE -- Flame Shock
+	t[  8056] = b.MINE -- Frost Shock
+	t[ 63685] = b.MINE -- Frozen Power (from Frost Shock with talent)
+	t[  8178] = b.SELF -- Grounding Totem Effect
+	t[ 89523] = b.SELF -- Grounding Totem (with Glyph of Grounding Totem)
+	t[ 77762] = b.MINE -- Lava Surge
+--	t[ 31616] = b.SELF -- Nature's Guardian
+	t[ 61295] = b.MINE -- Riptide
+	t[ 30823] = b.MINE -- Shamanistic Rage
+	t[ 98007] = b.FRIEND -- Spirit Link Totem (TODO?)
+	t[151175] = b.FRIEND -- Spirit Link Totem (TODO?)
+	t[ 58875] = b.MINE -- Spirit Walk
+	t[ 79206] = b.MINE -- Spiritwalker's Grace
+	t[118905] = b.MINE -- Static Charge (from Capacitor Totem)
+	t[114893] = b.MINE -- Stone Bulwark
+	t[ 17364] = b.MINE -- Stormstrike
+	t[ 53390] = b.MINE -- Tidal Waves
+	t[165462] = b.MINE -- Unleash Flame
+	t[ 73685] = b.MINE -- Unleash Life
+	t[118470] = b.MINE -- Unleashed Fury (elemental)
+	t[118473] = b.MINE -- Unleashed Fury (restoration)
+	t[   546] = b.FRIEND_OOC -- Water Walking
+--	t[114896] = b.MINE -- Windwalk Totem
+	t[115356] = b.MINE -- Windstrike (replaces Stormstrike during Ascendance)
+elseif class == "WARLOCK" then
+	t[   980] = b.MINE -- Agony
+	t[117828] = b.MINE -- Backdraft
+	t[111397] = b.MINE -- Blood Horror (self buff)
+	t[137143] = b.MINE -- Blood Horror (debuff)
+	t[111400] = b.MINE -- Burning Rush
+	t[124915] = b.MINE -- Chaos Wave
+	t[ 17962] = b.MINE -- Conflagrate
+	t[146739] = b.MINE -- Corruption
+	t[110913] = b.MINE -- Dark Bargain (absorb)
+	t[110914] = b.MINE -- Dark Bargain (dot)
+	t[109773] = b.FRIEND_OOC -- Dark Intent
+	t[108359] = b.MINE -- Dark Regeneration
+	t[113858] = b.MINE -- Dark Soul: Instability
+	t[113861] = b.MINE -- Dark Soul: Knowledge
+	t[113860] = b.MINE -- Dark Soul: Misery
+	t[   603] = b.MINE -- Doom
+	t[114635] = b.MINE -- Ember Tap (with glyph)
+	t[  1098] = b.ALL -- Enslave Demon
+	t[108683] = b.MINE -- Fire and Brimstone
+	t[ 47960] = b.MINE -- Hand of Gul'dan
+	t[ 48181] = b.MINE -- Haunt
+	t[ 80240] = b.MINE -- Havoc
+	t[  6262] = b.MINE -- Healthstone (with glyph) (TODO?)
+	t[157736] = b.MINE -- Immolate
+	t[108686] = b.MINE -- Immolate (with Fire and Brimstone)
+	t[137587] = b.MINE -- Kil'jaeden's Cunning
+	t[  1454] = b.MINE -- Life Tab (with glyph) (TODO?)
+	t[171018] = b.MINE -- Meteor Strike
+	t[  6789] = b.MINE -- Mortal Coil
+	t[122355] = b.MINE -- Molten Core
+	t[104232] = b.MINE -- Rain of Fire
+	t[108416] = b.MINE -- Sacrificial Pact
+	t[ 27243] = b.MINE -- Seed of Corruption
+	t[114790] = b.MINE -- Seed of Corruption (with Soulburn)
+	t[ 30283] = b.MINE -- Shadowfury
+	t[ 86211] = b.MINE -- Soul Swap
+	t[ 74434] = b.MINE -- Soulburn
+	t[ 20707] = b.FRIEND -- Soulstone
+	t[  5697] = b.FRIEND_OOC -- Unending Breath
+	t[104773] = b.MINE -- Unending Resolve
+	t[ 30108] = b.MINE -- Unstable Affliction
+elseif class == "WARRIOR" then
+end
+
+---------------------------
+-- Functions
+---------------------------
+
+local UnitAffectingCombat = UnitAffectingCombat
+local UnitIsUnit = UnitIsUnit
+local UnitReaction = UnitReaction
+
+function Stardust.AuraFilter(element, unit, icon, name, _, _, count, dispelType, duration, expires, caster, isStealable, _, spellID, canApplyAura, isBossDebuff)
+	local x = t[spellID]
+	if isBossDebuff or caster == "vehicle" or x == b.ALL then
+		return true
+	elseif x == b.MINE then
+		return icon.isPlayer or caster == "pet"
+	elseif x == b.FRIEND then
+		return UnitReaction(unit, "player") >= 4
+	elseif x == b.FRIEND_OOC then
+		return UnitReaction(unit, "player") >= 4 and not UnitAffectingCombat("player")
+	elseif x == b.ENEMY then
+		return UnitReaction(unit, "player") < 4
+	elseif x == b.SELF then
+		return UnitIsUnit(unit, "player")
+	end
+end
+
diff --git a/Config.lua b/Config.lua
index 1f58cee..862dfe7 100644
--- a/Config.lua
+++ b/Config.lua
@@ -6,16 +6,23 @@

 local _, Stardust = ...

+local LibSharedMedia = LibStub("LibSharedMedia-3.0")
+LibSharedMedia:Register("font", "Lato Bold", "Interface\\AddOns\\oUF_Stardust\\Fonts\\LatoBold.ttf", bit.bor(LibSharedMedia.LOCALE_BIT_western, LibSharedMedia.LOCALE_BIT_ruRU))
+--LibSharedMedia:Register("statusbar", "Qulight", "Interface\\AddOns\\oUF_Stardust\\Textures\\statusbar4")
+
 Stardust.config = {
 	-- Style
 	barTexture = "Qulight",
 	bgMultiplier = 0.35,
-	numberFont = "Blizzard Bold",
-	textFont = "Blizzard",
+	numberFont = "Lato Bold",
+	numberLarge = 26,
+	numberSmall = 14,
+	textFont = "Lato Bold",
+	textSize = 16,

 	-- Layout
 	width = 220,
-	height = 32,
+	height = 38,
 	power = true,
 	powerHeight = 5,

@@ -42,18 +49,12 @@ Stardust.config = {
 	}
 }

-Stardust.colors = {
-	power = {
-		MANA = { 0, 0.8, 1 }
-	},
-	totems = {
-		[1] = { 0.6, 1,   0.2 }, -- Earth
-		[2] = { 1,   0.6, 0.2 }, -- Fire
-		[3] = { 0.2, 0.8, 1   }, -- Water
-		[4] = { 0.8, 0.4, 1   }, -- Air
-	}
-}
+oUF.colors.power.MANA = { 0, 0.8, 1 }

-setmetatable(Stardust.colors, { __index = oUF.colors })
-setmetatable(Stardust.colors.power, { __index = oUF.colors.power })
+oUF.colors.totems = {
+	[1] = { 0.6, 1,   0.2 }, -- Earth
+	[2] = { 1,   0.6, 0.2 }, -- Fire
+	[3] = { 0.2, 0.8, 1   }, -- Water
+	[4] = { 0.8, 0.4, 1   }, -- Air
+}

diff --git a/Elements/AFK.lua b/Elements/AFK.lua
new file mode 100644
index 0000000..d6a9cad
--- /dev/null
+++ b/Elements/AFK.lua
@@ -0,0 +1,101 @@
+--[[-----------------------------------------------------------------------
+	oUF_Phanx/Elements/AFK.lua
+	Element to display AFK times on oUF frames.
+	Based on code from oUF_Smurf by Merl@chainweb.net.
+	Written and distributed by Phanx with permission.
+
+	Additional changes for oUF Stardust:
+	- Added PreUpdate and PostUpdate callbacks.
+
+	Usage:
+	self.AFK = self:CreateFontString(nil, "OVERLAY")
+	self.AFK:SetPoint("CENTER")
+----------------------------------------------------------------------]]
+
+if select(4, GetAddOnInfo("oUF_AFK")) then return end
+
+local _, ns = ...
+local oUF = ns.oUF or oUF
+assert(oUF, "AFK element requires oUF")
+
+local times = {}
+local elements = {}
+
+local updater = CreateFrame("Frame")
+updater:Hide()
+
+local lastUpdate = 0
+local floor, mod, next, pairs, GetTime = floor, mod, next, pairs, GetTime
+updater:SetScript("OnUpdate", function(self, elapsed)
+	lastUpdate = lastUpdate + elapsed
+	if lastUpdate > 0.2 then
+		lastUpdate = 0
+		if not next(times) then
+			self:Hide()
+		end
+		for element, unit in pairs(elements) do
+			local t = times[unit]
+			if t then
+				t = GetTime() - t
+				element:SetFormattedText("AFK %d:%02.0f", floor(t / 60), mod(t, 60))
+			else
+				elements[element] = nil
+				element:SetText("") -- nil gives it 0 height which might disturb the layout
+			end
+		end
+	end
+end)
+
+local function Update(self, event, unit)
+	if unit ~= self.unit then return end
+
+	local element = self.AFK
+	if element.PreUpdate then
+		element:PreUpdate()
+	end
+
+	local afk = UnitIsAFK(unit)
+
+	if afk and not times[unit] then
+		times[unit] = GetTime()
+		elements[element] = unit
+		updater:Show()
+	elseif times[unit] and not afk then
+		times[unit] = nil
+		element:SetText("")
+	end
+
+	if element.PostUpdate then
+		element:PostUpdate(afk)
+	end
+end
+
+local ForceUpdate = function(element)
+	return Update(element.__owner, "ForceUpdate", element.__owner.unit)
+end
+
+local function Enable(self)
+	local element = self.AFK
+	if not element then return end
+
+	element.__owner = self
+	element.ForceUpdate = Update
+
+	if not element:GetFont() then
+		element:SetFontObject("GameFontHighlightSmall")
+	end
+
+	self:RegisterEvent("PLAYER_FLAGS_CHANGED", Update)
+	return true
+end
+
+local function Disable(self)
+	local element = self.AFK
+	if not element then return end
+
+	self:UnregisterEvent("PLAYER_FLAGS_CHANGED", Update)
+	element:Hide()
+end
+
+oUF:AddElement("AFK", Update, Enable, Disable)
+
diff --git a/Elements/AuraStack.lua b/Elements/AuraStack.lua
new file mode 100755
index 0000000..fe25d14
--- /dev/null
+++ b/Elements/AuraStack.lua
@@ -0,0 +1,150 @@
+--[[--------------------------------------------------------------------
+	oUF_Phanx/Elements/AuraStack.lua
+	Element to track stacking self-(de)buffs like combo points.
+	Copyright (c) 2008-2015 Phanx <addons@phanx.net>. All rights reserved.
+
+	You may embed this module in your own layout, but please do not
+	distribute it as a standalone plugin.
+
+	Modified for oUF Stardust:
+	- Added .spec key to hide in wrong specs
+------------------------------------------------------------------------
+	Usage:
+
+	self.AuraStack = {
+		aura = GetSpellInfo(53817) -- Localized spell name for Maelstrom Weapon
+		filter = "HELPFUL", -- Optional filter to pass to UnitAura, defaults to "HELPFUL"
+	}
+	for i = 1, 5 do
+		self.AuraStack[i] = CreateTexture(nil, "OVERLAY")
+		self.AuraStack[i]:SetSize(20, 20)
+		if i == 1 then
+			self.AuraStack[i]:SetPoint("LEFT", self, "BOTTOMLEFT", 5, 0)
+		else
+			self.AuraStack[i]:SetPoint("LEFT", self.AuraStack[i-1], "RIGHT", 0, 0)
+		end
+	end
+------------------------------------------------------------------------
+	Notes:
+
+	Only supports one aura per frame.
+
+	Supports Override or PreUpdate/PostUpdate.
+
+	Does not support spell IDs, as the WoW API cannot look up a buff
+	directly by its ID and looping is not desirable here.
+
+	The buff to track can be changed dynamically (for example, when
+	the player's spec changes) by the layout.
+
+	The individual objects do not have to be textures. They can be
+	frames, fontstrings, or even basic tables, as long as they have
+	Show, Hide, SetAlpha, and IsObjectType methods.
+----------------------------------------------------------------------]]
+
+local _, ns = ...
+local oUF = ns.oUF or oUF
+assert(oUF, "AuraStack element requires oUF")
+
+local UnitBuff = UnitBuff
+
+local UpdateVisibility, Update, Path, ForceUpdate, Enable, Disable
+
+function UpdateVisibility(self, event)
+	local element = self.AuraStack
+
+	if UnitHasVehicleUI(self.unit) or (element.spec and element.spec ~= GetSpecialization()) then
+		element.__disabled = true
+		for i = 1, #element do
+			element[i]:Hide()
+		end
+		return self:UnregisterEvent("UNIT_AURA", Path)
+	end
+
+	element.__disabled = false
+	self:RegisterEvent("UNIT_AURA", Path)
+	return Path(self, "UpdateVisibility", self.unit)
+end
+
+function Update(self, event, unit)
+	if unit ~= self.unit then return end
+	local element = self.AuraStack
+	if element.__disabled or not element.aura then return end
+
+	local _, _, _, count = UnitAura(unit, element.aura, nil, element.filter or "HELPFUL")
+	count = count or 0
+
+	if count == element.__count then return end
+
+	if element.PreUpdate then
+		element:PreUpdate()
+	end
+
+	element.__count = count
+
+	for i = 1, #element do
+		local obj = element[i]
+		obj:SetShown(count > 0 and i <= element.__max)
+	end
+
+	if element.PostUpdate then
+		element:PostUpdate(count)
+	end
+end
+
+function Path(self, ...)
+	return (self.AuraStack.Override or Update)(self, ...)
+end
+
+function ForceUpdate(element)
+	return Path(element.__owner, "ForceUpdate", element.__owner.unit)
+end
+
+function Enable(self)
+	local element = self.AuraStack
+	if not element then return end
+
+	element.__name = "AuraStack"
+	element.__owner = self
+	element.ForceUpdate = ForceUpdate
+
+	if not element.__max then
+		element.__max = #element
+	end
+
+	for i = 1, #element do
+		local obj = element[i]
+		obj.__owner = self
+		if obj:IsObjectType("Texture") and not obj:GetTexture() then
+			obj:SetTexture([[Interface\ComboFrame\ComboPoint]])
+			obj:SetTexCoord(0, 0.375, 0, 1)
+		end
+	end
+
+	self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED", UpdateVisibility)
+	self:RegisterEvent("UNIT_ENTERING_VEHICLE", UpdateVisibility)
+	self:RegisterEvent("UNIT_EXITED_VEHICLE", UpdateVisibility)
+
+	UpdateVisibility(self, "Enable")
+
+	return true
+end
+
+function Disable(self)
+	local element = self.AuraStack
+	if not element then return end
+
+	self:UnregisterEvent("UNIT_AURA", Path)
+	self:UnregisterEvent("UNIT_ENTERING_VEHICLE", UpdateVisibility)
+	self:UnregisterEvent("UNIT_EXITED_VEHICLE", UpdateVisibility)
+
+	for i = 1, #element do
+		element[i]:Hide()
+	end
+	if element.SetShown then
+		element:SetShown(false)
+	end
+end
+
+oUF:AddElement("AuraStack", Path, Enable, Disable)
+
diff --git a/Elements/BurningEmbers.lua b/Elements/BurningEmbers.lua
index 395dfe2..9c5de26 100644
--- a/Elements/BurningEmbers.lua
+++ b/Elements/BurningEmbers.lua
@@ -1,12 +1,7 @@
 --[[--------------------------------------------------------------------
-	oUF_Phanx
-	Fully-featured PVE-oriented layout for oUF.
-	Copyright (c) 2008-2015 Phanx <addons@phanx.net>. All rights reserved.
-	http://www.wowinterface.com/downloads/info13993-oUF_Phanx.html
-	http://www.curse.com/addons/wow/ouf-phanx
-	https://github.com/Phanx/oUF_Phanx
-------------------------------------------------------------------------
+	oUF_Phanx/Elements/BurningEmbers.lua
 	Element to display burning embers on oUF frames.
+	Copyright (c) 2008-2015 Phanx <addons@phanx.net>. All rights reserved.

 	You may embed this module in your own layout, but please do not
 	distribute it as a standalone plugin.
diff --git a/Elements/DemonicFury.lua b/Elements/DemonicFury.lua
index ef8c32a..9bf39a4 100644
--- a/Elements/DemonicFury.lua
+++ b/Elements/DemonicFury.lua
@@ -1,12 +1,7 @@
 --[[--------------------------------------------------------------------
-	oUF_Phanx
-	Fully-featured PVE-oriented layout for oUF.
-	Copyright (c) 2008-2015 Phanx <addons@phanx.net>. All rights reserved.
-	http://www.wowinterface.com/downloads/info13993-oUF_Phanx.html
-	http://www.curse.com/addons/wow/ouf-phanx
-	https://github.com/Phanx/oUF_Phanx
-------------------------------------------------------------------------
+	oUF_Phanx/Elements/DemonicFury.lua
 	Element to display demonic fury on oUF frames.
+	Copyright (c) 2008-2015 Phanx <addons@phanx.net>. All rights reserved.

 	You may embed this module in your own layout, but please do not
 	distribute it as a standalone plugin.
diff --git a/Elements/DispelIcon.lua b/Elements/DispelIcon.lua
new file mode 100644
index 0000000..b6271e0
--- /dev/null
+++ b/Elements/DispelIcon.lua
@@ -0,0 +1,87 @@
+--[[-----------------------------------------------------------------------
+	oUF: Stardust - a layout for the oUF framework
+	Copyright (c) 2016 Andrew Mordecai <armordecai@protonmail.ch>
+	This code is released under the zlib license; see LICENSE for details.
+---------------------------------------------------------------------------
+	This file provides a "DispelIcon" element, which may be used in any
+	oUF layout and does not depend on any other files in this addon.
+
+	self.DispelIcon = self:CreateTexture(nil, "OVERLAY")
+	self.DispelIcon:SetPoint("CENTER")
+	self.DispelIcon:SetSize(16, 16)
+
+	To use Blizzard's graphical icons for each debuff type, set
+	.showIcons = true on the element. Otherwise, the element's existing
+	texture will be colored according to the debuff type.
+-------------------------------------------------------------------------]]
+
+local _, ns = ...
+local oUF = ns.oUF or oUF
+assert(oUF, "DispelIcon element requires oUF")
+
+function Update(self, event, unit)
+	local element = self.DispelIcon
+	if element.PreUpdate then
+		element:PreUpdate()
+	end
+
+	local _, _, _, _, dispelType = UnitDebuff(unit, 1, "RAID")
+	if dispelType == "" then dispelType = nil end
+
+	local color, r, g, b = dispelType and DebuffTypeColor[dispelType]
+	if element.showIcons then
+		if dispelType then
+			element:SetTexture("Interface\\RAIDFRAME\\Raid-Icon-Debuff"..dispelType)
+			element:SetVertexColor(1, 1, 1)
+			element:Show()
+		else
+			element:SetTexture("")
+			element:Hide()
+		end
+	else
+		if dispelType then
+			r, g, b = color.r, color.g, color.b
+			element:SetVertexColor(r, g, b)
+			element:Show()
+		else
+			element:SetVertexColor(1, 1, 1)
+			element:Hide()
+		end
+	end
+
+	if element.PostUpdate then
+		element:PostUpdate(dispelType, r, g, b)
+	end
+end
+
+function Path(self, ...)
+	return (self.DispelIcon.Override or Update)(self, ...)
+end
+
+function ForceUpdate(element)
+	return Path(element.__owner, "ForceUpdate", element.__owner.unit)
+end
+
+function Enable(self, unit)
+	local element = self.DispelIcon
+	if not element then return end
+
+	element.__owner = self
+	element.ForceUpdate = ForceUpdate
+
+	self:RegisterEvent("UNIT_AURA", Path)
+
+	return true
+end
+
+function Disable(self)
+	local element = self.DispelIcon
+	if not element then return end
+
+	self:UnregisterEvent("UNIT_AURA", Path)
+
+	element:Hide()
+end
+
+oUF:AddElement("DispelIcon", Path, Enable, Disable)
+
diff --git a/Fonts/LatoBold.ttf b/Fonts/LatoBold.ttf
new file mode 100644
index 0000000..9aa2d0c
Binary files /dev/null and b/Fonts/LatoBold.ttf differ
diff --git a/Functions.lua b/Functions.lua
index 6ffe6e6..aff1d5b 100644
--- a/Functions.lua
+++ b/Functions.lua
@@ -5,21 +5,21 @@
 -------------------------------------------------------------------------]]

 local _, Stardust = ...
+local config = Stardust.config

----------------------------
--- General utility functions
----------------------------
+local LibSharedMedia = LibStub("LibSharedMedia-3.0")

 local function abbrev(n)
-	if n > 1000000000 then
+	local v = abs(n)
+	if v > 1000000000 then
 		return format("%.0fb", n / 1000000000)
-	elseif n > 10000000 then
+	elseif v > 10000000 then
 		return format("%.0fm", n / 1000000)
-	elseif n > 1000000 then
+	elseif v > 1000000 then
 		return format("%.1fm", n / 1000000)
-	elseif n > 10000 then
+	elseif v > 10000 then
 		return format("%.0fk", n / 1000)
-	elseif n > 1000 then
+	elseif v > 1000 then
 		return format("%.1fk", n / 1000)
 	else
 		return n
@@ -27,24 +27,56 @@ local function abbrev(n)
 end

 ---------------------------
+-- Frames
+---------------------------
+
+function Stardust.OnEnter(self)
+	self.isMouseOver = true
+	UnitFrame_OnEnter(self)
+	self:UpdateAllElements("OnEnter")
+end
+
+function Stardust.OnLeave(self)
+	self.isMouseOver = false
+	UnitFrame_OnLeave(self)
+	self:UpdateAllElements("OnLeave")
+end
+
+---------------------------
 -- Health
 ---------------------------

-function Stardust.PostUpdateHealth(self, unit, cur, max)
-	self:SetValue(max - cur)
-	self.value:SetText(cur < max and abbrev(-max + cur) or "")
-	self.bg:SetVertexColor(0.15, 0.15, 0.15)
+function Stardust.PostUpdateHealth(element, unit, cur, max)
+	element:SetValue(max - cur)
+	element.value:SetText(element.__owner.isMouseOver and abbrev(cur) or cur < max and abbrev(-max + cur) or "")
 end

 ---------------------------
--- Power
--- DruidMana
--- DemonicFury
+-- Power / DruidMana / DemonicFury
 ---------------------------

-function Stardust.PostUpdatePower(self, unit, cur, max)
-	self:SetShown(max > 0)
-	self.value:SetText(abbrev(cur))
+local UnitIsDead = UnitIsDead
+
+function Stardust.PostUpdatePower(element, unit, cur, max)
+	if UnitIsDead(unit) then
+		local t = oUF.colors.disconnected
+		local r, g, b = t[1], t[2], t[3]
+		local mu = element.bg.multiplier or 1
+		element:SetStatusBarColor(r, g, b)
+		element.bg:SetVertexColor(r * mu, g * mu, b * mu)
+		element.value:SetText("")
+		element:SetValue(0)
+	else
+		element.value:SetText(max > 0 and element.__owner.isMouseOver and abbrev(cur) or "")
+	end
+end
+
+---------------------------
+-- Stagger
+---------------------------
+
+function Stardust.PostUpdateStagger(element, maxHealth, stagger, staggerPercent, r, g, b)
+	element.value:SetFormattedText("%d%%", staggerPercent)
 end

 ---------------------------
@@ -53,8 +85,7 @@ end

 function Stardust.ResizeClassIcons(element)
 	local COUNT = element.__max
-	local SPACING = 3
-	local widthTotal = ((Stardust.config.width - SPACING - SPACING) * 0.6) - (COUNT * SPACING)
+	local widthTotal = ((Stardust.config.width - 6) * 0.7) - (COUNT * 3)
 	local widthEach = floor(widthTotal / COUNT + 0.5)
 	for i = 1, COUNT do
 		element[i]:SetWidth(widthEach)
@@ -64,20 +95,35 @@ end
 function Stardust.PostUpdateClassIcons(element, cur, max, maxChanged, event)
 	if maxChanged then
 		Stardust.ResizeClassIcons(element)
+		if element.__owner.Stagger then
+			element.__owner.Stagger:SetPoint("TOPLEFT", element[max], 3, 0)
+		end
 	end
 end

 ---------------------------
--- CPoints
--- BurningEmbers
+-- CPoints / AuraStack / BurningEmbers
 ---------------------------

-function Stardust.PostUpdateCPoints(element, cur)
-	-- The number of combo points isn't dynamic, so they only need to be resized once per frame.
-	element.PostUpdate = nil
+function Stardust.PreUpdateCPoints(element)
+	element.PreUpdate = nil
 	Stardust.ResizeClassIcons(element)
 end

+function Stardust.PostUpdateCPoints(element, cur)
+	if cur == 0 or element.__disabled then return end
+
+	for i = 1, element.__max or #element do
+		local bar = element[i]
+		if i > cur then
+			bar:SetValue(0)
+			bar:Show()
+		else
+			bar:SetValue(1)
+		end
+	end
+end
+
 ---------------------------
 -- Runes
 ---------------------------
@@ -98,25 +144,41 @@ end
 -- Partly adapted from oUF_Phanx
 ---------------------------

-local function OnUpdateTotem(bar, elapsed)
-	local t = bar.duration - elapsed
-	if t > 0 then
-		bar.duration = t
-		bar:SetValue(t)
-	else
-		bar:SetValue(0)
+local OnUpdateTotem
+do
+	local ceil = ceil
+	local unpack = unpack
+	local SMOOTH_COLORS = oUF.colors.smooth
+	local ColorGradient = oUF.ColorGradient
+
+	function OnUpdateTotem(bar, elapsed)
+		local t = bar.remaining - elapsed
+		if t > 0 then
+			bar.remaining = t
+			bar:SetValue(t)
+			if bar.__owner.isMouseOver then
+				bar.value:SetText(ceil(t))
+				bar.value:SetTextColor(ColorGradient(t, bar.max, unpack(SMOOTH_COLORS)))
+			else
+				bar.value:SetText("")
+			end
+		else
+			bar:SetValue(0)
+			bar.value:SetText("")
+		end
 	end
 end

 function Stardust.PostUpdateTotem(element, index, _, name, start, duration)
 	local bar = element[index]
-	bar.duration, bar.max = duration, duration
+	bar.remaining, bar.max = start + duration - GetTime(), duration
 	if duration > 0 then
 		bar:SetMinMaxValues(0, duration)
 		bar:SetScript("OnUpdate", OnUpdateTotem)
 	else
 		bar:SetScript("OnUpdate", nil)
 		bar:SetValue(0)
+		bar.value:SetText("")
 	end
 end

@@ -130,43 +192,121 @@ end
 -- Status icons
 ---------------------------

-function Stardust.PostUpdateStatusIcon(self)
-	if not self:IsShown() then return end
-	local icons, x = self.__owner.StatusIcons, 2
+function Stardust.PostUpdateStatusIcon(element)
+	if not element:IsShown() then return end
+	local icons, x = element.__owner.StatusIcons, 2
 	for i = 1, #icons do
 		local icon = icons[i]
-		if icon == self then
+		if icon == element then
 			break
 		elseif icon:IsShown() and icon:GetTexture() then
 			x = x + icon:GetWidth()
 		end
 	end
-	self:SetPoint("LEFT", self.__owner.Health, "TOPLEFT", x + self.offsetX, 0 + self.offsetY)
+	element:SetPoint("LEFT", element.__owner.Health, "TOPLEFT", x + element.offsetX, 0 + element.offsetY)
 end

 local updatingCombatOrResting

-function Stardust.PostUpdateCombat(self)
+function Stardust.PostUpdateCombat(element)
 	if not updatingCombatOrResting then
 		updatingCombatOrResting = true
-		self.__owner.Resting:ForceUpdate()
+		element.__owner.Resting:ForceUpdate()
 		updatingCombatOrResting = nil
 	end
 end

-function Stardust.PostUpdateResting(self)
+function Stardust.PostUpdateResting(element)
 	if not updatingCombatOrResting then
 		updatingCombatOrResting = true
-		self.__owner.Combat:ForceUpdate()
+		element.__owner.Combat:ForceUpdate()
 		updatingCombatOrResting = nil
 	end
 end

 ---------------------------
--- Glow
+-- AFK text
+---------------------------
+
+function Stardust.PostUpdateAFK(element, isAFK)
+	local Power = element.__owner.Power
+	if Power and Power.value then
+		Power.value:SetShown(not isAFK)
+	end
+end
+
+---------------------------
+-- DispelIcon (redirected to frame.Shadows)
 ---------------------------

-local GLOW_SEGMENTS = {
+Stardust.DispelIconStub = {
+	SetTexture = nop,
+	SetVertexColor = nop,
+	Show = nop,
+	Hide = nop,
+}
+
+function Stardust.PostUpdateDispelIcon(element, dispelType, r, g, b)
+	local frame = element.__owner
+	if not frame.__shadowSize then
+		frame.__shadowSize = frame.Shadows[1]:GetWidth()
+	end
+	if dispelType then
+		frame:SetShadowColor(r, g, b)
+		frame:SetShadowSize(frame.__shadowSize * 2)
+	else
+		frame:SetShadowColor()
+		frame:SetShadowSize(frame.__shadowSize)
+	end
+end
+
+---------------------------
+-- Auras
+---------------------------
+
+function Stardust.PostCreateAuraIcon(element, button)
+	button.cd:SetDrawBling(false)
+	button.cd:SetDrawEdge(false)
+	button.cd:SetReverse(true)
+
+	button.icon:SetTexCoord(0.07, 0.93, 0.07, 0.93)
+
+	button.count:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE")
+	button.count:SetShadowOffset(0, 0)
+	button.count:SetTextColor(1, 1, 1)
+
+	button.backdrop = button:CreateTexture(nil, "BACKGROUND")
+	button.backdrop:SetPoint("BOTTOMLEFT", -1, -1)
+	button.backdrop:SetPoint("TOPRIGHT", 1, 1)
+	button.backdrop:SetTexture("Interface\\BUTTONS\\WHITE8X8")
+
+	Stardust.AddShadow(button, 6)
+end
+
+function Stardust.PostUpdateAuraIcon(element, unit, button, index, offset)
+	local _, _, _, _, dispelType, duration, timeLeft, _, isStealable, _, spellID, canApplyAura, isBossDebuff = UnitAura(unit, index, button.filter)
+
+	if timeLeft > 600 then
+		button.cd:Hide()
+	end
+
+	local color = button.isDebuff and dispelType and DebuffTypeColor[dispelType]
+	if color then
+		button.backdrop:SetVertexColor(color.r, color.g, color.b)
+	else
+		button.backdrop:SetVertexColor(0, 0, 0)
+	end
+end
+
+---------------------------
+-- Shadow
+---------------------------
+
+local SHADOW_SIZE = 3
+local SHADOW_COLOR = { 0, 0, 0 }
+
+local SHADOW_SEGMENTS = {
+	-- 1 point, 2 coordLeft, 3 coordRight, 4 coordTop, 5 coordBottom, 6 offsetMultiplierX, 7 offsetMultiplierY
 	{ "TOPLEFT", 0, 1/3, 0, 1/3, -1, 1 },
 	{ "TOPRIGHT", 2/3, 1, 0, 1/3, 1, 1 },
 	{ "BOTTOMRIGHT", 2/3, 1, 2/3, 1, 1, -1 },
@@ -177,63 +317,72 @@ local GLOW_SEGMENTS = {
 	{ "LEFT", 0, 1/3, 1/3, 2/3, -1, 0 },
 }

-local function SetGlowColor(self, r, g, b, a)
-	for i = 1, #self.Glow do
-		self.Glow[i]:SetVertexColor(r, g, b, a)
+local function SetShadowColor(self, r, g, b, a)
+	if not r or not g or not b then
+		r, g, b = unpack(SHADOW_COLOR)
+	end
+	for i = 1, #self.Shadows do
+		self.Shadows[i]:SetVertexColor(r, g, b, a or 1)
 	end
 end

-local function SetGlowSize(self, size, offset)
-	local Glow = self.Glow
-	for i = 1, #Glow do
-		Glow[i]:SetSize(size, size)
+local function SetShadowSize(self, size, offset)
+	if not size then size = SHADOW_SIZE end
+
+	local Shadows = self.Shadows
+	for i = 1, #Shadows do
+		Shadows[i]:SetSize(size, size)
 	end

 	local d = offset or floor(size * 2 / 3 + 0.5)
-	Glow[1]:SetPoint("TOPLEFT", -d, d)
-	Glow[2]:SetPoint("TOPRIGHT", d, d)
-	Glow[3]:SetPoint("BOTTOMRIGHT", d, -d)
-	Glow[4]:SetPoint("BOTTOMLEFT", -d, -d)
---[[
-	Glow[5]:SetShown(Glow[2]:GetLeft() > Glow[1]:GetRight())
-	Glow[6]:SetShown(Glow[2]:GetBottom() > Glow[3]:GetTop())
-	Glow[7]:SetShown(Glow[3]:GetLeft() > Glow[4]:GetLeft())
-	Glow[8]:SetShown(Glow[1]:GetBottom() > Glow[4]:GetTop())
-]]
-end
-
-function Stardust.CreateGlow(self)
-	local Glow = {}
-	for i = 1, #GLOW_SEGMENTS do
-		local seg = GLOW_SEGMENTS[i]
+	Shadows[1]:SetPoint("TOPLEFT", -d, d)
+	Shadows[2]:SetPoint("TOPRIGHT", d, d)
+	Shadows[3]:SetPoint("BOTTOMRIGHT", d, -d)
+	Shadows[4]:SetPoint("BOTTOMLEFT", -d, -d)
+
+	local showH = self:GetWidth() > (2 * (size - d))
+	local showV = self:GetHeight() > (2 * (size - d))
+	Shadows[5]:SetShown(showH)
+	Shadows[6]:SetShown(showV)
+	Shadows[7]:SetShown(showH)
+	Shadows[8]:SetShown(showV)
+end
+
+function Stardust.AddShadow(self, size, r, g, b, a)
+	local Shadows = {}
+	for i = 1, #SHADOW_SEGMENTS do
+		local seg = SHADOW_SEGMENTS[i]
 		local tex = self:CreateTexture(nil, "BACKGROUND")
-		tex:SetTexture("Interface\\AddOns\\oUF_Stardust\\textures\\glow")
+		tex:SetTexture("Interface\\AddOns\\oUF_Stardust\\Textures\\glow")
 		tex:SetTexCoord(seg[2], seg[3], seg[4], seg[5])
 		tex:SetVertexColor(0, 0, 0)
-		Glow[i] = tex
+		Shadows[i] = tex
 	end

 	-- TOP
-	Glow[5]:SetPoint("LEFT", Glow[1], "RIGHT")
-	Glow[5]:SetPoint("RIGHT", Glow[2], "LEFT")
+	Shadows[5]:SetPoint("LEFT", Shadows[1], "RIGHT")
+	Shadows[5]:SetPoint("RIGHT", Shadows[2], "LEFT")

 	-- RIGHT
-	Glow[6]:SetPoint("TOP", Glow[2], "BOTTOM")
-	Glow[6]:SetPoint("BOTTOM", Glow[3], "TOP")
+	Shadows[6]:SetPoint("TOP", Shadows[2], "BOTTOM")
+	Shadows[6]:SetPoint("BOTTOM", Shadows[3], "TOP")

 	-- BOTTOM
-	Glow[7]:SetPoint("LEFT", Glow[4], "RIGHT")
-	Glow[7]:SetPoint("RIGHT", Glow[3], "LEFT")
+	Shadows[7]:SetPoint("LEFT", Shadows[4], "RIGHT")
+	Shadows[7]:SetPoint("RIGHT", Shadows[3], "LEFT")

 	-- LEFT
-	Glow[8]:SetPoint("TOP", Glow[1], "BOTTOM")
-	Glow[8]:SetPoint("BOTTOM", Glow[4], "TOP")
+	Shadows[8]:SetPoint("TOP", Shadows[1], "BOTTOM")
+	Shadows[8]:SetPoint("BOTTOM", Shadows[4], "TOP")
+
+	self.Shadows = Shadows
+	self.SetBackdropBorderColor = SetShadowColor
+	self.SetShadowColor = SetShadowColor
+	self.SetShadowSize = SetShadowSize

-	self.Glow = Glow
-	self.SetBackdropBorderColor = SetGlowColor
-	self.SetGlowColor = SetGlowColor
-	self.SetGlowSize = SetGlowSize
+	self:SetShadowColor(r, g, b, a)
+	self:SetShadowSize(size)

-	return Glow
+	return Shadows
 end

diff --git a/Layout.lua b/Layout.lua
index 7f7828a..001e248 100644
--- a/Layout.lua
+++ b/Layout.lua
@@ -6,7 +6,7 @@

 local _, Stardust = ...
 local config = Stardust.config
-local colors = Stardust.colors
+local colors = oUF.colors

 local _, class = UnitClass("player")
 local LibSharedMedia = LibStub("LibSharedMedia-3.0")
@@ -15,15 +15,17 @@ local LibSharedMedia = LibStub("LibSharedMedia-3.0")
 -- Widget creation
 ---------------------------

-local function NewStatusBar(parent, textFont, textSize, noBG, noBackdrop)
+local function NewStatusBar(parent, textSize, noBG, noBorder, noShadow)
 	local TEXTURE = LibSharedMedia:Fetch("statusbar", config.barTexture)

 	local bar = CreateFrame("StatusBar", nil, parent)
 	bar:SetStatusBarTexture(TEXTURE)

-	if textFont and textSize then
-		bar.value = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
-		bar.value:SetFont(LibSharedMedia:Fetch("font", config.textFont), textSize)
+	if textSize then
+		bar.value = bar:CreateFontString(nil, "OVERLAY")
+		bar.value:SetFont(LibSharedMedia:Fetch("font", config.numberFont), textSize, "OUTLINE")
+		bar.value:SetShadowOffset(0, 0)
+		bar.value:SetTextColor(1, 1, 1)
 		bar.value:SetPoint("CENTER")
 	end

@@ -34,34 +36,31 @@ local function NewStatusBar(parent, textFont, textSize, noBG, noBackdrop)
 		bar.bg.multiplier = config.bgMultiplier
 	end

-	if not noBackdrop then
+	if not noBorder then
 		bar.backdrop = bar:CreateTexture(nil, "BACKGROUND")
 		bar.backdrop:SetPoint("BOTTOMLEFT", -1, -1)
 		bar.backdrop:SetPoint("TOPRIGHT", 1, 1)
 		bar.backdrop:SetTexture(0, 0, 0)
 	end

+	if not noShadow then
+		Stardust.AddShadow(bar, 3)
+	end
+
 	return bar
 end

-local function NewBarGroup(parent, count, width, height)
-	local group = { __max = count }
-
-	local color = Stardust.colors.class[class]
-	local r, g, b = color[1], color[2], color[3]
+local function NewBarGroup(parent, count, width, height, textSize)
+	local group = {
+		__max = count
+	}

 	for i = 1, count do
-		local bar = NewStatusBar(parent)
+		local bar = NewStatusBar(parent, textSize)
 		bar:SetSize(width, height)
 		bar:SetMinMaxValues(0, 1)
 		bar:SetValue(1)

-		if not noColor then
-			local mu = bar.bg.multiplier
-			bar:SetStatusBarColor(r, g, b)
-			bar.bg:SetVertexColor(r * mu, g * mu, b * mu)
-		end
-
 		-- ClassIcons expects texture objects
 		bar.SetVertexColor = bar.SetStatusBarColor

@@ -97,178 +96,284 @@ end
 local function ApplyStyle(self, unit, isSingle)
 	unit = unit:gsub("%d+", "")

-	local uconfig = config.units[unit]
+	local unitConfig = config.units[unit]
+	local classColor = oUF.colors.class[class]
+	local classR, classG, classB = classColor[1], classColor[2], classColor[3]

 	---------------------------
 	-- Frame
 	---------------------------

-	self:SetScript("OnEnter", UnitFrame_OnEnter)
-	self:SetScript("OnLeave", UnitFrame_OnLeave)
+	self:SetScript("OnEnter", Stardust.OnEnter)
+	self:SetScript("OnLeave", Stardust.OnLeave)

 	self:RegisterForClicks("AnyUp")

 	if (isSingle) then
-		self:SetSize(config.width * (uconfig and uconfig.width or 1), config.height * (uconfig and uconfig.height or 1))
+		self:SetSize(config.width * (unitConfig and unitConfig.width or 1), config.height * (unitConfig and unitConfig.height or 1))
 	end

-	---------------------------
-	-- Outer glow
-	---------------------------
-
-	self.Glow = Stardust.CreateGlow(self)
+	Stardust.AddShadow(self, 6)

 	---------------------------
 	-- Health bar
 	---------------------------

-	local Health = NewStatusBar(self, config.numberFont, 25)
+	local Health = NewStatusBar(self, config.numberLarge)
 	Health:SetPoint("BOTTOMLEFT")
 	Health:SetPoint("TOPRIGHT")
 	Health:SetReverseFill(true)

+	Health:SetStatusBarColor(0.3, 0.3, 0.3)
+	Health.bg:SetVertexColor(0.15, 0.15, 0.15)
+
 	Health.value:ClearAllPoints()
-	Health.value:SetPoint("RIGHT", Health, "TOPRIGHT", 0, 3)
+	Health.value:SetPoint("BOTTOMRIGHT", 0, 3 + config.powerHeight + 2)

-	Health.colorClass = true
-	Health.colorDisconnected = true
-	Health.colorReaction = true
-	Health.colorTapping = true
 	Health.frequentUpdates = true
+
 	Health.PostUpdate = Stardust.PostUpdateHealth
+
+	self:SmoothBar(Health)
 	self.Health = Health

 	---------------------------
+	-- Name text
+	---------------------------
+
+	local Name = self.Health:CreateFontString(nil, "OVERLAY")
+	Name:SetPoint("BOTTOMLEFT", 2, 3 + config.powerHeight + 2)
+	Name:SetPoint("RIGHT", self.Health.value, "LEFT")
+	Name:SetJustifyH("LEFT")
+	Name:SetFont(LibSharedMedia:Fetch("font", config.textFont), config.textSize, "OUTLINE")
+	Name:SetShadowOffset(0, 0)
+	Name:SetTextColor(1, 1, 1)
+	self:Tag(Name, "[unitcolor][name]")
+	self.Name = Name
+
+	---------------------------
 	-- Power bar
 	---------------------------

-	local Power = NewStatusBar(Health, config.numberFont, 15)
+	local Power = NewStatusBar(Health, config.numberSmall)
 	Power:SetPoint("BOTTOMLEFT", 3, 3)
 	Power:SetPoint("BOTTOMRIGHT", -3, 3)
 	Power:SetHeight(config.powerHeight)

+	Power.colorClass = true
 	Power.colorDisconnected = true
-	Power.colorPower = true
+	Power.colorReaction = true
+	Power.colorTapping = true
 	Power.frequentUpdates = true
+
 	Power.PostUpdate = Stardust.PostUpdatePower
+
+	self:SmoothBar(Power)
 	self.Power = Power

+	Stardust.AddShadow(Power, 3)
+
 	---------------------------
-	-- Secondary resources
+	-- AFK text
 	---------------------------

-	if class == "WARLOCK" then
-		local BurningEmbers = NewBarGroup(self.Health, 4, 30, config.powerHeight)
-		BurningEmbers[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
-		BurningEmbers.PostUpdate = Stardust.PostUpdateCPoints
-		self.BurningEmbers = BurningEmbers
+	if unit == "player" or unit == "party" then
+		local AFK = self:CreateFontString(nil, "OVERLAY")
+		AFK:SetPoint("CENTER", self.Power)
+		AFK:SetFont(LibSharedMedia:Fetch("font", config.numberFont), config.numberSmall, "OUTLINE")
+		AFK:SetShadowOffset(0, 0)
+		AFK:SetTextColor(1, 1, 1)
+		AFK.PostUpdate = Stardust.PostUpdateAFK
+		self.AFK = AFK
 	end

-	if class == "PALADIN" or class == "PRIEST" or class == "WARLOCK" then
-		local ClassIcons = NewBarGroup(self.Health, 6, 30, config.powerHeight)
-		ClassIcons[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
-		ClassIcons.PostUpdate = Stardust.PostUpdateClassIcons
-		self.ClassIcons = ClassIcons
-	end
+	---------------------------
+	-- Secondary resources
+	---------------------------

-	if class == "DRUID" or class == "ROGUE" then
-		local CPoints = NewBarGroup(self.Health, 5, 30, config.powerHeight)
-		CPoints[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
-		CPoints.PostUpdate = Stardust.PostUpdateCPoints
-		self.CPoints = CPoints
-	end
+	if unit == "player" then

-	if class == "DEATHKNIGHT" then
-		local Runes = NewBarGroup(self.Health, 6, 30, config.powerHeight)
-		Runes[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
-		Runes.PostUpdateRune = Stardust.PostUpdateRune
-		Runes.PostUpdateRuneType = Stardust.PostUpdateRuneType
-		self.Runes = Runes
-	end
+		---------------------------
+		-- Runes @ TOPLEFT
+		---------------------------
+
+		if class == "DEATHKNIGHT" then
+			local Runes = NewBarGroup(self.Health, 6, 30, config.powerHeight)
+			Runes[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
+			Runes.PostUpdateRune = Stardust.PostUpdateRune
+			Runes.PostUpdateRuneType = Stardust.PostUpdateRuneType
+			self.Runes = Runes
+		end
+
+		---------------------------
+		-- Combo Points @ TOPLEFT
+		---------------------------
+
+		if class == "DRUID" or class == "ROGUE" then
+			local CPoints = NewBarGroup(self.Health, 5, 30, config.powerHeight)
+			CPoints[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
+			CPoints.PreUpdate = Stardust.PreUpdateCPoints
+			self.CPoints = CPoints
+
+			for i = 1, #CPoints do
+				local bar = CPoints[i]
+				local mu = bar.bg.multiplier
+				bar:SetStatusBarColor(classR, classG, classB)
+				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)
+			end
+		end
+
+		---------------------------
+		-- Totems @ TOPLEFT
+		---------------------------

-	if class == "DRUID" or class == "SHAMAN" then
-	    -- TODO: This can also show death knight ghouls.
-	    -- How do these work? Are they important?
-	    -- How to show them at the same time as runes?
-		local Totems = NewBarGroup(self.Health, 4, 30, config.powerHeight)
-		Totems[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
 		if class == "SHAMAN" then
-			local mu = config.bgMultiplier
+			local Totems = NewBarGroup(self.Health, 4, 30, config.powerHeight, config.numberSmall)
+			Totems[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
+			Totems.PreUpdate = Stardust.PreUpdateTotems
+			Totems.PostUpdate = Stardust.PostUpdateTotem
+			self.Totems = Totems
+
 			for i = 1, 4 do
-				local totem = Totems[i]
-				totem:EnableMouse(true)
+				local bar = Totems[i]
+				bar:EnableMouse(true)
+				bar:SetHitRectInsets(0, 0, -6, -3)
+				bar.__owner = self -- oUF sets it on Totems, but not Totems[i]

-				local color = Stardust.colors.totems[i]
+				local color = oUF.colors.totems[i]
 				local r, g, b = color[1], color[2], color[3]
-				totem:SetStatusBarColor(r, g, b)
-				totem.bg:SetVertexColor(r * mu, b * mu, g * mu)
+				local mu = bar.bg.multiplier
+				bar:SetStatusBarColor(r, g, b)
+				bar.bg:SetVertexColor(r * mu, g * mu, b * mu)

-				local value = totem:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
-				value:SetFont(LibSharedMedia:Fetch("font", config.numberFont), 15)
-				value:SetPoint("CENTER")
+				bar.value:SetPoint("CENTER", 0, 3)
 			end
 		end
-		Totems.PreUpdate = Stardust.PreUpdateTotems
-		self.Totems = Totems
-	end

-	-- TODO: burning embers
+		---------------------------
+		-- Chi / Holy Power / Shadow Orbs / Soul Shards @ TOPLEFT
+		---------------------------

-	---------------------------
-	-- Druid mana
-	-- Warlock demonic fury
-	---------------------------
+		if class == "MONK" or class == "PALADIN" or class == "PRIEST" or class == "WARLOCK" then
+			local ClassIcons = NewBarGroup(self.Health, 6, 30, config.powerHeight)
+			ClassIcons[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
+			ClassIcons.PostUpdate = Stardust.PostUpdateClassIcons
+			self.ClassIcons = ClassIcons
+		end

-	if class == "DRUID" or class == "WARLOCK" then
-		local bar = NewStatusBar(Health)
-		bar:SetPoint("TOPLEFT", 3, -3)
-		bar:SetPoint("TOPRIGHT", -3, -3)
-		bar:SetHeight(config.powerHeight)
-		if class == "DRUID" then
-			bar:SetPoint("TOPLEFT", self.Totems[4], 3, 0)
-			bar.colorPower = true
-			bar.PostUpdate = PostUpdatePower
-			self.DruidMana = bar
-		else
-			bar.colorPower = true
-			bar.PostUpdate = PostUpdatePower
-			self.DemonicFury = bar
+		---------------------------
+		-- Druid Mana @ TOP / Stagger @ TOPRIGHT / Demonic Fury @ TOP
+		---------------------------
+
+		if class == "DRUID" or class == "MONK" or class == "WARLOCK" then
+			local bar = NewStatusBar(Health, config.numberSmall)
+			bar:SetPoint("TOPLEFT", 3, -3)
+			bar:SetPoint("TOPRIGHT", -3, -3)
+			bar:SetHeight(config.powerHeight)
+			self:SmoothBar(bar)
+			if class == "DRUID" then
+				bar.colorPower = true
+				bar.PostUpdate = PostUpdatePower
+				self.DruidMana = bar
+			elseif class == "MONK" then
+				bar:SetPoint("TOPLEFT", self.ClassIcons[4], 3, 0)
+				bar.PostUpdate = PostUpdateStagger
+				self.Stagger = bar
+			elseif class == "WARLOCK" then
+				bar.colorPower = true
+				bar.PostUpdate = PostUpdatePower
+				self.DemonicFury = bar
+			end
 		end
+
+		---------------------------
+		-- Maelstrom Weapon @ TOPRIGHT
+		---------------------------
+
+		if class == "SHAMAN" then
+			local AuraStack = NewBarGroup(self.Health, 5, config.powerHeight * 2, config.powerHeight)
+			AuraStack[1]:SetPoint("TOPRIGHT", self.Health, -3, -3)
+			AuraStack.PostUpdate = Stardust.PostUpdateCPoints
+			self.AuraStack = AuraStack
+
+			AuraStack.aura = GetSpellInfo(51530) -- Maelstrom Weapon
+			AuraStack.spec = 2 -- Enhancement
+
+			for i = 1, #AuraStack do
+				local bar = AuraStack[i]
+
+				local mu = bar.bg.multiplier
+				bar:SetStatusBarColor(classR, classG, classB)
+				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)
+
+				if i > 1 then
+					bar:ClearAllPoints()
+					bar:SetPoint("RIGHT", AuraStack[i-1], "LEFT", -3, 0)
+				end
+			end
+		end
+
+		---------------------------
+		-- Burning Embers @ TOPLEFT
+		---------------------------
+
+		if class == "WARLOCK" then
+			local BurningEmbers = NewBarGroup(self.Health, 4, 30, config.powerHeight)
+			BurningEmbers[1]:SetPoint("TOPLEFT", self.Health, 3, -3)
+			BurningEmbers.PreUpdate = Stardust.PreUpdateCPoints
+			self.BurningEmbers = BurningEmbers
+
+			for i = 1, #BurningEmbers do
+				local bar = BurningEmbers[i]
+				local mu = bar.bg.multiplier
+				bar:SetStatusBarColor(classR, classG, classB)
+				bar.bg:SetVertexColor(classR * mu, classG * mu, classB * mu)
+				self:SmoothBar(bar)
+			end
+		end
+
 	end

 	---------------------------
-	-- TODO
+	-- Alternate Power Bar
+	-- TODO: layout
 	---------------------------

-	-- AltPowerBar
-	-- EclipseBar
-	-- HealPrediction
-	-- Stagger
+	local AltPowerBar = NewStatusBar(self.Health, config.numberSmall)
+	AltPowerBar:SetPoint("TOPLEFT", self.Power, "BOTTOMLEFT", 0, -3)
+	AltPowerBar:SetPoint("TOPRIGHT", self.Power, "BOTTOMRIGHT", 0, 3)
+	AltPowerBar:SetHeight(config.powerHeight)
+	AltPowerBar:EnableMouse(true)

-	-- Castbar
+	AltPowerBar.colorTexture = true
+	AltPowerBar.PostUpdate = Stardust.PostUpdatePower

-	-- Auras
+	self:SmoothBar(AltPowerBar)
+	self.AltPowerBar = AltPowerBar
+
+	Stardust.AddShadow(AltPowerBar, 3)

 	---------------------------
-	-- Name text
+	-- TODO
 	---------------------------

-	local Name = self:CreateFontString(nil, "OVERLAY", "GameFontNormal")
-	Name:SetPoint("BOTTOMLEFT", Power, "TOPLEFT", 0, 3)
-	Name:SetFont(LibSharedMedia:Fetch("font", config.valueFont), 14)
-	self:Tag(Name, "[unitcolor][name]")
-	self.Name = Name
+	-- EclipseBar
+	-- HealPrediction
+
+	-- Castbar

 	---------------------------
 	-- Group status icons
 	---------------------------

-	AddStatusIcon(self, "LFDRole", 24)
-	AddStatusIcon(self, "RaidRole") -- maintank, mainassist
-	AddStatusIcon(self, "Leader")
-	AddStatusIcon(self, "Assistant")
-	AddStatusIcon(self, "MasterLooter", nil, 0, 1)
-	AddStatusIcon(self, "PvP", 32, 0, -5) -- TODO: move it somewhere else?
+	if not unit:match("pet") and not unit:match(".+target") then
+		AddStatusIcon(self, "LFDRole", 24)
+		AddStatusIcon(self, "RaidRole") -- maintank, mainassist
+		AddStatusIcon(self, "Leader")
+		AddStatusIcon(self, "Assistant")
+		AddStatusIcon(self, "MasterLooter", nil, 0, 1)
+		AddStatusIcon(self, "PvP", 32, 0, -5) -- TODO: move it somewhere else?
+	end

 	---------------------------
 	-- Combat icon
@@ -276,13 +381,13 @@ local function ApplyStyle(self, unit, isSingle)

 	if unit == "player" then
 		local Combat = self.Health:CreateTexture(nil, "OVERLAY")
-		Combat:SetSize(16, 16)
+		Combat:SetSize(32, 32)
 		Combat:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0)
 		Combat.PostUpdate = Stardust.PostUpdateCombat
 		self.Combat = Combat

 		local Resting = self.Health:CreateTexture(nil, "OVERLAY")
-		Resting:SetSize(16, 16)
+		Resting:SetSize(32, 32)
 		Resting:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0)
 		Resting.PostUpdate = Stardust.PostUpdateResting
 		self.Resting = Resting
@@ -292,7 +397,7 @@ local function ApplyStyle(self, unit, isSingle)
 	-- Phasing icon
 	---------------------------

-	if not unit:match(".+target$") and not unit:match(".+pet") then
+	if not unit:match("pet") and not unit:match(".+target") then
 		local PhaseIcon = self.Health:CreateTexture(nil, "OVERLAY")
 		PhaseIcon:SetPoint("TOP")
 		PhaseIcon:SetPoint("BOTTOM")
@@ -312,8 +417,8 @@ local function ApplyStyle(self, unit, isSingle)

 	if unit == "target" then
 		local QuestIcon = self.Health:CreateTexture(nil, "OVERLAY")
-		QuestIcon:SetSize(16, 16)
-		QuestIcon:SetPoint("RIGHT", self.Health, "TOPRIGHT", -3, 0)
+		QuestIcon:SetSize(32, 32)
+		QuestIcon:SetPoint("CENTER", self.Health, "LEFT", -3, 0)
 		self.QuestIcon = QuestIcon
 	end

@@ -324,7 +429,7 @@ local function ApplyStyle(self, unit, isSingle)
 	if unit == "player" or unit == "target" or unit == "party" then
 		local RaidIcon = self.Health:CreateTexture(nil, "OVERLAY")
 		RaidIcon:SetSize(24, 24)
-		RaidIcon:SetPoint("CENTER", 0, 3) -- TODO
+		RaidIcon:SetPoint("CENTER", self.Health, "TOP", 0, 6) -- TODO
 		self.RaidIcon = RaidIcon
 	end

@@ -343,7 +448,7 @@ local function ApplyStyle(self, unit, isSingle)
 	-- Resurrection icon
 	---------------------------

-	if not unit:match(".+target$") and not unit:match("pet") then
+	if not unit:match("pet") and not unit:match(".+target") then
 		local ResurrectIcon = self.Health:CreateTexture(nil, "OVERLAY")
 		ResurrectIcon:SetSize(24, 24)
 		ResurrectIcon:SetPoint("CENTER", 0, 3) -- TODO
@@ -352,6 +457,7 @@ local function ApplyStyle(self, unit, isSingle)

 	---------------------------
 	-- Threat level icon
+	-- TODO: colorize and enlarge frame shadow for aggro
 	---------------------------

 	if unit == "target" or unit == "focus" then
@@ -362,28 +468,48 @@ local function ApplyStyle(self, unit, isSingle)
 	end

 	---------------------------
-	-- Range
+	-- DispelIcon (redirected to frame glow)
 	---------------------------

-	self.Range = {
-		insideAlpha = 1,
-		outsideAlpha = 0.5
-	}
+	if not unit:match(".+target$") then
+		local DispelIcon = setmetatable({}, { __index = Stardust.DispelIconStub })
+		DispelIcon.PostUpdate = Stardust.PostUpdateDispelIcon
+		self.DispelIcon = DispelIcon
+	end

 	---------------------------
-	-- oUF_SmoothUpdate plugin support
+	-- Auras
 	---------------------------

-	if self.SmoothBar then
-		self:SmoothBar(self.Health)
-		self:SmoothBar(self.Power)
-		if self.DemonicFury then
-			self:SmoothBar(self.DemonicFury)
-		end
-		if self.DruidMana then
-			self:SmoothBar(self.DruidMana)
-		end
+	if unit == "player" or unit == "target" then
+		local Auras = CreateFrame("Frame", nil, self)
+		Auras:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 9)
+		Auras:SetSize(config.width, 16)
+
+		Auras.gap = true
+		Auras['growth-x'] = "LEFT"
+		Auras['growth-y'] = "UP"
+		Auras.initialAnchor = "BOTTOMRIGHT"
+		Auras.showDebuffType = true
+		Auras.showStealableBuffs = true
+		Auras.size = 24
+		Auras.spacing = 6
+
+		Auras.CustomFilter = Stardust.AuraFilter
+		Auras.PostCreateIcon = Stardust.PostCreateAuraIcon
+		Auras.PostUpdateIcon = Stardust.PostUpdateAuraIcon
+
+		self.Auras = Auras
 	end
+
+	---------------------------
+	-- Range
+	---------------------------
+
+	self.Range = {
+		insideAlpha = 1,
+		outsideAlpha = 0.5
+	}
 end

 oUF:Factory(function(self)
diff --git a/Textures/statusbar4.tga b/Textures/statusbar4.tga
new file mode 100755
index 0000000..e2fc1fa
Binary files /dev/null and b/Textures/statusbar4.tga differ
diff --git a/oUF_Stardust.toc b/oUF_Stardust.toc
index 3cf61c6..7ddd773 100644
--- a/oUF_Stardust.toc
+++ b/oUF_Stardust.toc
@@ -1,11 +1,13 @@
 ## Interface: 60200
 ## Dependencies: oUF

+## Version: 6.2.4.0
 ## Title: oUF: Stardust
 ## Author: Akkorian
 ## X-Credits: Phanx, Qulight
 ## X-Email: armordecai@protonmail.ch
 ## X-License: zlib/libpng License
+## X-Website: https://gitlab.com/akkorian/ouf-stardust

 Libs/LibStub.lua
 Libs/CallbackHandler-1.0.lua
@@ -13,11 +15,17 @@ Libs/LibSharedMedia-3.0.lua

 Plugins/Smooth.lua

+Elements/AFK.lua
+Elements/AuraStack.lua
 Elements/BurningEmbers.lua
 Elements/DemonicFury.lua
+Elements/DispelIcon.lua

 Config.lua
+
+Auras.lua
 Functions.lua
 Tags.lua

 Layout.lua
+