diff --git a/loader/include/Geode/ui/ColorPickPopup.hpp b/loader/include/Geode/ui/ColorPickPopup.hpp
new file mode 100644
index 00000000..49d3f17d
--- /dev/null
+++ b/loader/include/Geode/ui/ColorPickPopup.hpp
@@ -0,0 +1,55 @@
+#include "Popup.hpp"
+#include "InputNode.hpp"
+
+namespace geode {
+    class ColorPickPopupDelegate {
+    public:
+        virtual void updateColor(cocos2d::ccColor4B const& color) {}
+    };
+
+    class ColorPickPopup :
+        public Popup<cocos2d::ccColor4B const&, bool>,
+        public cocos2d::extension::ColorPickerDelegate,
+        public TextInputDelegate
+    {
+    protected:
+        cocos2d::ccColor4B m_color;
+        cocos2d::ccColor4B m_originalColor;
+        cocos2d::extension::CCControlColourPicker* m_picker;
+        Slider* m_opacitySlider = nullptr;
+        InputNode* m_rInput;
+        InputNode* m_gInput;
+        InputNode* m_bInput;
+        InputNode* m_hexInput;
+        InputNode* m_opacityInput = nullptr;
+        ColorPickPopupDelegate* m_delegate = nullptr;
+        cocos2d::CCSprite* m_newColorSpr;
+        CCMenuItemSpriteExtra* m_resetBtn;
+
+        static constexpr auto TAG_OPACITY_INPUT = 0;
+        static constexpr auto TAG_R_INPUT       = 1;
+        static constexpr auto TAG_G_INPUT       = 2;
+        static constexpr auto TAG_B_INPUT       = 3;
+        static constexpr auto TAG_HEX_INPUT     = 4;
+
+        bool setup(cocos2d::ccColor4B const& color, bool isRGBA) override;
+    
+        void onOpacitySlider(cocos2d::CCObject* sender);
+        void onReset(cocos2d::CCObject* sender);
+
+        void textChanged(CCTextInputNode* input) override;
+        void colorValueChanged(cocos2d::ccColor3B color) override;
+
+        void updateState(cocos2d::CCNode* except = nullptr);
+        
+        static ColorPickPopup* create(cocos2d::ccColor4B const& color, bool isRGBA);
+
+    public:
+        static ColorPickPopup* create(bool isRGBA);
+        static ColorPickPopup* create(cocos2d::ccColor3B const& color);
+        static ColorPickPopup* create(cocos2d::ccColor4B const& color);
+
+        void setColorTarget(cocos2d::CCSprite* spr);
+        void setDelegate(ColorPickPopupDelegate* delegate);
+    };
+}
diff --git a/loader/resources/hue-sliders.png b/loader/resources/hue-sliders.png
new file mode 100644
index 00000000..7c2b93bf
Binary files /dev/null and b/loader/resources/hue-sliders.png differ
diff --git a/loader/src/load/Setting.cpp b/loader/src/load/Setting.cpp
index 4944902d..4b722453 100644
--- a/loader/src/load/Setting.cpp
+++ b/loader/src/load/Setting.cpp
@@ -22,7 +22,8 @@ Result<std::shared_ptr<Setting>> Setting::parse(
         case hash("int"):    return IntSetting::parse(key, obj);
         case hash("float"):  return FloatSetting::parse(key, obj);
         case hash("string"): return StringSetting::parse(key, obj);
-        case hash("color"):  return ColorSetting::parse(key, obj);
+        case hash("rgb"): case hash("color"):
+            return ColorSetting::parse(key, obj);
         case hash("rgba"):   return ColorAlphaSetting::parse(key, obj);
         default: return Err(
             "Setting \"" + key + "\" has unknown type \"" + type + "\""
diff --git a/loader/src/ui/internal/settings/GeodeSettingNode.cpp b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
index 9dd5bc70..c9f165be 100644
--- a/loader/src/ui/internal/settings/GeodeSettingNode.cpp
+++ b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
@@ -98,6 +98,11 @@ void ColorSettingNode::valueChanged(bool updateText) {
     m_colorSpr->setColor(m_uncommittedValue);
 }
 
+void ColorSettingNode::updateColor(ccColor4B const& color) {
+    m_uncommittedValue = to3B(color);
+    this->valueChanged(true);
+}
+
 bool ColorSettingNode::setup(std::shared_ptr<ColorSetting> setting, float width) {
 	m_colorSpr = ColorChannelSprite::create();
 	m_colorSpr->setColor(m_uncommittedValue);
@@ -105,7 +110,10 @@ bool ColorSettingNode::setup(std::shared_ptr<ColorSetting> setting, float width)
 	
 	auto button = CCMenuItemSpriteExtra::create(
 		m_colorSpr, this, makeMenuSelector([this](CCObject*) {
-            ColorPickPopup<ColorSettingNode>::create(this)->show();
+            auto popup = ColorPickPopup::create(m_uncommittedValue);
+            popup->setDelegate(this);
+            popup->setColorTarget(m_colorSpr);
+            popup->show();
         })
 	);
     button->setPositionX(-10.f);
@@ -122,6 +130,11 @@ void ColorAlphaSettingNode::valueChanged(bool updateText) {
     m_colorSpr->updateOpacity(m_uncommittedValue.a / 255.f);
 }
 
+void ColorAlphaSettingNode::updateColor(ccColor4B const& color) {
+    m_uncommittedValue = color;
+    this->valueChanged(true);
+}
+
 bool ColorAlphaSettingNode::setup(std::shared_ptr<ColorAlphaSetting> setting, float width) {
 	m_colorSpr = ColorChannelSprite::create();
 	m_colorSpr->setColor(to3B(m_uncommittedValue));
@@ -130,7 +143,10 @@ bool ColorAlphaSettingNode::setup(std::shared_ptr<ColorAlphaSetting> setting, fl
 	
 	auto button = CCMenuItemSpriteExtra::create(
 		m_colorSpr, this, makeMenuSelector([this](CCObject*) {
-            ColorPickPopup<ColorAlphaSettingNode>::create(this)->show();
+            auto popup = ColorPickPopup::create(m_uncommittedValue);
+            popup->setDelegate(this);
+            popup->setColorTarget(m_colorSpr);
+            popup->show();
         })
 	);
     button->setPositionX(-10.f);
diff --git a/loader/src/ui/internal/settings/GeodeSettingNode.hpp b/loader/src/ui/internal/settings/GeodeSettingNode.hpp
index 98b0217d..d3dc6dc3 100644
--- a/loader/src/ui/internal/settings/GeodeSettingNode.hpp
+++ b/loader/src/ui/internal/settings/GeodeSettingNode.hpp
@@ -9,12 +9,10 @@
 #include <Geode/utils/string.hpp>
 #include <Geode/utils/cocos.hpp>
 #include <Geode/ui/Popup.hpp>
+#include <Geode/ui/ColorPickPopup.hpp>
 
 USE_GEODE_NAMESPACE();
 
-template<class T>
-class ColorPickPopup;
-
 namespace {
     template<class Num>
     Num parseNumForInput(std::string const& str) {
@@ -497,155 +495,27 @@ protected:
 };
 
 class ColorSettingNode : 
-    public GeodeSettingNode<ColorSettingNode, ColorSetting>
+    public GeodeSettingNode<ColorSettingNode, ColorSetting>,
+    public ColorPickPopupDelegate
 {
 protected:
     ColorChannelSprite* m_colorSpr;
 
-    friend class ColorPickPopup<ColorSettingNode>;
-
     void valueChanged(bool updateText) override;
+    void updateColor(ccColor4B const& color) override;
 
     bool setup(std::shared_ptr<ColorSetting> setting, float width) override;
 };
 
 class ColorAlphaSettingNode : 
-    public GeodeSettingNode<ColorAlphaSettingNode, ColorAlphaSetting>
+    public GeodeSettingNode<ColorAlphaSettingNode, ColorAlphaSetting>,
+    public ColorPickPopupDelegate
 {
 protected:
     ColorChannelSprite* m_colorSpr;
 
-    friend class ColorPickPopup<ColorAlphaSettingNode>;
-
     void valueChanged(bool updateText) override;
+    void updateColor(ccColor4B const& color) override;
 
     bool setup(std::shared_ptr<ColorAlphaSetting> setting, float width) override;
 };
-
-template<class T>
-class ColorPickPopup :
-    public Popup<T*>,
-    public ColorPickerDelegate,
-    public TextInputDelegate
-{
-protected:
-    T* m_settingNode;
-    Slider* m_opacitySlider = nullptr;
-    InputNode* m_opacityInput = nullptr;
-
-    static constexpr auto TAG_OPACITY_INPUT = 0;
-    static constexpr auto TAG_R_INPUT       = 1;
-    static constexpr auto TAG_G_INPUT       = 2;
-    static constexpr auto TAG_B_INPUT       = 3;
-    static constexpr auto TAG_HEX_INPUT     = 4;
-    static constexpr auto IS_RGBA = std::is_same_v<
-        decltype(T::m_uncommittedValue),
-        ccColor4B
-    >;
-
-    FLAlertLayer* asAlert() {
-        return static_cast<FLAlertLayer*>(this);
-    }
-
-    bool setup(T* node) override {
-        asAlert()->m_noElasticity = true;
-
-        m_settingNode = node;
-
-        auto winSize = CCDirector::sharedDirector()->getWinSize();
-
-        auto picker = CCControlColourPicker::colourPicker();
-        if constexpr (IS_RGBA) {
-            picker->setColorValue(to3B(node->m_uncommittedValue));
-        } else {
-            picker->setColorValue(node->m_uncommittedValue);
-        }
-        picker->setColorTarget(node->m_colorSpr);
-        picker->setPosition(
-            winSize.width / 2,
-            winSize.height / 2 + (IS_RGBA ? 20.f : 0.f)
-        );
-        picker->setDelegate(this);
-        asAlert()->m_mainLayer->addChild(picker);
-
-        if constexpr (IS_RGBA) {
-            m_opacitySlider = Slider::create(
-                this,
-                menu_selector(ColorPickPopup::onOpacity),
-                .75f
-            );
-            m_opacitySlider->setPosition(
-                winSize.width / 2 - 30.f,
-                winSize.height / 2 - 90.f
-            );
-            m_opacitySlider->setValue(node->m_uncommittedValue.a / 255.f);
-            m_opacitySlider->updateBar();
-            asAlert()->m_mainLayer->addChild(m_opacitySlider);
-
-            m_opacityInput = InputNode::create(45.f, "0.00");
-            m_opacityInput->setPosition(85.f, -90.f);
-            m_opacityInput->setString(numToString(
-                node->m_uncommittedValue.a / 255.f, 2
-            ));
-            m_opacityInput->getInput()->setTag(TAG_OPACITY_INPUT);
-            m_opacityInput->getInput()->setDelegate(this);
-            asAlert()->m_buttonMenu->addChild(m_opacityInput);
-        }
-
-        return true;
-    }
-
-    void textChanged(CCTextInputNode* input) override {
-        switch (input->getTag()) {
-            case TAG_OPACITY_INPUT: {
-                if constexpr (IS_RGBA) {
-                    try {
-                        auto value = std::stof(input->getString());
-                        m_settingNode->m_uncommittedValue.a = static_cast<GLubyte>(
-                            value * 255.f
-                        );
-                        m_opacitySlider->setValue(value);
-                        m_opacitySlider->updateBar();
-                        m_settingNode->valueChanged(true);
-                    } catch(...) {}
-                }
-            } break;
-
-            default: break;
-        }
-    }
-
-    void onOpacity(CCObject* sender) {
-        if constexpr (IS_RGBA) {
-            m_settingNode->m_uncommittedValue.a = static_cast<GLubyte>(
-                static_cast<SliderThumb*>(sender)->getValue() * 255.f
-            );
-            m_opacityInput->setString(numToString(
-                m_settingNode->m_uncommittedValue.a / 255.f, 3
-            ));
-            m_settingNode->valueChanged(true);
-        }
-    }
-
-    void colorValueChanged(ccColor3B color) override {
-        if constexpr (IS_RGBA) {
-            m_settingNode->m_uncommittedValue = to4B(
-                color, m_settingNode->m_uncommittedValue.a
-            );
-        } else {
-            m_settingNode->m_uncommittedValue = color;
-        }
-        m_settingNode->valueChanged(true);
-    }
-
-public:
-    static ColorPickPopup* create(T* node) {
-        auto ret = new ColorPickPopup();
-        if (ret && ret->init(250.f, (IS_RGBA ? 250.f : 200.f), node)) {
-            ret->autorelease();
-            return ret;
-        }
-        CC_SAFE_DELETE(ret);
-        return nullptr;
-    }
-};
diff --git a/loader/src/ui/nodes/ColorPickPopup.cpp b/loader/src/ui/nodes/ColorPickPopup.cpp
new file mode 100644
index 00000000..7595718d
--- /dev/null
+++ b/loader/src/ui/nodes/ColorPickPopup.cpp
@@ -0,0 +1,289 @@
+#include <Geode/ui/ColorPickPopup.hpp>
+#include <Geode/utils/operators.hpp>
+
+USE_GEODE_NAMESPACE();
+
+static GLubyte parseInt(const char* str) {
+    try {
+        return static_cast<GLubyte>(std::stoi(str));
+    } catch(...) {
+        return 255;
+    }
+}
+
+static GLubyte parseFloat(const char* str) {
+    try {
+        return static_cast<GLubyte>(std::stof(str) * 255.f);
+    } catch(...) {
+        return 255;
+    }
+}
+
+bool ColorPickPopup::setup(ccColor4B const& color, bool isRGBA) {
+    m_noElasticity = true;
+    m_color = color;
+    m_originalColor = color;
+
+    auto winSize = CCDirector::sharedDirector()->getWinSize();
+
+    this->setTitle("Select Color");
+
+    // picker
+
+    m_picker = CCControlColourPicker::colourPicker();
+    m_picker->setPosition(
+        winSize.width / 2 - 45.f,
+        winSize.height / 2 + (isRGBA ? 25.f : 0.f)
+    );
+    m_picker->setDelegate(this);
+    m_mainLayer->addChild(m_picker);
+
+    // color difference
+
+    auto oldColorSpr = CCSprite::createWithSpriteFrameName("whiteSquare60_001.png");
+    oldColorSpr->setPosition({
+        winSize.width / 2 - 165.f,
+        winSize.height / 2 + 15.f
+    });
+    oldColorSpr->setColor(to3B(m_color));
+    m_mainLayer->addChild(oldColorSpr);
+
+    m_newColorSpr = CCSprite::createWithSpriteFrameName("whiteSquare60_001.png");
+    m_newColorSpr->setPosition({
+        winSize.width / 2 - 165.f,
+        winSize.height / 2 - 15.f
+    });
+    m_newColorSpr->setColor(to3B(m_color));
+    m_mainLayer->addChild(m_newColorSpr);
+
+    auto resetBtnSpr = ButtonSprite::create(
+        CCSprite::createWithSpriteFrameName("reset-gold.png"_spr),
+        0x20, true, 0.f, "GJ_button_01.png", 1.25f
+    );
+    resetBtnSpr->setScale(.6f);
+
+    m_resetBtn = CCMenuItemSpriteExtra::create(
+        resetBtnSpr, this, menu_selector(ColorPickPopup::onReset)
+    );
+    m_resetBtn->setPosition({ -165.f, -50.f });
+    m_buttonMenu->addChild(m_resetBtn);
+
+    // r
+
+    auto rText = CCLabelBMFont::create("R", "goldFont.fnt");
+    rText->setPosition(
+        winSize.width / 2 + 75.f,
+        winSize.height / 2 + (isRGBA ? 60.f : 35.f)
+    );
+    rText->setScale(.55f);
+    m_mainLayer->addChild(rText);
+    
+    m_rInput = InputNode::create(50.f, "R");
+    m_rInput->setPosition(75.f, (isRGBA ? 40.f : 15.f));
+    m_rInput->setScale(.7f);
+    m_rInput->getInput()->setTag(TAG_R_INPUT);
+    m_rInput->getInput()->setDelegate(this);
+    m_buttonMenu->addChild(m_rInput);
+
+    // g
+
+    auto gText = CCLabelBMFont::create("G", "goldFont.fnt");
+    gText->setPosition(
+        winSize.width / 2 + 115.f,
+        winSize.height / 2 + (isRGBA ? 60.f : 35.f)
+    );
+    gText->setScale(.55f);
+    m_mainLayer->addChild(gText);
+    
+    m_gInput = InputNode::create(50.f, "G");
+    m_gInput->setPosition(115.f, (isRGBA ? 40.f : 15.f));
+    m_gInput->setScale(.7f);
+    m_gInput->getInput()->setTag(TAG_G_INPUT);
+    m_gInput->getInput()->setDelegate(this);
+    m_buttonMenu->addChild(m_gInput);
+
+    // b
+
+    auto bText = CCLabelBMFont::create("B", "goldFont.fnt");
+    bText->setPosition(
+        winSize.width / 2 + 155.f,
+        winSize.height / 2 + (isRGBA ? 60.f : 35.f)
+    );
+    bText->setScale(.55f);
+    m_mainLayer->addChild(bText);
+    
+    m_bInput = InputNode::create(50.f, "B");
+    m_bInput->setPosition(155.f, (isRGBA ? 40.f : 15.f));
+    m_bInput->setScale(.7f);
+    m_bInput->getInput()->setTag(TAG_B_INPUT);
+    m_bInput->getInput()->setDelegate(this);
+    m_buttonMenu->addChild(m_bInput);
+
+    // hex
+
+    auto hexText = CCLabelBMFont::create("Hex", "goldFont.fnt");
+    hexText->setPosition(
+        winSize.width / 2 + 115.f,
+        winSize.height / 2 + (isRGBA ? 20.f : -5.f)
+    );
+    hexText->setScale(.55f);
+    m_mainLayer->addChild(hexText);
+    
+    m_hexInput = InputNode::create(165.f, "Hex");
+    m_hexInput->setPosition(115.f, (isRGBA ? 0.f : -25.f));
+    m_hexInput->setScale(.7f);
+    m_hexInput->getInput()->setTag(TAG_HEX_INPUT);
+    m_hexInput->getInput()->setDelegate(this);
+    m_buttonMenu->addChild(m_hexInput);
+
+    if (isRGBA) {
+        auto opacityText = CCLabelBMFont::create("Opacity", "goldFont.fnt");
+        opacityText->setPosition(
+            winSize.width / 2 - 30.f,
+            winSize.height / 2 - 70.f
+        );
+        opacityText->setScale(.55f);
+        m_mainLayer->addChild(opacityText);
+
+        m_opacitySlider = Slider::create(
+            this,
+            menu_selector(ColorPickPopup::onOpacitySlider),
+            .75f
+        );
+        m_opacitySlider->setPosition(
+            winSize.width / 2 - 30.f,
+            winSize.height / 2 - 90.f
+        );
+        m_opacitySlider->setValue(color.a / 255.f);
+        m_opacitySlider->updateBar();
+        m_mainLayer->addChild(m_opacitySlider);
+
+        m_opacityInput = InputNode::create(60.f, "0.00");
+        m_opacityInput->setPosition(85.f, -90.f);
+        m_opacityInput->setScale(.7f);
+        m_opacityInput->getInput()->setTag(TAG_OPACITY_INPUT);
+        m_opacityInput->getInput()->setDelegate(this);
+        m_buttonMenu->addChild(m_opacityInput);
+    }
+
+    this->updateState();
+
+    auto okBtnSpr = ButtonSprite::create("OK");
+    okBtnSpr->setScale(.7f);
+
+    auto okBtn = CCMenuItemSpriteExtra::create(
+        okBtnSpr, this, menu_selector(ColorPickPopup::onClose)
+    );
+    okBtn->setPosition(.0f, -m_size.height / 2 + 25.f);
+    m_buttonMenu->addChild(okBtn);
+
+    return true;
+}
+
+void ColorPickPopup::updateState(CCNode* except) {
+    #define IF_NOT_EXCEPT(inp, value)\
+        if (inp->getInput() != except) {\
+            inp->getInput()->setDelegate(nullptr);\
+            inp->setString(value);\
+            inp->getInput()->setDelegate(this);\
+        }
+
+    IF_NOT_EXCEPT(m_rInput, numToString<int>(m_color.r));
+    IF_NOT_EXCEPT(m_gInput, numToString<int>(m_color.g));
+    IF_NOT_EXCEPT(m_bInput, numToString<int>(m_color.b));
+    IF_NOT_EXCEPT(m_hexInput, cc3bToHexString(to3B(m_color)));
+    if (m_opacityInput) {
+        IF_NOT_EXCEPT(
+            m_opacityInput,
+            numToString(m_color.a / 255.f, 2)
+        );
+    }
+    if (m_opacitySlider) {
+        m_opacitySlider->setValue(m_color.a / 255.f);
+        m_opacitySlider->updateBar();
+    }
+    if (m_picker != except) {
+        m_picker->setDelegate(nullptr);
+        m_picker->setColorValue(to3B(m_color));
+        m_picker->setDelegate(this);
+    }
+    m_resetBtn->setVisible(m_originalColor != m_color);
+    m_newColorSpr->setColor(to3B(m_color));
+    if (m_delegate) m_delegate->updateColor(m_color);
+}
+
+void ColorPickPopup::onOpacitySlider(CCObject* sender) {
+    m_color.a = static_cast<GLubyte>(
+        static_cast<SliderThumb*>(sender)->getValue() * 255.f
+    );
+    this->updateState();
+}
+
+void ColorPickPopup::onReset(CCObject*) {
+    m_color = m_originalColor;
+    this->updateState();
+}
+
+void ColorPickPopup::textChanged(CCTextInputNode* input) {
+    if (input->getString() && strlen(input->getString())) {
+        switch (input->getTag()) {
+            case TAG_HEX_INPUT: {
+                if (auto color = cc3bFromHexString(input->getString())) {
+                    m_color.r = color.value().r;
+                    m_color.g = color.value().g;
+                    m_color.b = color.value().b;
+                }
+            } break;
+            
+            case TAG_OPACITY_INPUT: {
+                m_color.a = parseFloat(input->getString());
+            } break;
+            
+            case TAG_R_INPUT: m_color.r = parseInt(input->getString()); break;
+            case TAG_G_INPUT: m_color.g = parseInt(input->getString()); break;
+            case TAG_B_INPUT: m_color.b = parseInt(input->getString()); break;
+
+            default: break;
+        }
+    }
+    this->updateState(input);
+}
+
+void ColorPickPopup::colorValueChanged(ccColor3B color) {
+    m_color.r = color.r;
+    m_color.g = color.g;
+    m_color.b = color.b;
+    this->updateState(m_picker);
+}
+
+void ColorPickPopup::setDelegate(ColorPickPopupDelegate* delegate) {
+    m_delegate = delegate;
+}
+
+void ColorPickPopup::setColorTarget(cocos2d::CCSprite* spr) {
+    m_picker->setColorTarget(spr);
+}
+
+ColorPickPopup* ColorPickPopup::create(ccColor4B const& color, bool isRGBA) {
+    auto ret = new ColorPickPopup();
+    if (ret && ret->init(
+        380.f, (isRGBA ? 290.f : 240.f), color, isRGBA
+    )) {
+        ret->autorelease();
+        return ret;
+    }
+    CC_SAFE_DELETE(ret);
+    return nullptr;
+}
+
+ColorPickPopup* ColorPickPopup::create(bool isRGBA) {
+    return ColorPickPopup::create({ 255, 255, 255, 255 }, isRGBA);
+}
+
+ColorPickPopup* ColorPickPopup::create(ccColor3B const& color) {
+    return ColorPickPopup::create(to4B(color), false);
+}
+
+ColorPickPopup* ColorPickPopup::create(ccColor4B const& color) {
+    return ColorPickPopup::create(color, true);
+}