diff --git a/buildSrc/src/main/java/de/hysky/skyblocker/init/InitProcessor.java b/buildSrc/src/main/java/de/hysky/skyblocker/init/InitProcessor.java
index 3c864bdee3..14b6e742bf 100644
--- a/buildSrc/src/main/java/de/hysky/skyblocker/init/InitProcessor.java
+++ b/buildSrc/src/main/java/de/hysky/skyblocker/init/InitProcessor.java
@@ -2,7 +2,8 @@
 
 import de.hysky.skyblocker.Processor;
 import org.gradle.api.tasks.compile.JavaCompile;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java b/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java
index cf35208b3a..f0af84d019 100644
--- a/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java
+++ b/src/main/java/de/hysky/skyblocker/compatibility/jei/SkyblockerJEIPlugin.java
@@ -21,14 +21,12 @@
 import net.minecraft.client.gui.screen.ingame.InventoryScreen;
 import net.minecraft.client.util.math.Rect2i;
 import net.minecraft.item.ItemStack;
-import net.minecraft.recipe.*;
-import net.minecraft.recipe.book.CraftingRecipeCategory;
+import net.minecraft.recipe.CraftingRecipe;
 import net.minecraft.util.Identifier;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 @JeiPlugin
 public class SkyblockerJEIPlugin implements IModPlugin {
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
index 3eed81da63..8fa8b4fc26 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/MiningCategory.java
@@ -4,14 +4,14 @@
 import de.hysky.skyblocker.config.SkyblockerConfig;
 import de.hysky.skyblocker.config.configs.MiningConfig;
 import de.hysky.skyblocker.config.screens.powdertracker.PowderFilterConfigScreen;
-import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget;
 import de.hysky.skyblocker.skyblock.dwarven.CarpetHighlighter;
+import de.hysky.skyblocker.skyblock.dwarven.CrystalsHudWidget;
 import de.hysky.skyblocker.skyblock.dwarven.PowderMiningTracker;
+import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen;
 import de.hysky.skyblocker.skyblock.tabhud.widget.CommsWidget;
+import de.hysky.skyblocker.utils.Location;
 import dev.isxander.yacl3.api.*;
 import dev.isxander.yacl3.api.controller.ColorControllerBuilder;
-import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen;
-import de.hysky.skyblocker.utils.Location;
 import dev.isxander.yacl3.api.controller.FloatFieldControllerBuilder;
 import dev.isxander.yacl3.api.controller.IntegerSliderControllerBuilder;
 import it.unimi.dsi.fastutil.objects.ObjectImmutableList;
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
index 6d18c45175..9a47f15eb5 100644
--- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
+++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java
@@ -95,6 +95,14 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
                                 newValue -> config.uiAndVisuals.showEquipmentInInventory = newValue)
                         .controller(ConfigUtils::createBooleanController)
                         .build())
