diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index dd3890188ec49959214bada52c55503812e0ac83..ba61c4635769738bdf338362e3a66475a433f619 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -682,6 +682,7 @@ set(viewer_SOURCE_FILES
     llviewerwearable.cpp
     llviewerwindow.cpp
     llviewerwindowlistener.cpp
+	llvisualeffect.cpp
     llvlcomposition.cpp
     llvlmanager.cpp
     llvoavatar.cpp
@@ -1302,6 +1303,7 @@ set(viewer_HEADER_FILES
     llviewerwearable.h
     llviewerwindow.h
     llviewerwindowlistener.h
+	llvisualeffect.h
     llvlcomposition.h
     llvlmanager.h
     llvoavatar.h
diff --git a/indra/newview/llvisualeffect.cpp b/indra/newview/llvisualeffect.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..23c9065f9623ea08680ad9e362b5820313d8d48d
--- /dev/null
+++ b/indra/newview/llvisualeffect.cpp
@@ -0,0 +1,140 @@
+/**
+ *
+ * Copyright (c) 2021, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llvisualeffect.h"
+
+#include <boost/iterator/filter_iterator.hpp>
+#include <boost/range/iterator_range.hpp>
+
+// ============================================================================
+// LLTweenableValue class
+//
+
+template<>
+float LLTweenableValueLerp<float>::get()
+{
+	if (!m_CurValue)
+	{
+		float curFactor = (LLTimer::getElapsedSeconds() - m_StartTime) / m_Duration;
+		if (curFactor < 1.0)
+			return lerp(m_StartValue, m_EndValue, curFactor);
+		m_CurValue = m_EndValue;
+	}
+	return m_CurValue.get();
+}
+
+template<>
+LLColor3 LLTweenableValueLerp<LLColor3>::get()
+{
+	if (!m_CurValue)
+	{
+		float curFactor = (LLTimer::getElapsedSeconds() - m_StartTime) / m_Duration;
+		if (curFactor < 1.0)
+			return lerp(m_StartValue, m_EndValue, curFactor);
+		m_CurValue = m_EndValue;
+	}
+	return m_CurValue.get();
+}
+
+template<>
+LLVector4 LLTweenableValueLerp<LLVector4>::get()
+{
+	if (!m_CurValue)
+	{
+		float curFactor = (LLTimer::getElapsedSeconds() - m_StartTime) / m_Duration;
+		if (curFactor < 1.0)
+			return lerp(m_StartValue, m_EndValue, curFactor);
+		m_CurValue = m_EndValue;
+	}
+	return m_CurValue.get();
+}
+
+// ============================================================================
+// LLVfxManager class
+//
+
+LLVfxManager::LLVfxManager()
+{
+
+}
+
+bool LLVfxManager::addEffect(LLVisualEffect* pEffectInst)
+{
+	if (m_Effects.end() != m_Effects.find(pEffectInst))
+		return false;
+
+	m_Effects.insert(pEffectInst);
+
+	return true;
+}
+
+LLVisualEffect* LLVfxManager::getEffect(const LLUUID& idEffect) const
+{
+	auto itEffect = std::find_if(m_Effects.begin(), m_Effects.end(), [&idEffect](const LLVisualEffect* pEffect) { return pEffect->getId() == idEffect; });
+	return (m_Effects.end() != itEffect) ? *itEffect : nullptr;
+}
+
+LLVisualEffect* LLVfxManager::getEffect(EVisualEffect eCode) const
+{
+	// NOTE: returns the first found but there could be more
+	auto itEffect = std::find_if(m_Effects.begin(), m_Effects.end(), [eCode](const LLVisualEffect* pEffect) { return pEffect->getCode() == eCode; });
+	return (m_Effects.end() != itEffect) ? *itEffect : nullptr;
+}
+
+bool LLVfxManager::removeEffect(const LLUUID& idEffect)
+{
+	auto itEffect = std::find_if(m_Effects.begin(), m_Effects.end(), [&idEffect](const LLVisualEffect* pEffect) { return pEffect->getId() == idEffect; });
+	if (m_Effects.end() == itEffect)
+		return false;
+
+	delete *itEffect;
+	m_Effects.erase(itEffect);
+	return true;
+}
+
+void LLVfxManager::runEffect(EVisualEffect eCode)
+{
+	// *TODO-Catz: once we're done, check whether iterating over the entire list still has negliable impact
+	auto pred = [eCode](const LLVisualEffect* pEffect) { return pEffect->getCode() == eCode; };
+
+	auto beginEffect = boost::make_filter_iterator(pred, m_Effects.begin(), m_Effects.end()),
+	     endEffect = boost::make_filter_iterator(pred, m_Effects.end(), m_Effects.end());
+
+	auto effectRange = boost::make_iterator_range(beginEffect, endEffect);
+	for (LLVisualEffect* pEffect : effectRange)
+	{
+		pEffect->run();
+	}
+}
+
+void LLVfxManager::runEffect(EVisualEffectType eType)
+{
+	// *TODO-Catz: once we're done, check whether iterating over the entire list still has negliable impact
+	auto pred = [eType](const LLVisualEffect* pEffect) { return pEffect->getType() == eType;  };
+
+	auto beginEffect = boost::make_filter_iterator(pred, m_Effects.begin(), m_Effects.end()),
+		endEffect = boost::make_filter_iterator(pred, m_Effects.end(), m_Effects.end());
+
+	auto effectRange = boost::make_iterator_range(beginEffect, endEffect);
+	for (LLVisualEffect* pEffect : effectRange)
+	{
+		pEffect->run();
+	}
+}
+
+// ============================================================================
diff --git a/indra/newview/llvisualeffect.h b/indra/newview/llvisualeffect.h
new file mode 100644
index 0000000000000000000000000000000000000000..86f3ab56c7bad2ad1742a06981fc944321209935
--- /dev/null
+++ b/indra/newview/llvisualeffect.h
@@ -0,0 +1,153 @@
+/**
+ *
+ * Copyright (c) 2021, Kitty Barnett
+ *
+ * The source code in this file is provided to you under the terms of the
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ *
+ * By copying, modifying or distributing this software, you acknowledge that
+ * you have read and understood your obligations described above, and agree to
+ * abide by those obligations.
+ *
+ */
+
+#pragma once
+
+#include "llsingleton.h"
+
+// ============================================================================
+//
+//
+
+enum class EVisualEffect
+{
+	RlvOverlay,
+	RlvSphere,
+};
+
+enum class EVisualEffectType
+{
+	PostProcessShader,
+	Custom,
+};
+
+// ============================================================================
+//
+//
+
+class LLVisualEffect
+{
+public:
+	LLVisualEffect(LLUUID id, EVisualEffect eCode, EVisualEffectType eType)
+		: m_id(id), m_eCode(eCode), m_eType(eType)
+	{}
+	virtual ~LLVisualEffect() {}
+
+	EVisualEffect     getCode() const     { return m_eCode;}
+	const LLUUID&     getId() const       { return m_id;}
+	U32               getPriority() const { return m_nPriority; }
+	EVisualEffectType getType() const     { return m_eType;}
+
+	virtual void      run() = 0;
+
+	/*
+	 * Member variables
+	 */
+protected:
+	LLUUID            m_id;
+	EVisualEffect     m_eCode;
+	EVisualEffectType m_eType;
+	U32               m_nPriority;
+};
+
+// ============================================================================
+//
+//
+
+enum class LLTweenType
+{
+	None,
+	Lerp,
+};
+
+template<typename T>
+class LLTweenableValue
+{
+public:
+	LLTweenableValue(const T& defaultValue) : m_CurValue(defaultValue) {}
+	virtual ~LLTweenableValue() {}
+
+	virtual T    get() = 0;
+	virtual void start(const T& endValue, double duration) = 0;
+
+	T& operator =(const T& value) { m_CurValue = value; }
+
+	/*
+	 * Member variables
+	 */
+protected:
+	boost::optional<T> m_CurValue;
+};
+
+template<typename T>
+class LLTweenableValueLerp : public LLTweenableValue<T>
+{
+public:
+	LLTweenableValueLerp(const T& defaultValue) : LLTweenableValue(defaultValue) {}
+
+	T    get() override;
+	void start(const T& endValue, double duration) override
+	{
+		m_StartValue = get();
+		m_CurValue = boost::none;
+		m_EndValue = endValue;
+
+		m_StartTime = LLTimer::getElapsedSeconds();
+		m_Duration = duration;
+	}
+
+	/*
+	 * Member variables
+	 */
+protected:
+	double m_StartTime;
+	double m_Duration;
+	T      m_StartValue;
+	T      m_EndValue;
+};
+
+// ============================================================================
+//
+//
+
+class LLVfxManager : public LLSingleton<LLVfxManager>
+{
+	LLSINGLETON(LLVfxManager);
+protected:
+	~LLVfxManager() {}
+
+	/*
+	 * Member functions
+	 */
+public:
+	bool            addEffect(LLVisualEffect* pEffectInst);
+	LLVisualEffect* getEffect(const LLUUID& idEffect) const;
+	template<typename T> T* getEffect(const LLUUID& idEffect) const { return dynamic_cast<T*>(getEffect(idEffect)); }
+	LLVisualEffect* getEffect(EVisualEffect eCode) const;
+	template<typename T> T* getEffect(EVisualEffect eCode) const { return dynamic_cast<T*>(getEffect(eCode)); }
+	bool            removeEffect(const LLUUID& idEffect);
+	void            runEffect(EVisualEffect eCode);
+	void            runEffect(EVisualEffectType eType);
+protected:
+
+	/*
+	 * Member variables
+	 */
+protected:
+	std::set<LLVisualEffect*> m_Effects;
+};
+
+// ============================================================================