+				.option(Option.<Boolean>createBuilder()
+						.name(Text.translatable("skyblocker.config.uiAndVisuals.museumOverlay"))
+						.description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip")))
+						.binding(defaults.uiAndVisuals.museumOverlay,
+								() -> config.uiAndVisuals.museumOverlay,
+								newValue -> config.uiAndVisuals.museumOverlay = newValue)
+						.controller(ConfigUtils::createBooleanController)
+						.build())
                 .option(Option.<Boolean>createBuilder()
                         .name(Text.translatable("skyblocker.config.uiAndVisuals.cancelComponentUpdateAnimation"))
                         .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.cancelComponentUpdateAnimation.@Tooltip")))
@@ -104,7 +112,7 @@ public static ConfigCategory create(SkyblockerConfig defaults, SkyblockerConfig
                         .controller(ConfigUtils::createBooleanController)
                         .build())
 
-                //Chest Value FIXME change dropdown to color controller
+				//Chest Value FIXME change dropdown to color controller
                 .group(OptionGroup.createBuilder()
                         .name(Text.translatable("skyblocker.config.uiAndVisuals.chestValue"))
                         .collapsed(true)
diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
index 7a6b3f5daa..fe6a7227d7 100644
--- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
+++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java
@@ -37,6 +37,9 @@ public class UIAndVisualsConfig {
     @SerialEntry
     public boolean showEquipmentInInventory = true;
 
+	@SerialEntry
+	public boolean museumOverlay = true;
+
     @SerialEntry
     public boolean cancelComponentUpdateAnimation = true;
 
diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
index d9d90cd82a..2dc07a26ca 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/ClientWorldMixin.java
@@ -1,5 +1,8 @@
 package de.hysky.skyblocker.mixins;
 
+import com.llamalad7.mixinextras.sugar.Local;
+import com.llamalad7.mixinextras.sugar.Share;
+import com.llamalad7.mixinextras.sugar.ref.LocalRef;
 import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager;
 import de.hysky.skyblocker.skyblock.dungeon.device.SimonSays;
 import de.hysky.skyblocker.skyblock.dwarven.CrystalsChestHighlighter;
@@ -11,17 +14,12 @@
 import net.minecraft.client.world.ClientWorld;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.world.BlockView;
-
 import org.jetbrains.annotations.Nullable;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-import com.llamalad7.mixinextras.sugar.Local;
-import com.llamalad7.mixinextras.sugar.Share;
-import com.llamalad7.mixinextras.sugar.ref.LocalRef;
-
 @Mixin(ClientWorld.class)
 public abstract class ClientWorldMixin implements BlockView {
 
diff --git a/src/main/java/de/hysky/skyblocker/mixins/CommandTreeS2CPacketMixin.java b/src/main/java/de/hysky/skyblocker/mixins/CommandTreeS2CPacketMixin.java
index 83605ca95b..b8d51bdb0c 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/CommandTreeS2CPacketMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/CommandTreeS2CPacketMixin.java
@@ -3,7 +3,6 @@
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.mojang.brigadier.tree.CommandNode;
 import com.mojang.brigadier.tree.LiteralCommandNode;
-
 import de.hysky.skyblocker.skyblock.SackItemAutocomplete;
 import de.hysky.skyblocker.skyblock.ViewstashAutocomplete;
 import de.hysky.skyblocker.skyblock.WarpAutocomplete;
diff --git a/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java b/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java
index 12022b7c2a..40b0ad1b44 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/DyedColorComponentMixin.java
@@ -2,7 +2,6 @@
 
 import com.llamalad7.mixinextras.injector.ModifyReturnValue;
 import com.llamalad7.mixinextras.sugar.Local;
-
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.skyblock.item.CustomArmorAnimatedDyes;
 import de.hysky.skyblocker.utils.ItemUtils;
@@ -10,7 +9,6 @@
 import net.minecraft.component.type.DyedColorComponent;
 import net.minecraft.item.ItemStack;
 import net.minecraft.util.math.ColorHelper;
-
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
index d092de6e2e..a00014f1f5 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenMixin.java
@@ -2,7 +2,6 @@
 
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.llamalad7.mixinextras.sugar.Local;
-
 import com.mojang.blaze3d.systems.RenderSystem;
 import de.hysky.skyblocker.config.SkyblockerConfig;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
@@ -12,10 +11,15 @@
 import de.hysky.skyblocker.skyblock.experiment.SuperpairsSolver;
 import de.hysky.skyblocker.skyblock.experiment.UltrasequencerSolver;
 import de.hysky.skyblocker.skyblock.garden.VisitorHelper;
-import de.hysky.skyblocker.skyblock.item.*;
+import de.hysky.skyblocker.skyblock.item.ItemPrice;
+import de.hysky.skyblocker.skyblock.item.ItemProtection;
+import de.hysky.skyblocker.skyblock.item.ItemRarityBackgrounds;
+import de.hysky.skyblocker.skyblock.item.WikiLookup;
 import de.hysky.skyblocker.skyblock.item.slottext.SlotTextManager;
 import de.hysky.skyblocker.skyblock.item.tooltip.BackpackPreview;
 import de.hysky.skyblocker.skyblock.item.tooltip.CompactorDeletorPreview;
+import de.hysky.skyblocker.skyblock.museum.MuseumItemCache;
+import de.hysky.skyblocker.skyblock.museum.MuseumManager;
 import de.hysky.skyblocker.skyblock.quicknav.QuickNav;
 import de.hysky.skyblocker.skyblock.quicknav.QuickNavButton;
 import de.hysky.skyblocker.utils.ItemUtils;
@@ -97,6 +101,15 @@ public abstract class HandledScreenMixin<T extends ScreenHandler> extends Screen
 	@Shadow
 	protected abstract List<Text> getTooltipFromItem(ItemStack stack);
 
+	@Shadow
+	protected int backgroundWidth;
+
+	@Shadow
+	protected int x;
+
+	@Shadow
+	protected int y;
+
 	@Unique
 	private List<QuickNavButton> quickNavButtons;
 
@@ -113,13 +126,28 @@ protected HandledScreenMixin(Text title) {
 		}
 	}
 
+	@Inject(method = "init", at = @At("TAIL"))
+	private void skyblocker$initMuseumOverlay(CallbackInfo ci) {
+		if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.museumOverlay && client != null && client.player != null && !client.player.isCreative() && getTitle().getString().contains("Museum")) {
+			new MuseumManager(this, this.x, this.y, this.backgroundWidth);
+		}
+	}
+
+	@Inject(method = "removed", at = @At("HEAD"))
+	private void skyblocker$removeMuseumOverlay(CallbackInfo ci) {
+		if (Utils.isOnSkyblock() && SkyblockerConfigManager.get().uiAndVisuals.museumOverlay && client != null && client.player != null && !client.player.isCreative() && getTitle().getString().contains("Museum")) {
+			// Reset Overlay variables when no longer in Museum inventory
+			MuseumManager.reset();
+		}
+	}
+
 	@Inject(at = @At("HEAD"), method = "keyPressed")
 	public void skyblocker$keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable<Boolean> cir) {
 		if (this.client != null && this.client.player != null && this.focusedSlot != null && keyCode != 256 && !this.client.options.inventoryKey.matchesKey(keyCode, scanCode) && Utils.isOnSkyblock()) {
 			SkyblockerConfig config = SkyblockerConfigManager.get();
 			//wiki lookup
 			if (config.general.wikiLookup.enableWikiLookup && WikiLookup.wikiLookup.matchesKey(keyCode, scanCode)) {
-				WikiLookup.openWiki(this.focusedSlot, client.player);
+				WikiLookup.openWiki(this.focusedSlot.getStack(), client.player);
 			}
 			//item protection
 			if (ItemProtection.itemProtection.matchesKey(keyCode, scanCode)) {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
index 9b47f736e1..cc8cfec841 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HandledScreenProviderMixin.java
@@ -6,8 +6,8 @@
 import de.hysky.skyblocker.skyblock.auction.AuctionHouseScreenHandler;
 import de.hysky.skyblocker.skyblock.auction.AuctionViewScreen;
 import de.hysky.skyblocker.skyblock.dungeon.partyfinder.PartyFinderScreen;
-import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreenHandler;
 import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreen;
+import de.hysky.skyblocker.skyblock.item.SkyblockCraftingTableScreenHandler;
 import de.hysky.skyblocker.skyblock.tabhud.config.WidgetsConfigurationScreen;
 import de.hysky.skyblocker.utils.Utils;
 import net.minecraft.client.MinecraftClient;
diff --git a/src/main/java/de/hysky/skyblocker/mixins/HeldItemRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixins/HeldItemRendererMixin.java
index 8605fec29b..e872765320 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/HeldItemRendererMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/HeldItemRendererMixin.java
@@ -1,14 +1,12 @@
 package de.hysky.skyblocker.mixins;
 
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
 import com.llamalad7.mixinextras.injector.ModifyReturnValue;
-
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.utils.Utils;
 import net.minecraft.client.render.item.HeldItemRenderer;
 import net.minecraft.item.ItemStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
 
 @Mixin(HeldItemRenderer.class)
 public class HeldItemRendererMixin {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
index a0ba8284ec..45120c0840 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/InventoryScreenMixin.java
@@ -1,31 +1,29 @@
 package de.hysky.skyblocker.mixins;
 
-import com.llamalad7.mixinextras.sugar.Local;
-import net.minecraft.entity.player.PlayerEntity;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.ModifyArg;
-
 import com.llamalad7.mixinextras.injector.ModifyReturnValue;
 import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
-
+import com.llamalad7.mixinextras.sugar.Local;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.skyblock.garden.GardenPlotsWidget;
 import de.hysky.skyblocker.skyblock.itemlist.recipebook.SkyblockRecipeBookWidget;
 import de.hysky.skyblocker.utils.Location;
 import de.hysky.skyblocker.utils.Utils;
-import net.minecraft.client.gui.DrawContext;
 import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
+import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
 import net.minecraft.client.gui.screen.ingame.InventoryScreen;
 import net.minecraft.client.gui.screen.ingame.StatusEffectsDisplay;
 import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
 import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.entity.player.PlayerInventory;
 import net.minecraft.screen.PlayerScreenHandler;
 import net.minecraft.text.Text;
+import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(InventoryScreen.class)
diff --git a/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java b/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
index a4b0282c66..19b2cbdb42 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/PlayerListHudMixin.java
@@ -1,13 +1,12 @@
 package de.hysky.skyblocker.mixins;
 
+import de.hysky.skyblocker.utils.Utils;
+import net.minecraft.client.gui.hud.PlayerListHud;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-import de.hysky.skyblocker.utils.Utils;
-import net.minecraft.client.gui.hud.PlayerListHud;
-
 @Mixin(PlayerListHud.class)
 public class PlayerListHudMixin {
 
diff --git a/src/main/java/de/hysky/skyblocker/mixins/RenderLayerMultiPhaseMixin.java b/src/main/java/de/hysky/skyblocker/mixins/RenderLayerMultiPhaseMixin.java
index 87d591de70..dad6977bd8 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/RenderLayerMultiPhaseMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/RenderLayerMultiPhaseMixin.java
@@ -1,14 +1,12 @@
 package de.hysky.skyblocker.mixins;
 
-import org.objectweb.asm.Opcodes;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
-
 import de.hysky.skyblocker.utils.render.SkyblockerRenderLayers;
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.client.render.RenderPhase.DepthTest;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
 
 @Mixin(RenderLayer.MultiPhase.class)
 public class RenderLayerMultiPhaseMixin {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java b/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java
index 7595d3e2a8..506627c7f6 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/RenderPhaseDepthTestMixin.java
@@ -1,16 +1,13 @@
 package de.hysky.skyblocker.mixins;
 
-import org.lwjgl.opengl.GL11;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.ModifyArg;
-
 import com.llamalad7.mixinextras.sugar.Local;
 import com.mojang.blaze3d.systems.RenderSystem;
-
 import de.hysky.skyblocker.skyblock.entity.MobGlow;
-
 import net.minecraft.client.render.RenderPhase;
+import org.lwjgl.opengl.GL11;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyArg;
 
 @Mixin(RenderPhase.DepthTest.class)
 public class RenderPhaseDepthTestMixin {
diff --git a/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java b/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java
index 4248a3e51c..4cdd81efb1 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/SignEditScreenMixin.java
@@ -1,6 +1,7 @@
 package de.hysky.skyblocker.mixins;
 
 
+import com.llamalad7.mixinextras.sugar.Local;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.skyblock.calculators.SignCalculator;
 import de.hysky.skyblocker.skyblock.speedPreset.SpeedPresets;
@@ -17,8 +18,6 @@
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import com.llamalad7.mixinextras.sugar.Local;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(AbstractSignEditScreen.class)
diff --git a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java
index 27a2094139..40fb63967b 100644
--- a/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java
+++ b/src/main/java/de/hysky/skyblocker/mixins/WorldRendererMixin.java
@@ -1,27 +1,24 @@
 package de.hysky.skyblocker.mixins;
 
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.ModifyVariable;
-import org.spongepowered.asm.mixin.injection.Slice;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
 import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
 import com.llamalad7.mixinextras.sugar.Local;
-
 import de.hysky.skyblocker.skyblock.dungeon.LividColor;
 import de.hysky.skyblocker.skyblock.entity.MobBoundingBoxes;
 import de.hysky.skyblocker.skyblock.entity.MobGlow;
 import de.hysky.skyblocker.skyblock.slayers.SlayerManager;
-
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.render.DefaultFramebufferSet;
 import net.minecraft.client.render.WorldRenderer;
 import net.minecraft.entity.Entity;
 import net.minecraft.entity.decoration.ArmorStandEntity;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+import org.spongepowered.asm.mixin.injection.Slice;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(WorldRenderer.class)
 public class WorldRendererMixin {
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/NightVisionCommand.java b/src/main/java/de/hysky/skyblocker/skyblock/NightVisionCommand.java
index 7fa9d780cc..e0f5b68230 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/NightVisionCommand.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/NightVisionCommand.java
@@ -5,7 +5,6 @@
 import com.mojang.brigadier.context.CommandContext;
 import de.hysky.skyblocker.SkyblockerMod;
 import de.hysky.skyblocker.annotations.Init;
-import de.hysky.skyblocker.config.SkyblockerConfig;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
 import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/chat/ConfirmationPromptHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/chat/ConfirmationPromptHelper.java
index 7f4a7f46a4..46e0315496 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/chat/ConfirmationPromptHelper.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/chat/ConfirmationPromptHelper.java
@@ -1,7 +1,5 @@
 package de.hysky.skyblocker.skyblock.chat;
 
-import java.util.Optional;
-
 import de.hysky.skyblocker.annotations.Init;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.utils.Constants;
@@ -17,6 +15,8 @@
 import net.minecraft.text.Style;
 import net.minecraft.text.Text;
 
+import java.util.Optional;
+
 public class ConfirmationPromptHelper {
 	private static String command;
 	private static long commandFoundAt;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
index 9f52c414ad..54f986d426 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/DungeonClass.java
@@ -1,12 +1,12 @@
 package de.hysky.skyblocker.skyblock.dungeon;
 
+import de.hysky.skyblocker.skyblock.entity.MobGlow;
+
 import java.util.Arrays;
 import java.util.Map;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import de.hysky.skyblocker.skyblock.entity.MobGlow;
-
 public enum DungeonClass {
 	UNKNOWN("Unknown", MobGlow.NO_GLOW),
 	HEALER("Healer", 0x820dd1),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
index 47bd1096f4..e3a728684e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/Reparty.java
@@ -1,5 +1,8 @@
 package de.hysky.skyblocker.skyblock.dungeon;
 
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.logging.LogUtils;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.utils.Constants;
 import de.hysky.skyblocker.utils.Utils;
@@ -18,18 +21,13 @@
 import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.text.Text;
+import org.slf4j.Logger;
 
 import java.util.Map;
 import java.util.Objects;
 import java.util.UUID;
 import java.util.regex.Matcher;
 
-import org.slf4j.Logger;
-
-import com.mojang.brigadier.Command;
-import com.mojang.brigadier.context.CommandContext;
-import com.mojang.logging.LogUtils;
-
 public class Reparty extends ChatPatternListener {
 	private static final Logger LOGGER = LogUtils.getLogger();
 	private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java
index 1b94ca1838..39d3cdb593 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java
@@ -29,11 +29,10 @@
 import net.minecraft.util.math.Box;
 import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Objects;
 
-import org.jetbrains.annotations.Nullable;
-
 public class SimonSays {
 	private static final Box BOARD_AREA = Box.enclosing(new BlockPos(111, 123, 92), new BlockPos(111, 120, 95));
 	private static final Box BUTTONS_AREA = Box.enclosing(new BlockPos(110, 123, 92), new BlockPos(110, 120, 95));
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java
index 4c3a0e8c02..e77808ae61 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/DungeonPlayerManager.java
@@ -1,10 +1,5 @@
 package de.hysky.skyblocker.skyblock.dungeon.secrets;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.jetbrains.annotations.Range;
-
 import de.hysky.skyblocker.annotations.Init;
 import de.hysky.skyblocker.events.DungeonEvents;
 import de.hysky.skyblocker.skyblock.dungeon.DungeonClass;
@@ -12,6 +7,10 @@
 import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
 import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import net.minecraft.entity.player.PlayerEntity;
+import org.jetbrains.annotations.Range;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public class DungeonPlayerManager {
 	/**
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java
index 78cc2c1fd6..466e9a140e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsHudWidget.java
@@ -16,7 +16,6 @@
 import org.joml.Vector2ic;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 @RegisterWidget
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
index 860bc43dc9..71f7cd0b29 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsLocationsManager.java
@@ -15,8 +15,8 @@
 import de.hysky.skyblocker.utils.command.argumenttypes.blockpos.ClientPosArgument;
 import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
 import de.hysky.skyblocker.utils.scheduler.Scheduler;
-import de.hysky.skyblocker.utils.ws.WsMessageHandler;
 import de.hysky.skyblocker.utils.ws.Service;
+import de.hysky.skyblocker.utils.ws.WsMessageHandler;
 import de.hysky.skyblocker.utils.ws.WsStateManager;
 import de.hysky.skyblocker.utils.ws.message.CrystalsWaypointMessage;
 import de.hysky.skyblocker.utils.ws.message.CrystalsWaypointSubscribeMessage;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
index a87ac6d91a..b721df7605 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/end/TheEnd.java
@@ -31,7 +31,10 @@
 import org.slf4j.LoggerFactory;
 
 import java.nio.file.Path;
-import java.util.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
 
 public class TheEnd {
     protected static final Logger LOGGER = LoggerFactory.getLogger(TheEnd.class);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ConsumableProtection.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ConsumableProtection.java
index ce4e151443..e33b7ecbac 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ConsumableProtection.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ConsumableProtection.java
@@ -1,7 +1,5 @@
 package de.hysky.skyblocker.skyblock.item;
 
-import java.util.Set;
-
 import de.hysky.skyblocker.annotations.Init;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.utils.Utils;
@@ -13,6 +11,8 @@
 import net.minecraft.util.Hand;
 import net.minecraft.world.World;
 
+import java.util.Set;
+
 public class ConsumableProtection {
 	private static final Set<String> PROTECTED_CONSUMABLES = Set.of("NEW_BOTTLE_OF_JYRRE", "DARK_CACAO_TRUFFLE", "DISCRITE");
 
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
index cc0b3be8e0..b47494cd61 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/ItemRarityBackgrounds.java
@@ -12,7 +12,6 @@
 import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.client.texture.Sprite;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
deleted file mode 100644
index 84ceb82c11..0000000000
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/MuseumItemCache.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package de.hysky.skyblocker.skyblock.item;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.mojang.brigadier.Command;
-import com.mojang.brigadier.CommandDispatcher;
-import com.mojang.serialization.Codec;
-import com.mojang.serialization.codecs.RecordCodecBuilder;
-import com.mojang.util.UndashedUuid;
-import de.hysky.skyblocker.SkyblockerMod;
-import de.hysky.skyblocker.annotations.Init;
-import de.hysky.skyblocker.events.SkyblockEvents;
-import de.hysky.skyblocker.utils.Constants;
-import de.hysky.skyblocker.utils.Http;
-import de.hysky.skyblocker.utils.Http.ApiResponse;
-import de.hysky.skyblocker.utils.ItemUtils;
-import de.hysky.skyblocker.utils.Utils;
-import de.hysky.skyblocker.utils.profile.ProfiledData;
-import it.unimi.dsi.fastutil.objects.ObjectArrayList;
-import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
-import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
-import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
-import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
-import net.minecraft.command.CommandRegistryAccess;
-import net.minecraft.item.ItemStack;
-import net.minecraft.nbt.*;
-import net.minecraft.screen.slot.Slot;
-import net.minecraft.text.Text;
-import net.minecraft.util.collection.DefaultedList;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.ByteArrayInputStream;
-import java.nio.file.Path;
-import java.util.Base64;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
-
-import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
-
-public class MuseumItemCache {
-	private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class);
-	private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json");
-	private static final ProfiledData<ProfileMuseumData> MUSEUM_ITEM_CACHE = new ProfiledData<>(CACHE_FILE, ProfileMuseumData.CODEC, true, true);
-	private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}";
-	public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation";
-	private static final int CONFIRM_DONATION_BUTTON_SLOT = 20;
-
-	private static CompletableFuture<Void> loaded;
-
-	@Init
-	public static void init() {
-		ClientLifecycleEvents.CLIENT_STARTED.register(client -> loaded = MUSEUM_ITEM_CACHE.load());
-		ClientCommandRegistrationCallback.EVENT.register(MuseumItemCache::registerCommands);
-		SkyblockEvents.PROFILE_CHANGE.register((prev, profile) -> tick());
-	}
-
-	private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
-		dispatcher.register(literal(SkyblockerMod.NAMESPACE)
-				.then(literal("museum")
-						.then(literal("resync")
-								.executes(context -> {
-									if (tryResync(context.getSource())) {
-										context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.attemptingResync")));
-									} else {
-										context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.cannotResync")));
-									}
-
-									return Command.SINGLE_SUCCESS;
-								}))));
-	}
-
-	public static void handleClick(Slot slot, int slotId, DefaultedList<Slot> slots) {
-		if (slotId == CONFIRM_DONATION_BUTTON_SLOT) {
-			//Slots 0 to 17 can have items, well not all but thats the general range
-			for (int i = 0; i < 17; i++) {
-				ItemStack stack = slots.get(i).getStack();
-
-				if (!stack.isEmpty()) {
-					String itemId = ItemUtils.getItemId(stack);
-					String profileId = Utils.getProfileId();
-
-					if (!itemId.isEmpty() && !profileId.isEmpty()) {
-						MUSEUM_ITEM_CACHE.putIfAbsent(ProfileMuseumData.EMPTY.get()).collectedItemIds().add(itemId);
-						MUSEUM_ITEM_CACHE.save();
-					}
-				}
-			}
-		}
-	}
-
-	private static void updateData4ProfileMember(UUID uuid, String profileId) {
-		updateData4ProfileMember(uuid, profileId, null);
-	}
-
-	private static void updateData4ProfileMember(UUID uuid, String profileId, FabricClientCommandSource source) {
-		CompletableFuture.runAsync(() -> {
-			try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) {
-				//The request was successful
-				if (response.ok()) {
-					JsonObject profileData = JsonParser.parseString(response.content()).getAsJsonObject();
-					JsonObject members = profileData.getAsJsonObject("members");
-
-					String uuidString = UndashedUuid.toString(uuid);
-					if (members.has(uuidString)) {
-						JsonObject memberData = members.get(uuidString).getAsJsonObject();
-
-						//We call them sets because it could either be a singular item or an entire armour set
-						Map<String, JsonElement> donatedSets = memberData.get("items").getAsJsonObject().asMap();
-
-						//Set of all found item ids on profile
-						ObjectOpenHashSet<String> itemIds = new ObjectOpenHashSet<>();
-
-						for (Map.Entry<String, JsonElement> donatedSet : donatedSets.entrySet()) {
-							//Item is plural here because the nbt is a list
-							String itemsData = donatedSet.getValue().getAsJsonObject().get("items").getAsJsonObject().get("data").getAsString();
-							NbtList items = NbtIo.readCompressed(new ByteArrayInputStream(Base64.getDecoder().decode(itemsData)), NbtSizeTracker.ofUnlimitedBytes()).getList("i", NbtElement.COMPOUND_TYPE);
-
-							for (int i = 0; i < items.size(); i++) {
-								NbtCompound tag = items.getCompound(i).getCompound("tag");
-
-								if (tag.contains("ExtraAttributes")) {
-									NbtCompound extraAttributes = tag.getCompound("ExtraAttributes");
-
-									if (extraAttributes.contains("id")) itemIds.add(extraAttributes.getString("id"));
-								}
-							}
-						}
-
-						MUSEUM_ITEM_CACHE.put(uuid, profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds));
-						MUSEUM_ITEM_CACHE.save();
-
-						if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncSuccess")));
-
-						LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId);
-					} else {
-						//If the player's Museum API is disabled
-						putEmpty(uuid, profileId);
-
-						if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
-
-						LOGGER.warn(ERROR_LOG_TEMPLATE + " because the Museum API is disabled!", profileId);
-					}
-				} else {
-					//If the request returns a non 200 status code
-					putEmpty(uuid, profileId);
-
-					if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
-
-					LOGGER.error(ERROR_LOG_TEMPLATE + " because a non 200 status code was encountered! Status Code: {}", profileId, response.statusCode());
-				}
-			} catch (Exception e) {
-				//If an exception was somehow thrown
-				putEmpty(uuid, profileId);
-
-				if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
-
-				LOGGER.error(ERROR_LOG_TEMPLATE, profileId, e);
-			}
-		});
-	}
-
-	private static void putEmpty(UUID uuid, String profileId) {
-		//Only put new data if they didn't have any before
-		MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, profileId, () -> new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of()));
-		MUSEUM_ITEM_CACHE.save();
-	}
-
-	private static boolean tryResync(FabricClientCommandSource source) {
-		UUID uuid = Utils.getUuid();
-		String profileId = Utils.getProfileId();
-
-		//Only allow resyncing if the data is actually present yet, otherwise the player needs to swap servers for the tick method to be called
-		if (loaded.isDone() && !profileId.isEmpty() && MUSEUM_ITEM_CACHE.containsKey() && MUSEUM_ITEM_CACHE.get().canResync()) {
-			updateData4ProfileMember(uuid, profileId, source);
-
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * The cache is ticked upon switching Skyblock servers. Only loads from the API if the profile wasn't cached yet.
-	 */
-	public static void tick() {
-		UUID uuid = Utils.getUuid();
-
-		if (loaded.isDone() && !MUSEUM_ITEM_CACHE.containsKey()) {
-			MUSEUM_ITEM_CACHE.putIfAbsent(ProfileMuseumData.EMPTY.get());
-
-			updateData4ProfileMember(uuid, Utils.getProfileId());
-		}
-	}
-
-	public static boolean hasItemInMuseum(String id) {
-		return MUSEUM_ITEM_CACHE.containsKey() && MUSEUM_ITEM_CACHE.get().collectedItemIds().contains(id);
-	}
-
-	private record ProfileMuseumData(long lastResync, ObjectOpenHashSet<String> collectedItemIds) {
-		private static final Supplier<ProfileMuseumData> EMPTY = () -> new ProfileMuseumData(0L, new ObjectOpenHashSet<>());
-		private static final long TIME_BETWEEN_RESYNCING_ALLOWED = 600_000L;
-		private static final Codec<ProfileMuseumData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
-				Codec.LONG.fieldOf("lastResync").forGetter(ProfileMuseumData::lastResync),
-				Codec.STRING.listOf()
-						.xmap(ObjectOpenHashSet::new, ObjectArrayList::new)
-						.fieldOf("collectedItemIds")
-						.forGetter(ProfileMuseumData::collectedItemIds)
-		).apply(instance, ProfileMuseumData::new));
-
-		private boolean canResync() {
-			return this.lastResync + TIME_BETWEEN_RESYNCING_ALLOWED < System.currentTimeMillis();
-		}
-	}
-}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/PetInfo.java b/src/main/java/de/hysky/skyblocker/skyblock/item/PetInfo.java
index 02e3826b44..e16356a723 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/PetInfo.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/PetInfo.java
@@ -1,10 +1,10 @@
 package de.hysky.skyblocker.skyblock.item;
 
-import java.util.Optional;
-
 import com.mojang.serialization.Codec;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 
+import java.util.Optional;
+
 public record PetInfo(String type, double exp, SkyblockItemRarity tier, Optional<String> uuid, Optional<String> item, Optional<String> skin) {
 	public static final Codec<PetInfo> CODEC = RecordCodecBuilder.create(instance -> instance.group(
 			Codec.STRING.fieldOf("type").forGetter(PetInfo::type),
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
index 9ebf72a6e8..3030abd1fb 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/WikiLookup.java
@@ -9,7 +9,6 @@
 import net.minecraft.client.util.InputUtil;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.ItemStack;
-import net.minecraft.screen.slot.Slot;
 import net.minecraft.text.Text;
 import net.minecraft.util.Util;
 import org.jetbrains.annotations.NotNull;
@@ -33,17 +32,13 @@ public static void init() {
         ));
     }
 
-    public static void openWiki(@NotNull Slot slot, @NotNull PlayerEntity player) {
-        WikiLookup.openWiki(slot.getStack(), player);
+    public static void openWiki(@NotNull ItemStack itemStack, @NotNull PlayerEntity player) {
+        ItemUtils.getItemIdOptional(itemStack)
+                .map(ItemRepository::getWikiLink)
+                .ifPresentOrElse(wikiLink -> CompletableFuture.runAsync(() -> Util.getOperatingSystem().open(wikiLink)).exceptionally(e -> {
+                    LOGGER.error("[Skyblocker] Error while retrieving wiki article...", e);
+                    player.sendMessage(Constants.PREFIX.get().append("Error while retrieving wiki article, see logs..."), false);
+                    return null;
+                }), () -> player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.wikiLookup.noArticleFound")), false));
     }
-
-	public static void openWiki(ItemStack stack, PlayerEntity player) {
-		ItemUtils.getItemIdOptional(stack)
-				.map(ItemRepository::getWikiLink)
-				.ifPresentOrElse(wikiLink -> CompletableFuture.runAsync(() -> Util.getOperatingSystem().open(wikiLink)).exceptionally(e -> {
-					LOGGER.error("[Skyblocker] Error while retrieving wiki article...", e);
-					player.sendMessage(Constants.PREFIX.get().append("Error while retrieving wiki article, see logs..."), false);
-					return null;
-				}), () -> player.sendMessage(Constants.PREFIX.get().append(Text.translatable("skyblocker.wikiLookup.noArticleFound")), false));
-	}
 }
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java
index 1671936438..fbcd12aeed 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/AttributeShardAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java
index 87a67b9306..a804bebb5a 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CatacombsLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import de.hysky.skyblocker.utils.container.SlotTextAdder;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java
index 7104a61ddc..156d6cfc42 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CollectionAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
index 9d4a1470ef..b1f95a7a36 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/CommunityShopAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java
index 6755370abd..294c164b57 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/EnchantmentLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java
index 6b723d634b..2163f30687 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/MinionLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.Items;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java
index 96f3b32f4b..f205fa96e5 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PotionLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import net.minecraft.item.ItemStack;
 import net.minecraft.nbt.NbtCompound;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java
index 967c1cf25c..d2d4c447c9 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/PowerStonesGuideAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import net.minecraft.item.ItemStack;
 import net.minecraft.screen.slot.Slot;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java
index 60107abf04..f4e14f9777 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkillLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import de.hysky.skyblocker.utils.RomanNumerals;
 import net.minecraft.item.ItemStack;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java
index 6f6ff562d8..81859684d5 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/SkyblockLevelAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import net.minecraft.item.ItemStack;
 import net.minecraft.screen.slot.Slot;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java
index b5c70ca0ff..2f4fc5c99c 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/slottext/adders/StatsTuningAdder.java
@@ -1,7 +1,7 @@
 package de.hysky.skyblocker.skyblock.item.slottext.adders;
 
-import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.skyblock.item.slottext.SimpleSlotTextAdder;
+import de.hysky.skyblocker.skyblock.item.slottext.SlotText;
 import de.hysky.skyblocker.utils.ItemUtils;
 import net.minecraft.item.ItemStack;
 import net.minecraft.screen.slot.Slot;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java
index 943e855dca..c5e644eea8 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/CraftPriceTooltip.java
@@ -67,7 +67,7 @@ public void addToTooltip(@Nullable Slot focusedSloFt, ItemStack stack, List<Text
         }
     }
 
-    private double getItemCost(NEURecipe recipe, int depth) {
+    public static double getItemCost(NEURecipe recipe, int depth) {
         if (depth >= MAX_RECURSION_DEPTH) return -1;
 
         double totalCraftCost = 0;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java
index c4bf96c7d2..e52eefe25e 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/adders/MuseumTooltip.java
@@ -1,6 +1,6 @@
 package de.hysky.skyblocker.skyblock.item.tooltip.adders;
 
-import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
+import de.hysky.skyblocker.skyblock.museum.MuseumItemCache;
 import de.hysky.skyblocker.skyblock.item.tooltip.SimpleTooltipAdder;
 import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType;
 import de.hysky.skyblocker.utils.ItemUtils;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
index bff62731b6..0ed1f1d814 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/item/tooltip/info/TooltipInfoType.java
@@ -1,12 +1,6 @@
 package de.hysky.skyblocker.skyblock.item.tooltip.info;
 
-import java.util.Map;
-import java.util.function.BiPredicate;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
 import com.mojang.serialization.Codec;
-
 import de.hysky.skyblocker.config.SkyblockerConfig;
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.config.configs.GeneralConfig;
@@ -21,6 +15,11 @@
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
 
+import java.util.Map;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
 public interface TooltipInfoType {
 	DataTooltipInfoType<Object2DoubleMap<String>> NPC = ofData("https://hysky.de/api/npcprice", CodecUtils.object2DoubleMapCodec(Codec.STRING), true, Object2DoubleMap::containsKey, itemTooltip -> itemTooltip.enableNPCPrice);
 	DataTooltipInfoType<Object2ObjectMap<String, BazaarProduct>> BAZAAR = ofData("https://hysky.de/api/bazaar", BazaarProduct.MAP_CODEC, false, Object2ObjectMap::containsKey, itemTooltip -> itemTooltip.enableBazaarPrice, itemTooltip -> itemTooltip.enableBazaarPrice || itemTooltip.enableCraftingCost != Craft.OFF || itemTooltip.enableEstimatedItemValue || getConfig().dungeons.dungeonChestProfit.enableProfitCalculator || getConfig().dungeons.dungeonChestProfit.croesusProfit || getConfig().uiAndVisuals.chestValue.enableChestValue || SkyblockerConfigManager.get().helpers.enableBitsTooltip || itemTooltip.showEssenceCost, EssenceShopPrice::refreshEssencePrices);
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockCraftingRecipeResults.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockCraftingRecipeResults.java
index d6d7ea56f4..47f6e703d0 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockCraftingRecipeResults.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockCraftingRecipeResults.java
@@ -1,11 +1,6 @@
 package de.hysky.skyblocker.skyblock.itemlist.recipebook;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
 import com.google.common.collect.Lists;
-
 import de.hysky.skyblocker.config.SkyblockerConfigManager;
 import de.hysky.skyblocker.skyblock.item.WikiLookup;
 import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
@@ -16,7 +11,6 @@
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.font.TextRenderer;
 import net.minecraft.client.gui.DrawContext;
-import net.minecraft.client.gui.screen.Screen;
 import net.minecraft.client.gui.screen.recipebook.RecipeBookResults;
 import net.minecraft.client.gui.widget.ToggleButtonWidget;
 import net.minecraft.component.DataComponentTypes;
@@ -28,6 +22,10 @@
 import net.minecraft.util.Identifier;
 import net.minecraft.util.Language;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
 //TODO when in recipe view set search hint to talk about close or smth
 /**
  * Based off {@link net.minecraft.client.gui.screen.recipebook.RecipeBookResults}.
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeBookWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeBookWidget.java
index b1767acd0f..884715ad49 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeBookWidget.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeBookWidget.java
@@ -1,15 +1,8 @@
 package de.hysky.skyblocker.skyblock.itemlist.recipebook;
 
-import java.util.List;
-import java.util.Locale;
-
-import de.hysky.skyblocker.utils.render.gui.CyclingTextureWidget;
-import net.minecraft.screen.ScreenHandler;
-import org.jetbrains.annotations.Nullable;
-
 import com.google.common.collect.Lists;
-
 import de.hysky.skyblocker.mixins.accessors.RecipeBookWidgetAccessor;
+import de.hysky.skyblocker.utils.render.gui.CyclingTextureWidget;
 import it.unimi.dsi.fastutil.Pair;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
@@ -23,9 +16,14 @@
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.recipe.RecipeFinder;
 import net.minecraft.recipe.display.RecipeDisplay;
+import net.minecraft.screen.ScreenHandler;
 import net.minecraft.screen.slot.Slot;
 import net.minecraft.text.Text;
 import net.minecraft.util.context.ContextParameterMap;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+import java.util.Locale;
 
 /**
  * Based on {@link net.minecraft.client.gui.screen.recipebook.RecipeBookWidget}.
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeResultButton.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeResultButton.java
index abd93c6b2c..d026715e70 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeResultButton.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/SkyblockRecipeResultButton.java
@@ -1,8 +1,5 @@
 package de.hysky.skyblocker.skyblock.itemlist.recipebook;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.Screen;
@@ -14,6 +11,9 @@
 import net.minecraft.screen.ScreenTexts;
 import net.minecraft.text.Text;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class SkyblockRecipeResultButton extends ClickableWidget {
 	//Corresponds to AnimatedResultButton#field_32415
 	private static final int SIZE = 25;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/UpcomingEventsTab.java b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/UpcomingEventsTab.java
index c30df2943b..60646ca5ae 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/UpcomingEventsTab.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/itemlist/recipebook/UpcomingEventsTab.java
@@ -1,12 +1,5 @@
 package de.hysky.skyblocker.skyblock.itemlist.recipebook;
 
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.jetbrains.annotations.Nullable;
-
 import de.hysky.skyblocker.mixins.accessors.DrawContextInvoker;
 import de.hysky.skyblocker.skyblock.events.EventNotifications;
 import de.hysky.skyblocker.skyblock.tabhud.widget.JacobsContestWidget;
@@ -24,6 +17,12 @@
 import net.minecraft.text.Text;
 import net.minecraft.util.Colors;
 import net.minecraft.util.Formatting;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
 
 public class UpcomingEventsTab implements RecipeTab {
 	private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java
new file mode 100644
index 0000000000..276daaa7a1
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/Donation.java
@@ -0,0 +1,112 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
+import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Donation {
+    private final String category;
+    private final String id;
+    private final List<ObjectObjectMutablePair<String, PriceData>> set;
+    private final List<String> downgrades = new ArrayList<>();
+    private final int xp;
+	private List<ObjectIntPair<String>> countsTowards;
+    private PriceData priceData;
+    private ObjectDoublePair<String> discount;
+    private int totalXp;
+    private double xpCoinsRatio;
+
+    public Donation(String category, String id, List<ObjectObjectMutablePair<String, PriceData>> set, int xp) {
+        this.category = category;
+        this.id = id;
+        this.set = set;
+        this.xp = xp;
+    }
+
+    public int getTotalXp() {
+        return totalXp;
+    }
+
+    public void setTotalXp(int totalXp) {
+        this.totalXp = totalXp;
+    }
+
+    public List<ObjectIntPair<String>> getCountsTowards() {
+        return countsTowards;
+    }
+
+    public void setCountsTowards(List<ObjectIntPair<String>> countsTowards) {
+        this.countsTowards = countsTowards;
+    }
+
+    public PriceData getPriceData() {
+        return priceData;
+    }
+
+    public void setPriceData() {
+        this.priceData = new PriceData(this);
+    }
+
+    public ObjectDoublePair<String> getDiscount() {
+        return discount;
+    }
+
+    public void setDiscount(ObjectDoublePair<String> discount) {
+        this.discount = discount;
+    }
+
+    public boolean hasDiscount() {
+        return discount != null && discount.rightDouble() > 0d;
+    }
+
+    public void addDowngrade(String downgrade) {
+        this.downgrades.add(downgrade);
+    }
+
+    public List<String> getDowngrades() {
+        return downgrades;
+    }
+
+    public double getXpCoinsRatio() {
+        return xpCoinsRatio;
+    }
+
+    public void setXpCoinsRatio(double xpCoinsRatio) {
+        this.xpCoinsRatio = xpCoinsRatio;
+    }
+
+    public String getCategory() {
+        return category;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public boolean isSet() {
+        return !set.isEmpty();
+    }
+
+    public List<ObjectObjectMutablePair<String, PriceData>> getSet() {
+        return set;
+    }
+
+    public int getXp() {
+        return xp;
+    }
+
+    public boolean isCraftable() {
+        return this.priceData.getCraftCost() > 0;
+    }
+
+    public boolean hasLBinPrice() {
+        return this.priceData.getLBinPrice() > 0;
+    }
+
+    public boolean hasPrice() {
+        return this.priceData.getEffectivePrice() > 0;
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java
new file mode 100644
index 0000000000..5f3e52121f
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/DonationButton.java
@@ -0,0 +1,174 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import de.hysky.skyblocker.skyblock.item.WikiLookup;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.utils.ItemUtils;
+import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
+import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.screen.recipebook.AnimatedResultButton;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.ScreenTexts;
+import net.minecraft.text.MutableText;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class DonationButton extends ClickableWidget {
+    private static final int SIZE = 33;
+    private static final int ITEM_OFFSET = 8;
+    private static final TextRenderer TEXT_RENDERER = MinecraftClient.getInstance().textRenderer;
+    private Donation donation = null;
+    private ItemStack itemStack = null;
+    private String textToRender;
+    private List<Text> tooltip;
+
+    protected DonationButton(int x, int y) {
+        super(x, y, SIZE, SIZE + 2, ScreenTexts.EMPTY);
+    }
+
+    protected ItemStack getDisplayStack() {
+        return this.itemStack;
+    }
+
+    /**
+     * Initializes the button with a donation object.
+     *
+     * @param donation The donation to associate with this button.
+     */
+    public void init(Donation donation) {
+		this.visible = false;
+        this.donation = donation;
+        this.textToRender = MuseumUtils.formatPrice(donation.getPriceData().getEffectivePrice());
+
+        // Determine the item stack to display
+        this.itemStack = !donation.isSet()
+                ? ItemRepository.getItemStack(donation.getId())
+                : ItemRepository.getItemStack(
+                donation.getSet().stream()
+                        .filter(piece -> piece.left().toLowerCase(Locale.ENGLISH).contains("helmet") || piece.left().toLowerCase(Locale.ENGLISH).contains("hat"))
+                        .findFirst()
+                        .orElse(donation.getSet().get(1)) // gets chestplate
+                        .left()
+        );
+
+        if (itemStack != null && !itemStack.isEmpty()) {
+            this.visible = true;
+            createTooltip();
+        }
+    }
+
+
+    /**
+     * Clears the display stack and resets the button state.
+     */
+    protected void clearDisplayStack() {
+        this.visible = false;
+        this.donation = null;
+        this.itemStack = null;
+        this.tooltip = null;
+        this.textToRender = null;
+    }
+
+    @Override
+    protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+        context.drawGuiTexture(RenderLayer::getGuiTextured, AnimatedResultButton.SLOT_CRAFTABLE_TEXTURE, this.getX(), this.getY(), this.width, this.height);
+
+        int yOffset = 8;
+
+        if (donation.hasPrice()) {
+            context.drawCenteredTextWithShadow(TEXT_RENDERER, textToRender, this.getX() + (this.width / 2), this.getY() + ITEM_OFFSET + 13, 0xFF00FF00);
+
+            yOffset -= 4;
+        }
+
+        context.drawItemWithoutEntity(itemStack, this.getX() + ITEM_OFFSET, this.getY() + yOffset);
+
+    }
+
+    /**
+     * Creates the tooltip for the button based on its associated donation data
+     */
+    private void createTooltip() {
+        final String WIKI_LOCKUP_KEY = WikiLookup.wikiLookup.getBoundKeyTranslationKey();
+        List<Text> tooltip = new ArrayList<>();
+
+        boolean soulbound = ItemUtils.isSoulbound(itemStack);
+        ObjectDoublePair<String> discount = donation.getDiscount();
+        List<ObjectIntPair<String>> countsTowards = donation.getCountsTowards();
+
+        // Display name
+        tooltip.add(MuseumUtils.getDisplayName(donation.getId(), donation.isSet()));
+
+        // Set pieces display names
+        if (donation.isSet()) {
+            for (ObjectObjectMutablePair<String, PriceData> piece : donation.getSet()) {
+                ItemStack stack = ItemRepository.getItemStack(piece.left());
+                if (stack != null) {
+                    Text itemName = stack.getName().copy();
+                    if (soulbound) {
+                        tooltip.add(Text.literal("  ").append(itemName));
+                    } else if (piece.right().getEffectivePrice() > 0) {
+                        tooltip.add(Text.literal("  ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.literal(MuseumUtils.formatPrice(piece.right().getEffectivePrice())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)));
+                    } else {
+                        tooltip.add(Text.literal("  ").append(itemName).append(Text.literal(" (").formatted(Formatting.DARK_GRAY)).append(Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)));
+                    }
+                }
+            }
+        }
+        tooltip.add(Text.empty());
+
+        Text xpText = Text.literal(String.valueOf(donation.getTotalXp())).append(" ").append(Text.translatable("skyblocker.museum.hud.skyblockXp")).formatted(Formatting.AQUA);
+        tooltip.add(Text.translatable("skyblocker.museum.hud.xpReward").append(": ").formatted(Formatting.GRAY).append(xpText));
+
+        if (soulbound) {
+            tooltip.add(Text.translatable("skyblocker.museum.hud.untradeableItem").formatted(Formatting.GRAY).append(Text.literal(" (").append(Text.translatable("skyblocker.museum.hud.soulboundItem")).append(")").formatted(Formatting.DARK_GRAY)));
+        } else {
+            PriceData priceData = donation.getPriceData();
+            Text lBinText = donation.hasLBinPrice() ? Text.literal(MuseumUtils.formatPrice(priceData.getLBinPrice())).append(" Coins").formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED);
+            MutableText craftCostText = donation.isCraftable() ? Text.literal(MuseumUtils.formatPrice(donation.hasDiscount() ? priceData.getCraftCost() - discount.rightDouble() : priceData.getCraftCost())).append(" ").append(Text.translatable("skyblocker.museum.hud.coin")).formatted(Formatting.GOLD) : Text.translatable("skyblocker.museum.hud.unknownPrice").formatted(Formatting.RED);
+            Text discountText = donation.hasDiscount() && donation.isCraftable() ? Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(priceData.getCraftCost())).formatted(Formatting.GOLD)).append(" - ").append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(Text.literal(")").formatted(Formatting.DARK_GRAY)) : Text.empty();
+            Text xpCoinsRatio = Text.literal(MuseumUtils.formatPrice(donation.getXpCoinsRatio())).append(" ").append(Text.translatable("skyblocker.museum.hud.ratioText")).formatted(Formatting.AQUA);
+
+            tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.lBin").append(": ").formatted(Formatting.GRAY).append(lBinText));
+            tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.craftCost").append(": ").formatted(Formatting.GRAY).append(craftCostText).append(discountText));
+            tooltip.add(Text.translatable("skyblocker.museum.hud.sorter.ratio").append(": ").formatted(Formatting.GRAY).append(xpCoinsRatio));
+        }
+
+        if (countsTowards.size() > 1) {
+            tooltip.add(Text.empty());
+            tooltip.add(Text.translatable("skyblocker.museum.hud.countsFor").append(":").formatted(Formatting.GRAY));
+            for (ObjectIntPair<String> credit : countsTowards) {
+                tooltip.add(Text.literal(" ● ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(credit.left(), donation.isSet())).append(Text.literal(" (" + credit.rightInt() + " XP)").formatted(Formatting.AQUA)));
+            }
+        }
+
+        if (donation.isCraftable() && donation.hasDiscount()) {
+            tooltip.add(Text.empty());
+            tooltip.add(Text.translatable("skyblocker.museum.hud.craftIngredient").append(": ").formatted(Formatting.GRAY).append(Text.literal("(").append(Text.translatable("skyblocker.museum.hud.alreadyDonatedItem").append(")")).formatted(Formatting.DARK_GRAY)));
+            tooltip.add(Text.literal(" - ").formatted(Formatting.GRAY).append(MuseumUtils.getDisplayName(discount.left(), donation.isSet())).append(Text.literal(" ✔").formatted(Formatting.GREEN)).append(Text.literal(" (").formatted(Formatting.DARK_GRAY).append(Text.literal(MuseumUtils.formatPrice(discount.rightDouble())).formatted(Formatting.GOLD)).append(")").formatted(Formatting.DARK_GRAY)));
+        }
+
+        tooltip.add(Text.empty());
+        tooltip.add(Text.translatable("skyblocker.museum.hud.wikiLookup", WIKI_LOCKUP_KEY.substring(WIKI_LOCKUP_KEY.lastIndexOf('.') + 1).toUpperCase(Locale.ENGLISH)).formatted(Formatting.YELLOW));
+
+        this.tooltip = tooltip;
+    }
+
+    protected List<Text> getItemTooltip() {
+        return this.tooltip;
+    }
+
+
+    @Override
+    protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java
new file mode 100644
index 0000000000..eaf0baf909
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemFilter.java
@@ -0,0 +1,106 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import net.minecraft.client.gui.tooltip.Tooltip;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+public class ItemFilter {
+    private FilterMode currentFilterMode = FilterMode.ALL;
+
+    // Filtering logic methods
+    private static List<Donation> filterAll(List<Donation> donations) {
+        return donations;
+    }
+
+    private static List<Donation> filterWeapons(List<Donation> donations) {
+        return donations.stream()
+                .filter(donation -> "weapons".equals(donation.getCategory()))
+                .collect(Collectors.toList());
+    }
+
+    private static List<Donation> filterArmor(List<Donation> donations) {
+        return donations.stream()
+                .filter(donation -> "armor".equals(donation.getCategory()))
+                .collect(Collectors.toList());
+    }
+
+    private static List<Donation> filterRarities(List<Donation> donations) {
+        return donations.stream()
+                .filter(donation -> "rarities".equals(donation.getCategory()))
+                .collect(Collectors.toList());
+    }
+
+    // Method to cycle through filtering modes and apply the corresponding logic
+    public void cycleFilterMode(List<Donation> items, List<Donation> filteredList) {
+        // Cycle to the next filter mode
+        currentFilterMode = FilterMode.values()[(currentFilterMode.ordinal() + 1) % FilterMode.values().length];
+        // Apply the filtering logic for the current mode
+        currentFilterMode.applyFilter(items, filteredList);
+    }
+
+    public void applyFilter(List<Donation> items, List<Donation> filteredList) {
+        currentFilterMode.applyFilter(items, filteredList);
+    }
+
+    // Get the item associated with the current filter mode
+    public ItemStack getCurrentFilterItem() {
+        return currentFilterMode.getAssociatedItem();
+    }
+
+    public void resetFilter() {
+        this.currentFilterMode = FilterMode.ALL;
+    }
+
+    public Tooltip getTooltip() {
+        Text tooltip = Text.translatable("skyblocker.museum.hud.filter").append("\n\n").formatted(Formatting.GREEN)
+                .append(getFilterText(FilterMode.ALL))
+                .append(getFilterText(FilterMode.WEAPONS))
+                .append(getFilterText(FilterMode.ARMOR))
+                .append(getFilterText(FilterMode.RARITIES))
+                .append("\n").append(Text.translatable("skyblocker.museum.hud.filter.switch").formatted(Formatting.YELLOW));
+        return Tooltip.of(tooltip);
+    }
+
+    private Text getFilterText(FilterMode mode) {
+        boolean isCurrent = mode == currentFilterMode;
+        return Text.literal((isCurrent ? "➤ " : "  ")).append(mode.getDisplayName()).append("\n")
+                .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY);
+    }
+
+    public enum FilterMode {
+        ALL(new ItemStack(Items.NETHER_STAR), ItemFilter::filterAll, Text.translatable("skyblocker.museum.hud.filter.all")),
+        WEAPONS(new ItemStack(Items.DIAMOND_SWORD), ItemFilter::filterWeapons, Text.translatable("skyblocker.museum.hud.filter.weapons")),
+        ARMOR(new ItemStack(Items.DIAMOND_CHESTPLATE), ItemFilter::filterArmor, Text.translatable("skyblocker.museum.hud.filter.armor")),
+        RARITIES(ItemRepository.getItemStack("JADERALD"), ItemFilter::filterRarities, Text.translatable("skyblocker.museum.hud.filter.rarities"));
+
+        private final ItemStack associatedItem;
+        private final UnaryOperator<List<Donation>> filterFunction;
+        private final Text displayName;
+
+        FilterMode(ItemStack item, UnaryOperator<List<Donation>> function, Text displayName) {
+            this.associatedItem = item;
+            this.filterFunction = function;
+            this.displayName = displayName;
+        }
+
+        public ItemStack getAssociatedItem() {
+            return associatedItem;
+        }
+
+        public Text getDisplayName() {
+            return displayName;
+        }
+
+        public void applyFilter(List<Donation> items, List<Donation> filteredList) {
+            filteredList.clear();
+            filteredList.addAll(filterFunction.apply(items));
+        }
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java
new file mode 100644
index 0000000000..9d9a4ff04e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/ItemSorter.java
@@ -0,0 +1,160 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import it.unimi.dsi.fastutil.objects.ObjectDoublePair;
+import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import net.minecraft.client.gui.tooltip.Tooltip;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class ItemSorter {
+    // Sorting logic
+    private static final Consumer<List<Donation>> sortByLowestBIN = donations -> {
+        donations.forEach(donation -> updateDonationData(donation, false));
+        donations.sort(ItemSorter::compareEffectivePrices);
+    };
+    private static final Consumer<List<Donation>> sortByCraftCost = donations -> {
+        donations.forEach(donation -> updateDonationData(donation, true));
+        donations.sort(ItemSorter::compareEffectivePrices);
+    };
+    private static final Consumer<List<Donation>> sortByXpPerCoin = donations -> {
+        donations.forEach(donation -> updateDonationData(donation, true));
+        donations.sort(ItemSorter::compareCoinsPerXP);
+    };
+    private SortMode currentSortMode = SortMode.LOWEST_BIN;
+
+    // Set effective prices for the donation and its armor set pieces
+    public static void updateDonationData(Donation donation, boolean useCraftCost) {
+        // Gather all donations that this one counts towards
+        List<String> downgrades = donation.getDowngrades();
+        ObjectDoublePair<String> discount = donation.getDiscount();
+        List<Donation> willCountFor = downgrades.stream()
+                .map(MuseumManager::getDonation)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        willCountFor.addFirst(donation); // Ensure donation itself is part of the list
+
+        // Calculate cumulative XP
+        int totalXP = willCountFor.stream().mapToInt(Donation::getXp).sum();
+
+        // Calculate effective prices
+        double lBinPrice = donation.getPriceData().getLBinPrice();
+        double rawCraftCost = donation.isCraftable() ? donation.getPriceData().getCraftCost() : 0;
+        double craftCost = discount != null ? rawCraftCost - discount.rightDouble() : rawCraftCost;
+        double effectivePrice = useCraftCost
+                ? (craftCost > 0 ? (lBinPrice == 0 ? craftCost : Math.min(craftCost, lBinPrice)) : lBinPrice)
+                : (lBinPrice > 0 ? lBinPrice : craftCost);
+        double ratio = totalXP > 0 && effectivePrice > 0 ? effectivePrice / totalXP : 0;
+
+        // Update donation with computed data
+        if (donation.isSet())
+            donation.getSet().forEach(pair -> pair.right().setEffectivePrice(effectivePrice == craftCost ? pair.right().getCraftCost() : pair.right().getLBinPrice()));
+        donation.getPriceData().setEffectivePrice(effectivePrice);
+        donation.setXpCoinsRatio(ratio);
+        donation.setTotalXp(totalXP);
+        donation.setCountsTowards(willCountFor.stream()
+                .map(d -> ObjectIntPair.of(d.getId(), d.getXp()))
+                .toList());
+    }
+
+    private static int compareEffectivePrices(Donation a, Donation b) {
+        double priceA = a.getPriceData().getEffectivePrice();
+        double priceB = b.getPriceData().getEffectivePrice();
+
+        if (priceA <= 0 && priceB <= 0) {
+            // Both prices are invalid, sort by XP descending
+            return Integer.compare(b.getTotalXp(), a.getTotalXp());
+        }
+        if (priceA <= 0) return 1; // Move invalid price to the end
+        if (priceB <= 0) return -1; // Move invalid price to the end
+
+        // Compare valid prices in ascending order
+        return Double.compare(priceA, priceB);
+    }
+
+    private static int compareCoinsPerXP(Donation a, Donation b) {
+        double xpPerCoinA = a.getXpCoinsRatio();
+        double xpPerCoinB = b.getXpCoinsRatio();
+
+        if (xpPerCoinA == 0 && xpPerCoinB == 0) {
+            // Both ratios are 0, sort by XP descending
+            return Integer.compare(b.getTotalXp(), a.getTotalXp());
+        }
+        if (xpPerCoinA == 0) return 1; // Move items with 0 XP/coin to the end
+        if (xpPerCoinB == 0) return -1; // Move items with 0 XP/coin to the end
+
+        // Compare XP/coin ratios in descending order
+        return Double.compare(xpPerCoinA, xpPerCoinB);
+    }
+
+    // Method to cycle through sorting modes and apply the corresponding logic
+    public void cycleSortMode(List<Donation> donations) {
+        // Cycle to the next sorting mode
+        currentSortMode = SortMode.values()[(currentSortMode.ordinal() + 1) % SortMode.values().length];
+        // Apply the sorting logic for the current mode
+        applySort(donations);
+    }
+
+    public void applySort(List<Donation> donations) {
+        currentSortMode.getSortFunction().accept(donations);
+    }
+
+    // Get the item associated with the current filter mode
+    public ItemStack getCurrentSortingItem() {
+        return currentSortMode.getAssociatedItem();
+    }
+
+    public void resetSorting() {
+        this.currentSortMode = SortMode.LOWEST_BIN;
+    }
+
+    public Tooltip getTooltip() {
+        Text tooltip = Text.translatable("skyblocker.museum.hud.sorter").append("\n\n").formatted(Formatting.GREEN)
+                .append(getSortText(SortMode.LOWEST_BIN))
+                .append(getSortText(SortMode.CRAFT_COST))
+                .append(getSortText(SortMode.COINS_PER_XP))
+                .append("\n").append(Text.translatable("skyblocker.museum.hud.sorter.switch").formatted(Formatting.YELLOW));
+        return Tooltip.of(tooltip);
+    }
+
+    private Text getSortText(SortMode mode) {
+        boolean isCurrent = mode == currentSortMode;
+        return Text.literal((isCurrent ? "➤ " : "  ")).append(mode.getDisplayName()).append("\n")
+                .formatted(isCurrent ? Formatting.AQUA : Formatting.GRAY);
+    }
+
+    public enum SortMode {
+        LOWEST_BIN(new ItemStack(Items.GOLD_INGOT), sortByLowestBIN, Text.translatable("skyblocker.museum.hud.sorter.lBin")),
+        CRAFT_COST(new ItemStack(Items.CRAFTING_TABLE), sortByCraftCost, Text.translatable("skyblocker.museum.hud.sorter.craftCost")),
+        COINS_PER_XP(new ItemStack(Items.EXPERIENCE_BOTTLE), sortByXpPerCoin, Text.translatable("skyblocker.museum.hud.sorter.ratio"));
+
+        private final ItemStack associatedItem;
+        private final Consumer<List<Donation>> sortFunction;
+        private final Text displayName;
+
+        SortMode(ItemStack item, Consumer<List<Donation>> function, Text displayName) {
+            this.associatedItem = item;
+            this.sortFunction = function;
+            this.displayName = displayName;
+        }
+
+        public ItemStack getAssociatedItem() {
+            return associatedItem;
+        }
+
+        public Text getDisplayName() {
+            return displayName;
+        }
+
+        public Consumer<List<Donation>> getSortFunction() {
+            return sortFunction;
+        }
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java
new file mode 100644
index 0000000000..020803165d
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumItemCache.java
@@ -0,0 +1,393 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import com.mojang.util.UndashedUuid;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.annotations.Init;
+import de.hysky.skyblocker.events.SkyblockEvents;
+import de.hysky.skyblocker.utils.*;
+import de.hysky.skyblocker.utils.Http.ApiResponse;
+import de.hysky.skyblocker.utils.profile.ProfiledData;
+import io.github.moulberry.repo.NEURepoFile;
+import it.unimi.dsi.fastutil.objects.*;
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
+import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
+import net.minecraft.command.CommandRegistryAccess;
+import net.minecraft.item.ItemStack;
+import net.minecraft.screen.slot.Slot;
+import net.minecraft.text.Text;
+import net.minecraft.util.collection.DefaultedList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Supplier;
+
+import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.literal;
+
+public class MuseumItemCache {
+	private static final Logger LOGGER = LoggerFactory.getLogger(MuseumItemCache.class);
+	private static final String ERROR_LOG_TEMPLATE = "[Skyblocker] Failed to refresh museum item data for profile {}";
+	private static final int CONFIRM_DONATION_BUTTON_SLOT = 20;
+	private static final String CONSTANTS_MUSEUM_DATA = "constants/museum.json";
+	private static final Path CACHE_FILE = SkyblockerMod.CONFIG_DIR.resolve("museum_item_cache.json");
+	private static final ProfiledData<ProfileMuseumData> MUSEUM_ITEM_CACHE = new ProfiledData<>(CACHE_FILE, ProfileMuseumData.CODEC, true, true);
+	public static final String DONATION_CONFIRMATION_SCREEN_TITLE = "Confirm Donation";
+	public static final Map<String, String> ARMOR_NAMES = new Object2ObjectArrayMap<>();
+	public static final Map<String, String> MAPPED_IDS = new Object2ObjectArrayMap<>();
+	public static final ObjectArrayList<Donation> MUSEUM_DONATIONS = new ObjectArrayList<>();
+	public static final ObjectArrayList<ObjectArrayList<String>> ORDERED_UPGRADES = new ObjectArrayList<>();
+	private static CompletableFuture<Void> loaded;
+
+	@Init
+	public static void init() {
+		loadMuseumItems();
+		ClientLifecycleEvents.CLIENT_STARTED.register(client -> loaded = MUSEUM_ITEM_CACHE.load());
+		ClientCommandRegistrationCallback.EVENT.register(MuseumItemCache::registerCommands);
+		SkyblockEvents.PROFILE_CHANGE.register((prev, profile) -> tick());
+	}
+
+	private static void registerCommands(CommandDispatcher<FabricClientCommandSource> dispatcher, CommandRegistryAccess registryAccess) {
+		dispatcher.register(literal(SkyblockerMod.NAMESPACE)
+				.then(literal("museum")
+						.then(literal("resync")
+								.executes(context -> {
+									if (tryResync(context.getSource())) {
+										context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.attemptingResync")));
+									} else {
+										context.getSource().sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.cannotResync")));
+									}
+
+									return Command.SINGLE_SUCCESS;
+								}))));
+	}
+
+	/**
+	 * Loads museum data from local repo.
+	 */
+	public static void loadMuseumItems() {
+		NEURepoManager.runAsyncAfterLoad(() -> {
+			NEURepoFile filePath = NEURepoManager.NEU_REPO.file(CONSTANTS_MUSEUM_DATA);
+			if (filePath == null) return;
+			try (BufferedReader reader = Files.newBufferedReader(filePath.getFsPath())) {
+				// Parse the JSON file
+				JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
+
+				Map<String, JsonElement> setExceptions = json.get("set_exceptions").getAsJsonObject().asMap();
+				Map<String, JsonElement> mappedIds = json.get("mapped_ids").getAsJsonObject().asMap();
+				Map<String, JsonElement> itemToXp = json.get("itemToXp").getAsJsonObject().asMap();
+				Map<String, JsonElement> setsToItems = json.get("sets_to_items").getAsJsonObject().asMap();
+				Map<String, JsonElement> children = json.get("children").getAsJsonObject().asMap();
+
+				Map<String, JsonArray> allDonations = Map.of(
+						"weapons", json.get("weapons").getAsJsonArray(),
+						"armor", json.get("armor").getAsJsonArray(),
+						"rarities", json.get("rarities").getAsJsonArray()
+				);
+
+				mappedIds.forEach((s, jsonElement) -> MAPPED_IDS.put(s, jsonElement.getAsString()));
+
+				for (Map.Entry<String, JsonArray> entry : allDonations.entrySet()) {
+					String category = entry.getKey();
+					JsonArray array = entry.getValue();
+
+					for (JsonElement element : array) {
+						String itemID = element.getAsString();
+						List<ObjectObjectMutablePair<String, PriceData>> set = new ArrayList<>();
+						if (category.equals("armor")) {
+							boolean isEquipment = true;
+							for (JsonElement jsonElement : setsToItems.get(itemID).getAsJsonArray()) {
+								if (isEquipment) isEquipment = ItemUtils.isEquipment(jsonElement.getAsString());
+								set.add(new ObjectObjectMutablePair<>(jsonElement.getAsString(), null));
+							}
+							String realId = itemID;
+							for (Map.Entry<String, JsonElement> exception : setExceptions.entrySet()) {
+								if (exception.getValue().getAsString().equals(itemID)) {
+									realId = exception.getKey();
+									break;
+								}
+							}
+							ARMOR_NAMES.put(itemID, MuseumUtils.formatArmorName(realId, isEquipment));
+						}
+						int itemXP = itemToXp.get(itemID).getAsInt();
+						List<String> upgrades = getUpgrades(children, itemID);
+
+						if (!upgrades.isEmpty()) {
+							// Try to find an existing upgrade list that either contains itemID or overlaps with upgrades
+							Optional<ObjectArrayList<String>> matchingUpgrade = ORDERED_UPGRADES.stream()
+									.filter(orderedUpgrade ->
+											orderedUpgrade.contains(itemID) ||
+													!Collections.disjoint(orderedUpgrade, upgrades))
+									.findFirst();
+
+							if (matchingUpgrade.isPresent()) {
+								List<String> orderedUpgrade = matchingUpgrade.get();
+								// If the matching list has fewer or equal items, replace it with the new upgrade list
+								if (orderedUpgrade.size() <= upgrades.size()) {
+									orderedUpgrade.clear();
+									orderedUpgrade.add(itemID);
+									orderedUpgrade.addAll(upgrades);
+								}
+							} else {
+								// If no match, add a new upgrade list with itemID and upgrades
+								ObjectArrayList<String> newUpgrade = new ObjectArrayList<>();
+								newUpgrade.add(itemID);
+								newUpgrade.addAll(upgrades);
+								ORDERED_UPGRADES.add(newUpgrade);
+							}
+						}
+
+						MUSEUM_DONATIONS.add(new Donation(category, itemID, set, itemXP));
+					}
+				}
+
+				MUSEUM_DONATIONS.forEach(donation -> {
+					for (List<String> list : ORDERED_UPGRADES) {
+						int armorIndex = list.indexOf(donation.getId());
+						if (armorIndex > 0) {
+							for (int i = armorIndex - 1; i >= 0; i--) {
+								donation.addDowngrade(list.get(i));
+							}
+						}
+					}
+				});
+
+				LOGGER.info("[Skyblocker] Loaded museum data");
+			} catch (NoSuchFileException ignored) {
+			} catch (IOException e) {
+				LOGGER.error("[Skyblocker] Failed to load donations data", e);
+			}
+		});
+	}
+
+	/**
+	 * Gets a list of all upgrades for a given item.
+	 */
+	public static List<String> getUpgrades(Map<String, JsonElement> children, String item) {
+		List<String> upgrades = new ArrayList<>();
+		String currentItem = item;
+
+		while (true) {
+			// Find the parent item
+			String finalCurrentItem = currentItem;
+			String parentItem = children.entrySet().stream()
+					.filter(e -> e.getValue().getAsString().equals(finalCurrentItem))
+					.map(Map.Entry::getKey)
+					.findFirst()
+					.orElse(null);
+
+			if (parentItem == null) {
+				break; // No more parents found, exit the loop
+			}
+
+			upgrades.add(parentItem); // Add the parent to the upgrades list
+			currentItem = parentItem; // Move to the parent and repeat
+		}
+
+		return upgrades;
+	}
+
+	/**
+	 * Retrieves a list of donations that the player has not yet contributed.
+	 */
+	public static List<Donation> getDonations() {
+		List<Donation> uncontributedItems = new ArrayList<>();
+
+		if (MUSEUM_ITEM_CACHE.containsKey()) {
+			ObjectOpenHashSet<String> items = MUSEUM_ITEM_CACHE.get().collectedItemIds();
+			for (Donation donation : MUSEUM_DONATIONS) {
+				// Check if the donation id or his upgrades is not present in the collected items
+				if (!items.contains(donation.getId())) {
+					if (donation.isSet() && items.stream().anyMatch(i -> donation.getSet().stream().anyMatch(p -> p.left().equals(i)))) continue;
+					donation.setPriceData();
+					uncontributedItems.add(donation);
+				}
+			}
+
+			// Check if the item has a donated downgrade
+			uncontributedItems.forEach(donation -> donation.setDiscount(donation.getDowngrades().stream()
+					.filter(downgrade -> donation.isCraftable())
+					.filter(downgrade -> uncontributedItems.stream().noneMatch(item -> item.getId().equals(downgrade)))
+					.map(downgrade -> ObjectDoublePair.of(downgrade, MuseumUtils.getSetCraftCost(downgrade)))
+					.findFirst()
+					.orElse(null)));
+
+			uncontributedItems.sort(Comparator.comparing(Donation::getId)); //Sorting alphabetically
+		}
+		return uncontributedItems;
+	}
+
+	public static void handleClick(Slot slot, int slotId, DefaultedList<Slot> slots) {
+		if (slotId == CONFIRM_DONATION_BUTTON_SLOT) {
+			String profileId = Utils.getProfileId();
+
+			if (!profileId.isEmpty()) {
+				//Slots 0 to 17 can have items, well not all but thats the general range
+				for (int i = 0; i < 17; i++) {
+					ItemStack stack = slots.get(i).getStack();
+
+					if (!stack.isEmpty()) {
+						String itemId = ItemUtils.getItemId(stack);
+
+						if (!itemId.isEmpty()) {
+							ProfileMuseumData data = MUSEUM_ITEM_CACHE.putIfAbsent(ProfileMuseumData.EMPTY.get());
+
+							if (MAPPED_IDS.containsKey(itemId)) itemId = MAPPED_IDS.get(itemId);
+							String setId = MuseumUtils.getSetID(itemId);
+							Donation donation = MuseumManager.getDonation(setId != null ? setId : itemId);
+
+							data.collectedItemIds().add(itemId);
+
+							if (setId != null) data.collectedItemIds().add(setId);
+							if (donation != null && !donation.getDowngrades().isEmpty()) {
+								for (String downgrade : donation.getDowngrades()) {
+									if (donation.isSet()) {
+										List<String> pieces = MuseumUtils.getPiecesBySetID(downgrade);
+										data.collectedItemIds().addAll(pieces);
+									}
+									data.collectedItemIds().add(downgrade);
+								}
+							}
+						}
+					}
+				}
+				MUSEUM_ITEM_CACHE.save();
+			}
+		}
+	}
+
+	private static void updateData4ProfileMember(UUID uuid, String profileId) {
+		updateData4ProfileMember(uuid, profileId, null);
+	}
+
+	private static void updateData4ProfileMember(UUID uuid, String profileId, FabricClientCommandSource source) {
+		CompletableFuture.runAsync(() -> {
+			try (ApiResponse response = Http.sendHypixelRequest("skyblock/museum", "?profile=" + profileId)) {
+				//The request was successful
+				if (response.ok()) {
+					JsonObject profileData = JsonParser.parseString(response.content()).getAsJsonObject();
+					JsonObject members = profileData.getAsJsonObject("members");
+
+					String uuidString = UndashedUuid.toString(uuid);
+					if (members.has(uuidString)) {
+						JsonObject memberData = members.get(uuidString).getAsJsonObject();
+
+						//We call them sets because it could either be a singular item or an entire armour set
+						Map<String, JsonElement> donatedSets = memberData.get("items").getAsJsonObject().asMap();
+						//Set of all found item ids on profile
+						ObjectOpenHashSet<String> itemIds = new ObjectOpenHashSet<>();
+
+						donatedSets.forEach((s, __) -> {
+							Optional<Donation> donation = MUSEUM_DONATIONS.stream().filter(d -> d.getId().equals(s)).findFirst();
+							donation.ifPresent(value -> itemIds.addAll(value.getDowngrades()));
+							if (donation.isPresent()) {
+								if (donation.get().isSet()) {
+									itemIds.addAll(donation.get().getSet().stream().map(ObjectObjectMutablePair::left).toList());
+									donation.get().getDowngrades().forEach(downgrade -> itemIds.addAll(MuseumUtils.getPiecesBySetID(downgrade)));
+								} else {
+									itemIds.add(donation.get().getId().replace("STARRED_", ""));
+									itemIds.addAll(donation.get().getDowngrades());
+								}
+							}
+						});
+
+						MUSEUM_ITEM_CACHE.put(uuid, profileId, new ProfileMuseumData(System.currentTimeMillis(), itemIds));
+						MUSEUM_ITEM_CACHE.save();
+
+						if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncSuccess")));
+
+						LOGGER.info("[Skyblocker] Successfully updated museum item cache for profile {}", profileId);
+					} else {
+						//If the player's Museum API is disabled
+						putEmpty(uuid, profileId);
+
+						if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
+						LOGGER.warn(ERROR_LOG_TEMPLATE + " because the Museum API is disabled!", profileId);
+					}
+				} else {
+					//If the request returns a non 200 status code
+					putEmpty(uuid, profileId);
+
+					if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
+					LOGGER.error(ERROR_LOG_TEMPLATE + " because a non 200 status code was encountered! Status Code: {}", profileId, response.statusCode());
+				}
+			} catch (Exception e) {
+				//If an exception was somehow thrown
+				putEmpty(uuid, profileId);
+
+				if (source != null) source.sendFeedback(Constants.PREFIX.get().append(Text.translatable("skyblocker.museum.resyncFailure")));
+
+				LOGGER.error(ERROR_LOG_TEMPLATE, profileId, e);
+			}
+		});
+	}
+
+	private static void putEmpty(UUID uuid, String profileId) {
+		//Only put new data if they didn't have any before
+		MUSEUM_ITEM_CACHE.computeIfAbsent(uuid, profileId, () -> new ProfileMuseumData(System.currentTimeMillis(), ObjectOpenHashSet.of()));
+		MUSEUM_ITEM_CACHE.save();
+	}
+
+	private static boolean tryResync(FabricClientCommandSource source) {
+		UUID uuid = Utils.getUuid();
+		String profileId = Utils.getProfileId();
+
+		//Only allow resyncing if the data is actually present yet, otherwise the player needs to swap servers for the tick method to be called
+		if (loaded.isDone() && !profileId.isEmpty() && MUSEUM_ITEM_CACHE.containsKey() && MUSEUM_ITEM_CACHE.get().canResync()) {
+			updateData4ProfileMember(uuid, profileId, source);
+
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * The cache is ticked upon switching Skyblock servers. Only loads from the API if the profile wasn't cached yet.
+	 */
+	public static void tick() {
+		UUID uuid = Utils.getUuid();
+
+		if (loaded.isDone() && !MUSEUM_ITEM_CACHE.containsKey()) {
+			MUSEUM_ITEM_CACHE.putIfAbsent(ProfileMuseumData.EMPTY.get());
+
+			updateData4ProfileMember(uuid, Utils.getProfileId());
+		}
+	}
+
+	public static boolean hasItemInMuseum(String id) {
+		return MUSEUM_ITEM_CACHE.containsKey() && MUSEUM_ITEM_CACHE.get().collectedItemIds().contains(id.replace("STARRED_", ""));
+	}
+
+	private record ProfileMuseumData(long lastResync, ObjectOpenHashSet<String> collectedItemIds) {
+		private static final Supplier<ProfileMuseumData> EMPTY = () -> new ProfileMuseumData(0L, new ObjectOpenHashSet<>());
+		private static final long TIME_BETWEEN_RESYNCING_ALLOWED = 600_000L;
+		private static final Codec<ProfileMuseumData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
+				Codec.LONG.fieldOf("lastResync").forGetter(ProfileMuseumData::lastResync),
+				Codec.STRING.listOf()
+						.xmap(ObjectOpenHashSet::new, ObjectArrayList::new)
+						.fieldOf("collectedItemIds")
+						.forGetter(ProfileMuseumData::collectedItemIds)
+		).apply(instance, ProfileMuseumData::new));
+
+		private boolean canResync() {
+			return this.lastResync + TIME_BETWEEN_RESYNCING_ALLOWED < System.currentTimeMillis();
+		}
+	}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java
new file mode 100644
index 0000000000..2feb886f7c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumManager.java
@@ -0,0 +1,307 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import com.google.common.collect.Lists;
+import de.hysky.skyblocker.skyblock.item.WikiLookup;
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.utils.ItemUtils;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+import net.fabricmc.fabric.api.client.screen.v1.Screens;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
+import net.minecraft.client.gui.screen.recipebook.RecipeBookResults;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.ClickableWidget;
+import net.minecraft.client.gui.widget.TextFieldWidget;
+import net.minecraft.client.gui.widget.ToggleButtonWidget;
+import net.minecraft.client.option.KeyBinding;
+import net.minecraft.client.render.RenderLayer;
+import net.minecraft.component.DataComponentTypes;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Identifier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class MuseumManager extends ClickableWidget {
+	private static final MinecraftClient CLIENT = MinecraftClient.getInstance();
+	private static final TextRenderer TEXT_RENDERER = CLIENT.textRenderer;
+	private static final KeyBinding INVENTORY_OPEN_KEY = CLIENT.options.inventoryKey;
+    private static final Identifier BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/recipe_book.png");
+    private static final int SEARCH_FIELD_WIDTH = 69;
+    private static final int SEARCH_FIELD_HEIGHT = 20;
+    private static final int BUTTON_SIZE = 20;
+    private static final int BUTTONS_PER_PAGE = 12;
+    private static final ItemSorter ITEM_SORTER = new ItemSorter();
+    private static final ItemFilter ITEM_FILTER = new ItemFilter();
+	private static String searchQuery = "";
+	private static int currentPage = 0;
+    private static List<Donation> donations = new ArrayList<>();
+    private final ToggleButtonWidget nextPageButton;
+    private final ToggleButtonWidget prevPageButton;
+    private final TextFieldWidget searchField;
+    private final List<Donation> filteredDonations = new ArrayList<>();
+    private final List<String> excludedDonationIds = new ArrayList<>();
+    private final List<DonationButton> donationButtons = Lists.newArrayListWithCapacity(BUTTONS_PER_PAGE);
+    private final ButtonWidget filterButton;
+    private final ButtonWidget sortButton;
+    private DonationButton hoveredDonationButton;
+    private int pageCount = 0;
+
+    public MuseumManager(Screen screen, int x, int y, int backgroundWidth) {
+        super(x + backgroundWidth + 2, y, 147, 160, Text.empty());
+
+        // Initialize search field
+        this.searchField = new TextFieldWidget(TEXT_RENDERER, getX() + 25, getY() + 11, SEARCH_FIELD_WIDTH, SEARCH_FIELD_HEIGHT, Text.empty());
+        this.searchField.setMaxLength(60);
+        this.searchField.setVisible(true);
+        this.searchField.setEditableColor(0xFFFFFF);
+		this.searchField.setText(searchQuery);
+        this.searchField.setPlaceholder(Text.translatable("gui.recipebook.search_hint").formatted(Formatting.ITALIC).formatted(Formatting.GRAY));
+
+        // Initialize page navigation buttons
+        this.nextPageButton = new ToggleButtonWidget(getX() + 93, getY() + 133, 12, 17, false);
+        this.nextPageButton.setTextures(RecipeBookResults.PAGE_FORWARD_TEXTURES);
+        this.prevPageButton = new ToggleButtonWidget(getX() + 38, getY() + 133, 12, 17, true);
+        this.prevPageButton.setTextures(RecipeBookResults.PAGE_BACKWARD_TEXTURES);
+
+        donations = MuseumItemCache.getDonations();
+
+        // Create donation buttons for pagination
+        for (int i = 0; i < BUTTONS_PER_PAGE; i++) {
+            DonationButton button = new DonationButton(getX() + 11 + 31 * (i % 4), getY() + 34 + 31 * (i / 4));
+            this.donationButtons.add(button);
+        }
+
+        // Initialize sort button
+        this.sortButton = ButtonWidget.builder(Text.empty(), button -> {
+                    ITEM_SORTER.cycleSortMode(filteredDonations);
+                    button.setTooltip(ITEM_SORTER.getTooltip());
+					currentPage = 0;
+                    updateButtons();
+                })
+                .tooltip(ITEM_SORTER.getTooltip())
+                .position(getX() + 95, getY() + 11)
+                .size(BUTTON_SIZE, BUTTON_SIZE)
+                .build();
+
+        // Initialize filter button
+        this.filterButton = ButtonWidget.builder(Text.empty(), button -> {
+                    ITEM_FILTER.cycleFilterMode(donations, filteredDonations);
+                    ITEM_SORTER.applySort(filteredDonations);
+                    button.setTooltip(ITEM_FILTER.getTooltip());
+					currentPage = 0;
+                    updateButtons();
+                })
+                .tooltip(ITEM_FILTER.getTooltip())
+                .position(getX() + 116, getY() + 11)
+                .size(BUTTON_SIZE, BUTTON_SIZE)
+                .build();
+
+        ITEM_FILTER.applyFilter(donations, filteredDonations);
+        ITEM_SORTER.applySort(filteredDonations);
+        updateSearchResults(false);
+
+        Screens.getButtons(screen).add(this);
+        screen.setFocused(this);
+    }
+
+    /**
+     * Retrieves the Donation object corresponding to a given ID.
+     *
+     * @param id the ID of the donation to retrieve
+     * @return the Donation object associated with the given ID, or null if not found
+     */
+    protected static Donation getDonation(String id) {
+        return donations.stream()
+                .filter(donation -> donation.getId().equals(id))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * Resets the UI state including search text, current page, sorting, and filtering.
+     */
+    public static void reset() {
+		searchQuery = "";
+		currentPage = 0;
+        ITEM_SORTER.resetSorting();
+        ITEM_FILTER.resetFilter();
+    }
+
+    /**
+     * Updates visibility and content of page navigation buttons.
+     */
+    private void updateNavigationButtons() {
+		this.prevPageButton.active = currentPage > 0;
+		this.nextPageButton.active = currentPage < pageCount - 1;
+    }
+
+    /**
+     * Updates the donation buttons based on the current page and visible donations.
+     */
+    private void updateButtons() {
+        List<Donation> visibleDonations = filteredDonations.stream()
+                .filter(donation -> !excludedDonationIds.contains(donation.getId()))
+                .toList();
+
+        int buttonsSize = visibleDonations.size();
+        this.pageCount = (int) Math.ceil((double) buttonsSize / BUTTONS_PER_PAGE);
+
+        for (int i = 0; i < donationButtons.size(); ++i) {
+			int index = currentPage * donationButtons.size() + i;
+
+            if (index < buttonsSize) {
+                donationButtons.get(i).init(visibleDonations.get(index));
+            } else {
+                donationButtons.get(i).clearDisplayStack();
+            }
+        }
+        updateNavigationButtons();
+    }
+
+    /**
+     * Updates search results based on the search text.
+     *
+     * @param resetPage Whether to reset to the first page.
+     */
+    public void updateSearchResults(boolean resetPage) {
+		searchQuery = this.searchField.getText();
+        excludedDonationIds.clear();
+        for (Donation item : donations) {
+            StringBuilder searchableContent = new StringBuilder();
+            ItemStack itemStack = ItemRepository.getItemStack(item.getId());
+            if (itemStack != null) {
+                searchableContent.append(itemStack.getName().getString())
+                        .append(ItemUtils.getConcatenatedLore(itemStack));
+            }
+            if (item.getSet() != null && !item.getSet().isEmpty()) {
+                for (ObjectObjectMutablePair<String, PriceData> piece : item.getSet()) {
+                    ItemStack pieceStack = ItemRepository.getItemStack(piece.left());
+                    if (pieceStack != null) searchableContent.append(pieceStack.getName().getString())
+                            .append(ItemUtils.getConcatenatedLore(pieceStack));
+                }
+            }
+			if (!searchableContent.toString().toLowerCase(Locale.ENGLISH).contains(searchQuery.toLowerCase(Locale.ENGLISH))) {
+                excludedDonationIds.add(item.getId());
+            }
+        }
+		if (resetPage) currentPage = 0;
+        updateButtons();
+    }
+
+    @Override
+    protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+        // Render the background texture for the widget
+        context.drawTexture(RenderLayer::getGuiTextured, BACKGROUND_TEXTURE, getX(), getY(), 1.0f, 1.0f, getWidth(), getHeight(), 256, 256 - 10);
+
+        // Render page count if multiple pages exist
+        if (this.pageCount > 1) {
+			Text text = Text.translatable("gui.recipebook.page", currentPage + 1, this.pageCount);
+            int width = TEXT_RENDERER.getWidth(text);
+
+            context.drawText(TEXT_RENDERER, text, getX() - width / 2 + 73, getY() + 137, -1, false);
+        }
+
+        // Render donation buttons
+        this.hoveredDonationButton = null;
+        for (DonationButton resultButton : donationButtons) {
+            resultButton.render(context, mouseX, mouseY, delta);
+
+            if (resultButton.visible && resultButton.isHovered()) this.hoveredDonationButton = resultButton;
+        }
+
+        if (this.sortButton.active) {
+            int iconX = this.sortButton.getX() + (this.sortButton.getWidth() - 16) / 2;
+            int iconY = this.sortButton.getY() + (this.sortButton.getHeight() - 16) / 2;
+            ItemStack stack = ITEM_SORTER.getCurrentSortingItem();
+            context.drawItemWithoutEntity(stack, iconX, iconY);
+            this.sortButton.render(context, mouseX, mouseY, delta);
+        }
+
+        if (this.filterButton.active) {
+            int iconX = this.filterButton.getX() + (this.filterButton.getWidth() - 16) / 2;
+            int iconY = this.filterButton.getY() + (this.filterButton.getHeight() - 16) / 2;
+            ItemStack stack = ITEM_FILTER.getCurrentFilterItem();
+            context.drawItemWithoutEntity(stack, iconX, iconY);
+            this.filterButton.render(context, mouseX, mouseY, delta);
+        }
+
+        // Render the page flip buttons
+        if (this.prevPageButton.active) this.prevPageButton.render(context, mouseX, mouseY, delta);
+        if (this.nextPageButton.active) this.nextPageButton.render(context, mouseX, mouseY, delta);
+
+        this.searchField.render(context, mouseX, mouseY, delta);
+
+        this.drawTooltip(context, mouseX, mouseY);
+    }
+
+    public void drawTooltip(DrawContext context, int x, int y) {
+        // Draw the tooltip of the hovered result button if one is hovered over
+        if (this.hoveredDonationButton != null && !this.hoveredDonationButton.getDisplayStack().isEmpty()) {
+            ItemStack stack = this.hoveredDonationButton.getDisplayStack();
+            Identifier tooltipStyle = stack.get(DataComponentTypes.TOOLTIP_STYLE);
+
+            context.drawTooltip(TEXT_RENDERER, hoveredDonationButton.getItemTooltip(), x, y, tooltipStyle);
+        }
+    }
+
+    @Override
+    public boolean mouseClicked(double mouseX, double mouseY, int button) {
+        if (this.searchField.mouseClicked(mouseX, mouseY, button)) {
+            this.searchField.setFocused(true);
+            return true;
+        } else if (this.nextPageButton.mouseClicked(mouseX, mouseY, button)) {
+			currentPage++;
+            updateButtons();
+            return true;
+        } else if (this.prevPageButton.mouseClicked(mouseX, mouseY, button)) {
+			currentPage--;
+            updateButtons();
+            return true;
+        } else if (this.filterButton.mouseClicked(mouseX, mouseY, button)) {
+            return true;
+        } else if (this.sortButton.mouseClicked(mouseX, mouseY, button)) {
+            return true;
+        }
+
+        this.searchField.setFocused(false);
+
+        return false;
+    }
+
+    @Override
+    public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
+        return super.keyReleased(keyCode, scanCode, modifiers);
+    }
+
+    @Override
+    public boolean charTyped(char chr, int modifiers) {
+        if (this.searchField.charTyped(chr, modifiers)) {
+            updateSearchResults(true);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+        if ((this.searchField.isActive() && INVENTORY_OPEN_KEY.matchesKey(keyCode, scanCode))
+                || this.searchField.keyPressed(keyCode, scanCode, modifiers)) {
+            updateSearchResults(true);
+            return true;
+        } else if (WikiLookup.wikiLookup.matchesKey(keyCode, scanCode) && hoveredDonationButton != null && hoveredDonationButton.getDisplayStack() != null) {
+            WikiLookup.openWiki(hoveredDonationButton.getDisplayStack(), CLIENT.player);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java
new file mode 100644
index 0000000000..0202aaa4ce
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/MuseumUtils.java
@@ -0,0 +1,141 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import de.hysky.skyblocker.skyblock.itemlist.ItemRepository;
+import de.hysky.skyblocker.utils.ItemUtils;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Style;
+import net.minecraft.text.Text;
+
+import java.text.NumberFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class MuseumUtils {
+	private static final NumberFormat NUMBER_FORMATTER_S = NumberFormat.getCompactNumberInstance(Locale.CANADA, NumberFormat.Style.SHORT);
+
+	static {
+		NUMBER_FORMATTER_S.setMaximumFractionDigits(1);
+	}
+
+	/**
+	 * Calculates the total crafting cost for a set associated with a given ID.
+	 *
+	 * @param id the ID of the set for which the crafting cost is calculated
+	 * @return the total crafting cost of the set
+	 */
+	protected static double getSetCraftCost(String id) {
+		double cost = 0;
+		for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) {
+			if (donation.getId().equals(id)) {
+				for (ObjectObjectMutablePair<String, PriceData> piece : donation.getSet()) {
+					cost += ItemUtils.getCraftCost(piece.left());
+				}
+			}
+		}
+		return cost;
+	}
+
+	/**
+	 * Retrieves the display name for an item or a set.
+	 * If the item is part of a set, it returns the set's name like "Divan's armor".
+	 *
+	 * @param id    the ID of the item or set
+	 * @param isSet true if the ID refers to a set, false if it refers to an individual item
+	 * @return the display name of the item or set
+	 */
+	protected static Text getDisplayName(String id, boolean isSet) {
+		if (isSet) {
+			Style nameStyle = Style.EMPTY;
+			String setName = MuseumItemCache.ARMOR_NAMES.get(id);
+			if (setName != null) {
+				Optional<Donation> donation = MuseumItemCache.MUSEUM_DONATIONS.stream().filter(d -> d.getId().equals(id)).findFirst();
+				if (donation.isPresent()) {
+					if (!donation.get().getSet().isEmpty()) {
+						Text pieceName = getDisplayName(donation.get().getSet().getFirst().left(), false);
+						if (pieceName != null) {
+							List<Text> siblings = pieceName.getSiblings();
+							nameStyle = siblings.isEmpty() ? Style.EMPTY : siblings.getFirst().getStyle();
+						}
+					}
+				}
+				return Text.literal(setName).setStyle(nameStyle);
+			}
+		} else {
+			ItemStack stack = ItemRepository.getItemStack(id);
+			if (stack != null) {
+				return stack.getName();
+			}
+		}
+		return Text.literal(id);
+	}
+
+	/**
+	 * Retrieves the set ID for a given piece ID.
+	 *
+	 * @param id the piece ID to search for
+	 * @return the ID of the set that the piece belongs to, or null if not found
+	 */
+	protected static String getSetID(String id) {
+		for (Donation donation : MuseumItemCache.MUSEUM_DONATIONS) {
+			for (ObjectObjectMutablePair<String, PriceData> set : donation.getSet()) {
+				if (set.left().equals(id)) {
+					return donation.getId();
+				}
+			}
+		}
+		return null;
+	}
+
+	protected static List<String> getPiecesBySetID(String donationId) {
+		return MuseumItemCache.MUSEUM_DONATIONS.stream()
+				.filter(d -> d.getId().equals(donationId))
+				.map(Donation::getSet)
+				.flatMap(List::stream)
+				.map(ObjectObjectMutablePair::left)
+				.collect(Collectors.toList());
+	}
+
+	/**
+	 * Formats an armor item ID into a readable name
+	 */
+	public static String formatArmorName(String id, boolean isEquipment) {
+		String lowercaseKey = id.toLowerCase(Locale.ENGLISH);
+
+		// Step 2: Replace "_" with space
+		String withSpaces = lowercaseKey.replace("_", " ");
+
+		// Step 3: Capitalize the first letter of each word
+		String[] words = withSpaces.split(" ");
+		StringBuilder formattedName = new StringBuilder();
+		for (String word : words) {
+			// Uppercase first letter, lowercase the rest
+			formattedName.append(Character.toUpperCase(word.charAt(0)))
+					.append(word.substring(1))
+					.append(" ");
+		}
+
+		if (isEquipment) {
+			formattedName.append("Equipment");
+		} else if (!lowercaseKey.contains("armor") &&
+				!lowercaseKey.contains("outfit") &&
+				!lowercaseKey.contains("suit") &&
+				!lowercaseKey.contains("tuxedo")) {
+			formattedName.append("Armor");
+		}
+
+		return formattedName.toString().trim();
+	}
+
+	/**
+	 * Formats a double value into a shortened human-readable format.
+	 *
+	 * @param value The number to format.
+	 * @return A formatted string (e.g., "10M", "5K", "1.2B").
+	 */
+	public static String formatPrice(double value) {
+		return NUMBER_FORMATTER_S.format(value);
+	}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java
new file mode 100644
index 0000000000..4a968c9e66
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/museum/PriceData.java
@@ -0,0 +1,51 @@
+package de.hysky.skyblocker.skyblock.museum;
+
+import de.hysky.skyblocker.utils.ItemUtils;
+import it.unimi.dsi.fastutil.objects.ObjectObjectMutablePair;
+
+public class PriceData {
+    private final double lBinPrice;
+    private final double craftCost;
+    private double effectivePrice;
+
+    public PriceData(double lBinPrice, double craftCost) {
+        this.lBinPrice = lBinPrice;
+        this.craftCost = craftCost;
+    }
+
+    public PriceData(Donation donation) {
+        if (donation.isSet()) {
+            double totalLBinPrice = 0, totalCraftCost = 0;
+            for (ObjectObjectMutablePair<String, PriceData> piece : donation.getSet()) {
+                double lBinPrice = ItemUtils.getItemPrice(piece.left()).leftDouble();
+                double craftCost = ItemUtils.getCraftCost(piece.left());
+
+                totalLBinPrice += lBinPrice;
+                totalCraftCost += craftCost;
+
+                piece.right(new PriceData(lBinPrice, craftCost));
+            }
+            this.lBinPrice = totalLBinPrice;
+            this.craftCost = totalCraftCost;
+        } else {
+            this.lBinPrice = ItemUtils.getItemPrice(donation.getId()).leftDouble();
+            this.craftCost = ItemUtils.getCraftCost(donation.getId());
+        }
+    }
+
+    public double getLBinPrice() {
+        return lBinPrice;
+    }
+
+    public double getCraftCost() {
+        return craftCost;
+    }
+
+    public double getEffectivePrice() {
+        return effectivePrice;
+    }
+
+    public void setEffectivePrice(double effectivePrice) {
+        this.effectivePrice = effectivePrice;
+    }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
index 60f272c0fe..f75935c4f4 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
@@ -6,7 +6,6 @@
 import com.mojang.brigadier.arguments.StringArgumentType;
 import com.mojang.brigadier.builder.LiteralArgumentBuilder;
 import com.mojang.util.UndashedUuid;
-
 import de.hysky.skyblocker.SkyblockerMod;
 import de.hysky.skyblocker.annotations.Init;
 import de.hysky.skyblocker.skyblock.profileviewer.collections.CollectionsPage;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/speedPreset/SpeedPresetsScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/speedPreset/SpeedPresetsScreen.java
index 2f7d462be7..c71712c613 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/speedPreset/SpeedPresetsScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/speedPreset/SpeedPresetsScreen.java
@@ -3,7 +3,9 @@
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ConfirmScreen;
 import net.minecraft.client.gui.screen.Screen;
-import net.minecraft.client.gui.widget.*;
+import net.minecraft.client.gui.widget.ButtonWidget;
+import net.minecraft.client.gui.widget.GridWidget;
+import net.minecraft.client.gui.widget.SimplePositioningWidget;
 import net.minecraft.screen.ScreenTexts;
 import net.minecraft.text.Text;
 import net.minecraft.util.Formatting;
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java
index 0cbf5e5f06..580298af6d 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/tabhud/widget/component/PlayerComponent.java
@@ -3,7 +3,6 @@
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.PlayerSkinDrawer;
 import net.minecraft.client.network.PlayerListEntry;
-import net.minecraft.scoreboard.Team;
 import net.minecraft.text.Text;
 import net.minecraft.util.Identifier;
 
diff --git a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
index b886f40fc3..8e394160a0 100644
--- a/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/ItemUtils.java
@@ -9,11 +9,13 @@
 import com.mojang.serialization.JsonOps;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.item.tooltip.adders.CraftPriceTooltip;
 import de.hysky.skyblocker.skyblock.item.PetInfo;
 import de.hysky.skyblocker.skyblock.item.tooltip.adders.ObtainedDateTooltip;
 import de.hysky.skyblocker.skyblock.item.tooltip.info.TooltipInfoType;
 import de.hysky.skyblocker.utils.datafixer.ItemStackComponentizationFixer;
 import de.hysky.skyblocker.utils.networth.NetworthCalculator;
+import io.github.moulberry.repo.data.NEUItem;
 import it.unimi.dsi.fastutil.doubles.DoubleBooleanPair;
 import it.unimi.dsi.fastutil.ints.IntIntPair;
 import it.unimi.dsi.fastutil.longs.LongBooleanPair;
@@ -322,6 +324,14 @@ public static PetInfo getPetInfo(ComponentHolder stack) {
         return DoubleBooleanPair.of(0, false);
     }
 
+	public static double getCraftCost(String skyblockApiId) {
+		NEUItem neuItem = NEURepoManager.NEU_REPO.getItems().getItemBySkyblockId(skyblockApiId);
+		if (neuItem != null && !neuItem.getRecipes().isEmpty()) {
+			return CraftPriceTooltip.getItemCost(neuItem.getRecipes().getFirst(), 0);
+		}
+		return 0;
+	}
+
     /**
      * This method converts the "timestamp" variable into the same date format as Hypixel represents it in the museum.
      * Currently, there are two types of string timestamps the legacy which is built like this
@@ -475,4 +485,21 @@ public static Matcher getLoreLineIfContainsMatch(ItemStack stack, Pattern patter
         }
         return stringBuilder.toString();
     }
-}
\ No newline at end of file
+
+	/**
+	 * Checks if the given item ID represents an equipment piece.
+	 */
+	public static boolean isEquipment(String skyblockApiId) {
+		return (skyblockApiId.contains("BELT") ||
+				skyblockApiId.contains("GLOVES") ||
+				skyblockApiId.contains("CLOAK") ||
+				skyblockApiId.contains("GAUNTLET") ||
+				skyblockApiId.contains("NECKLACE") ||
+				skyblockApiId.contains("BRACELET") ||
+				skyblockApiId.contains("HAT"));
+	}
+
+	public static boolean isSoulbound(ItemStack stack) {
+		return getLore(stack).stream().anyMatch(lore -> lore.getString().toLowerCase(Locale.ENGLISH).contains("soulbound"));
+	}
+}
diff --git a/src/main/java/de/hysky/skyblocker/utils/Utils.java b/src/main/java/de/hysky/skyblocker/utils/Utils.java
index 972abd3c45..c0230db348 100644
--- a/src/main/java/de/hysky/skyblocker/utils/Utils.java
+++ b/src/main/java/de/hysky/skyblocker/utils/Utils.java
@@ -6,7 +6,6 @@
 import de.hysky.skyblocker.annotations.Init;
 import de.hysky.skyblocker.events.SkyblockEvents;
 import de.hysky.skyblocker.mixins.accessors.MessageHandlerAccessor;
-import de.hysky.skyblocker.skyblock.item.MuseumItemCache;
 import de.hysky.skyblocker.skyblock.slayers.SlayerManager;
 import de.hysky.skyblocker.utils.purse.PurseChangeCause;
 import de.hysky.skyblocker.utils.scheduler.MessageScheduler;
diff --git a/src/main/java/de/hysky/skyblocker/utils/render/SkyblockerRenderLayers.java b/src/main/java/de/hysky/skyblocker/utils/render/SkyblockerRenderLayers.java
index faa4eebca1..ba63bdae38 100644
--- a/src/main/java/de/hysky/skyblocker/utils/render/SkyblockerRenderLayers.java
+++ b/src/main/java/de/hysky/skyblocker/utils/render/SkyblockerRenderLayers.java
@@ -1,7 +1,5 @@
 package de.hysky.skyblocker.utils.render;
 
-import org.lwjgl.opengl.GL11;
-
 import net.minecraft.client.render.RenderLayer;
 import net.minecraft.client.render.RenderLayer.MultiPhase;
 import net.minecraft.client.render.RenderLayer.MultiPhaseParameters;
@@ -9,6 +7,7 @@
 import net.minecraft.client.render.RenderPhase.DepthTest;
 import net.minecraft.client.render.VertexFormat.DrawMode;
 import net.minecraft.client.render.VertexFormats;
+import org.lwjgl.opengl.GL11;
 
 public class SkyblockerRenderLayers {
 	public static final DepthTest OUTLINE_ALWAYS = new DepthTest("outline_always", GL11.GL_ALWAYS);
diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json
index 058b321de4..9ecf1ea95b 100644
--- a/src/main/resources/assets/skyblocker/lang/en_us.json
+++ b/src/main/resources/assets/skyblocker/lang/en_us.json
@@ -398,7 +398,6 @@
   "skyblocker.config.helpers.enableBitsHelper": "Highlight best Community Store (Bits Shop) offer",
   "skyblocker.config.helpers.enableBitsHelper.@Tooltip": "Highlights best offers in Community Store (Bits Shop). Also shows coins per bit. Green if best offer that is in list of items that quickly sell. Yellow for best offer overall - may include stuff that sells once in a year. \n\nBoth may include price manipulated items - please use common sense or at least 1day/3day avg tooltip",
 
-
   "skyblocker.config.helpers.enableWardrobeHelper": "Enable Wardrobe Helper",
   "skyblocker.config.helpers.enableWardrobeHelper.@Tooltip": "Allows changing armor from the wardrobe by pressing hotbar keys 1-9 while the menu is open. The corresponding armor set in the wardrobe menu will be equipped.",
 
@@ -632,7 +631,6 @@
   "skyblocker.config.mining.dwarvenMines.carpetHighlightColor": "Carpet Highlight Color",
   "skyblocker.config.mining.dwarvenMines.carpetHighlightColor.@Tooltip": "Sets the color of the highlight for the unbreakable carpets.",
 
-
   "skyblocker.config.mining.enableDrillFuel": "Enable Drill Fuel",
 
   "skyblocker.config.mining.commissionHighlight": "Highlights Completed Commissions",
@@ -871,6 +869,9 @@
   "skyblocker.config.uiAndVisuals.slotText.statsTuning.@Tooltip": "Displays your assigned and unassigned tuning points",
   "skyblocker.config.uiAndVisuals.slotText.yourEssence": "Your Essence",
 
+  "skyblocker.config.uiAndVisuals.museumOverlay": "Museum Overlay",
+  "skyblocker.config.uiAndVisuals.museumOverlay.@Tooltip": "Shows undonated items including their prices and more.\n\nMake sure to enable your Museum API for this to work!",
+
   "skyblocker.config.uiAndVisuals.searchOverlay": "Search Overlay",
   "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse": "Enable For Auction House",
   "skyblocker.config.uiAndVisuals.searchOverlay.enableAuctionHouse.@Tooltip": "Show custom search overlay when searching in the auction house.",
@@ -977,6 +978,29 @@
   "skyblocker.updateRepository.failed": "§cUpdating the local repository failed. See logs for details.",
   "skyblocker.updateRepository.error":  "§cEncountered an error while deleting and updating the local repository. Try again in a moment or remove the files manually and restart the game. See logs for details.",
 
+  "skyblocker.museum.hud.alreadyDonatedItem": "Donated Item",
+  "skyblocker.museum.hud.coin": "Coins",
+  "skyblocker.museum.hud.craftIngredient": "Crafted with",
+  "skyblocker.museum.hud.countsFor": "Will count for",
+  "skyblocker.museum.hud.filter": "Item Filter",
+  "skyblocker.museum.hud.filter.all": "All",
+  "skyblocker.museum.hud.filter.armor": "Armor",
+  "skyblocker.museum.hud.filter.rarities": "Rarities",
+  "skyblocker.museum.hud.filter.switch": "Click to switch filter!",
+  "skyblocker.museum.hud.filter.weapons": "Weapons",
+  "skyblocker.museum.hud.ratioText": "Coins per XP",
+  "skyblocker.museum.hud.skyblockXp": "SkyBlock XP",
+  "skyblocker.museum.hud.sorter": "Item Sort",
+  "skyblocker.museum.hud.sorter.craftCost": "Craft Cost",
+  "skyblocker.museum.hud.sorter.lBin": "Lowest BIN",
+  "skyblocker.museum.hud.sorter.ratio": "Coins/XP Ratio",
+  "skyblocker.museum.hud.sorter.switch": "Click to switch sort!",
+  "skyblocker.museum.hud.soulboundItem": "Soulbound",
+  "skyblocker.museum.hud.untradeableItem": "Untradable",
+  "skyblocker.museum.hud.unknownPrice": "Unknown",
+  "skyblocker.museum.hud.wikiLookup": "Click on %s to open the wiki page!",
+  "skyblocker.museum.hud.xpReward": "Reward",
+
   "skyblocker.museum.attemptingResync": "Attempting to resync your museum item donations...",
   "skyblocker.museum.cannotResync": "Cannot resync your museum item donations! Note that you can only do this once every two days.",
   "skyblocker.museum.resyncSuccess": "Successfully resynced your museum item donations!",