diff --git a/Project.xml b/Project.xml
index 8eb62bb1d..656590aec 100644
--- a/Project.xml
+++ b/Project.xml
@@ -114,11 +114,8 @@ xsi:schemaLocation="http://lime.openfl.org/project/1.0.4 http://lime.openfl.org/
 	<haxelib name="openfl" /> <!-- Game engine backend -->
 	<haxelib name="flixel" /> <!-- Game engine -->
 
-	<haxedev set="webgl" />
-
 	<haxelib name="flixel-addons" /> <!-- Additional utilities for Flixel -->
 	<haxelib name="hscript" /> <!-- Scripting -->
-	<haxelib name="flixel-ui" /> <!-- UI framework (DEPRECATED) -->
 	<haxelib name="haxeui-core" /> <!-- UI framework -->
 	<haxelib name="haxeui-flixel" /> <!-- Integrate HaxeUI with Flixel -->
 	<haxelib name="flixel-text-input" /> <!-- Improved text field rendering for HaxeUI -->
diff --git a/assets b/assets
index 62c4a8203..b57d7f8d3 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 62c4a8203362c1b434de0d376046ebccb96da254
+Subproject commit b57d7f8d308e468f7b0947d4784d0efeca44d9aa
diff --git a/hmm.json b/hmm.json
index aad0be8cf..d67760882 100644
--- a/hmm.json
+++ b/hmm.json
@@ -39,8 +39,8 @@
       "name": "flxanimate",
       "type": "git",
       "dir": null,
-      "ref": "17e0d59fdbc2b6283a5c0e4df41f1c7f27b71c49",
-      "url": "https://github.com/FunkinCrew/flxanimate"
+      "ref": "768740a56b26aa0c072720e0d1236b94afe68e3e",
+      "url": "https://github.com/Dot-Stuff/flxanimate"
     },
     {
       "name": "FlxPartialSound",
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 34516dee1..e53499e3c 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -244,11 +244,11 @@ class InitState extends FlxState
                 totalNotesHit: 140,
                 totalNotes: 190
               }
-            // 2000 = loss
-            // 240 = good
-            // 230 = great
-            // 210 = excellent
-            // 190 = perfect
+            // 2400 total notes = 7% = LOSS
+            // 240 total notes = 79% = GOOD
+            // 230 total notes = 82% = GREAT
+            // 210 total notes = 91% = EXCELLENT
+            // 190 total notes = PERFECT
           },
       }));
     #elseif ANIMDEBUG
diff --git a/source/funkin/data/freeplay/player/PlayerRegistry.hx b/source/funkin/data/freeplay/player/PlayerRegistry.hx
index 4656a1286..be8730ccd 100644
--- a/source/funkin/data/freeplay/player/PlayerRegistry.hx
+++ b/source/funkin/data/freeplay/player/PlayerRegistry.hx
@@ -58,8 +58,9 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
    * @param characterId The stage character ID.
    * @return The playable character.
    */
-  public function getCharacterOwnerId(characterId:String):Null<String>
+  public function getCharacterOwnerId(characterId:Null<String>):Null<String>
   {
+    if (characterId == null) return null;
     return ownedCharacterIds[characterId];
   }
 
diff --git a/source/funkin/data/stage/StageRegistry.hx b/source/funkin/data/stage/StageRegistry.hx
index 1f0504247..7754c380e 100644
--- a/source/funkin/data/stage/StageRegistry.hx
+++ b/source/funkin/data/stage/StageRegistry.hx
@@ -93,8 +93,8 @@ class StageRegistry extends BaseRegistry<Stage, StageData>
   public function listBaseGameStageIds():Array<String>
   {
     return [
-      "mainStage", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallEvil", "school", "schoolEvil",
-      "tankmanBattlefield", "phillyStreets", "phillyBlazin",
+      "mainStage", "mainStageErect", "spookyMansion", "phillyTrain", "phillyTrainErect", "limoRide", "limoRideErect", "mallXmas", "mallEvil", "school",
+      "schoolEvil", "tankmanBattlefield", "phillyStreets", "phillyBlazin",
     ];
   }
 
diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
index eb331b9c3..049c6e206 100644
--- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
+++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx
@@ -4,8 +4,11 @@ import flixel.util.FlxSignal.FlxTypedSignal;
 import flxanimate.FlxAnimate;
 import flxanimate.FlxAnimate.Settings;
 import flxanimate.frames.FlxAnimateFrames;
+import flixel.graphics.frames.FlxFrame;
+import flixel.system.FlxAssets.FlxGraphicAsset;
 import openfl.display.BitmapData;
 import openfl.utils.Assets;
+import flixel.math.FlxPoint;
 
 /**
  * A sprite which provides convenience functions for rendering a texture atlas with animations.
@@ -25,9 +28,19 @@ class FlxAtlasSprite extends FlxAnimate
     };
 
   /**
-   * Signal dispatched when an animation finishes playing.
+   * Signal dispatched when an animation advances to the next frame.
    */
-  public var onAnimationFinish:FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
+  public var onAnimationFrame:FlxTypedSignal<String->Int->Void> = new FlxTypedSignal();
+
+  /**
+   * Signal dispatched when a non-looping animation finishes playing.
+   */
+  public var onAnimationComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
+
+  /**
+   * Signal dispatched when a looping animation finishes playing
+   */
+  public var onAnimationLoopComplete:FlxTypedSignal<String->Void> = new FlxTypedSignal();
 
   var currentAnimation:String;
 
@@ -44,17 +57,20 @@ class FlxAtlasSprite extends FlxAnimate
 
     super(x, y, path, settings);
 
-    if (this.anim.curInstance == null)
+    if (this.anim.stageInstance == null)
     {
       throw 'FlxAtlasSprite not initialized properly. Are you sure the path (${path}) exists?';
     }
 
-    onAnimationFinish.add(cleanupAnimation);
+    onAnimationComplete.add(cleanupAnimation);
 
     // This defaults the sprite to play the first animation in the atlas,
     // then pauses it. This ensures symbols are intialized properly.
     this.anim.play('');
     this.anim.pause();
+
+    this.anim.onComplete.add(_onAnimationComplete);
+    this.anim.onFrame.add(_onAnimationFrame);
   }
 
   /**
@@ -62,9 +78,13 @@ class FlxAtlasSprite extends FlxAnimate
    */
   public function listAnimations():Array<String>
   {
-    if (this.anim == null) return [];
-    return this.anim.getFrameLabels();
-    // return [""];
+    var mainSymbol = this.anim.symbolDictionary[this.anim.stageInstance.symbol.name];
+    if (mainSymbol == null)
+    {
+      FlxG.log.error('FlxAtlasSprite does not have its main symbol!');
+      return [];
+    }
+    return mainSymbol.getFrameLabels().map(keyFrame -> keyFrame.name).filterNull();
   }
 
   /**
@@ -107,12 +127,11 @@ class FlxAtlasSprite extends FlxAnimate
    * @param restart Whether to restart the animation if it is already playing.
    * @param ignoreOther Whether to ignore all other animation inputs, until this one is done playing
    * @param loop Whether to loop the animation
+   * @param startFrame The frame to start the animation on
    * NOTE: `loop` and `ignoreOther` are not compatible with each other!
    */
-  public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, ?loop:Bool = false):Void
+  public function playAnimation(id:String, restart:Bool = false, ignoreOther:Bool = false, loop:Bool = false, startFrame:Int = 0):Void
   {
-    if (loop == null) loop = false;
-
     // Skip if not allowed to play animations.
     if ((!canPlayOtherAnims && !ignoreOther)) return;
 
@@ -128,7 +147,7 @@ class FlxAtlasSprite extends FlxAnimate
       else
       {
         // Resume animation if it's paused.
-        anim.play('', false, false);
+        anim.play('', restart, false, startFrame);
       }
     }
     else
@@ -141,31 +160,27 @@ class FlxAtlasSprite extends FlxAnimate
       }
     }
 
-    anim.callback = function(_, frame:Int) {
-      var offset = loop ? 0 : -1;
-
-      var frameLabel = anim.getFrameLabel(id);
-      if (frame == (frameLabel.duration + offset) + frameLabel.index)
+    anim.onComplete.removeAll();
+    anim.onComplete.add(function() {
+      if (loop)
       {
-        if (loop)
-        {
-          playAnimation(id, true, false, true);
-        }
-        else
-        {
-          onAnimationFinish.dispatch(id);
-        }
+        onAnimationLoopComplete.dispatch(id);
+        this.anim.play(id, restart, false, startFrame);
+        this.currentAnimation = id;
       }
-    };
-
-    anim.onComplete = function() {
-      onAnimationFinish.dispatch(id);
-    };
+      else
+      {
+        onAnimationComplete.dispatch(id);
+      }
+    });
 
     // Prevent other animations from playing if `ignoreOther` is true.
     if (ignoreOther) canPlayOtherAnims = false;
 
     // Move to the first frame of the animation.
+    // goToFrameLabel(id);
+    trace('Playing animation $id');
+    this.anim.play(id, restart, false, startFrame);
     goToFrameLabel(id);
     this.currentAnimation = id;
   }
@@ -175,6 +190,24 @@ class FlxAtlasSprite extends FlxAnimate
     super.update(elapsed);
   }
 
+  /**
+   * Returns true if the animation has finished playing.
+   * Never true if animation is configured to loop.
+   */
+  public function isAnimationFinished():Bool
+  {
+    return this.anim.finished;
+  }
+
+  /**
+   * Returns true if the animation has reached the last frame.
+   * Can be true even if animation is configured to loop.
+   */
+  public function isLoopComplete():Bool
+  {
+    return (anim.reversed && anim.curFrame == 0 || !(anim.reversed) && (anim.curFrame) >= (anim.length - 1));
+  }
+
   /**
    * Stops the current animation.
    */
@@ -219,4 +252,76 @@ class FlxAtlasSprite extends FlxAnimate
     // this.currentAnimation = null;
     this.anim.pause();
   }
+
+  function _onAnimationFrame(frame:Int):Void
+  {
+    if (currentAnimation != null)
+    {
+      onAnimationFrame.dispatch(currentAnimation, frame);
+      if (isLoopComplete()) onAnimationLoopComplete.dispatch(currentAnimation);
+    }
+  }
+
+  function _onAnimationComplete():Void
+  {
+    if (currentAnimation != null)
+    {
+      onAnimationComplete.dispatch(currentAnimation);
+    }
+  }
+
+  var prevFrames:Map<Int, FlxFrame> = [];
+
+  public function replaceFrameGraphic(index:Int, ?graphic:FlxGraphicAsset):Void
+  {
+    if (graphic == null || !Assets.exists(graphic))
+    {
+      var prevFrame:Null<FlxFrame> = prevFrames.get(index);
+      if (prevFrame == null) return;
+
+      prevFrame.copyTo(frames.getByIndex(index));
+      return;
+    }
+
+    var prevFrame:FlxFrame = prevFrames.get(index) ?? frames.getByIndex(index).copyTo();
+    prevFrames.set(index, prevFrame);
+
+    var frame = FlxG.bitmap.add(graphic).imageFrame.frame;
+    frame.copyTo(frames.getByIndex(index));
+
+    // Additional sizing fix.
+    @:privateAccess
+    if (true)
+    {
+      var frame = frames.getByIndex(index);
+      frame.tileMatrix[0] = prevFrame.frame.width / frame.frame.width;
+      frame.tileMatrix[3] = prevFrame.frame.height / frame.frame.height;
+    }
+  }
+
+  public function getBasePosition():Null<FlxPoint>
+  {
+    var stagePos = new FlxPoint(anim.stageInstance.matrix.tx, anim.stageInstance.matrix.ty);
+    var instancePos = new FlxPoint(anim.curInstance.matrix.tx, anim.curInstance.matrix.ty);
+    var firstElement = anim.curSymbol.timeline?.get(0)?.get(0)?.get(0);
+    if (firstElement == null) return instancePos;
+    var firstElementPos = new FlxPoint(firstElement.matrix.tx, firstElement.matrix.ty);
+
+    return instancePos + firstElementPos;
+  }
+
+  public function getPivotPosition():Null<FlxPoint>
+  {
+    return anim.curInstance.symbol.transformationPoint;
+  }
+
+  public override function destroy():Void
+  {
+    for (prevFrameId in prevFrames.keys())
+    {
+      replaceFrameGraphic(prevFrameId, null);
+    }
+
+    super.destroy();
+  }
 }
diff --git a/source/funkin/modding/base/ScriptedFlxUIState.hx b/source/funkin/modding/base/ScriptedFlxUIState.hx
deleted file mode 100644
index c58fc294f..000000000
--- a/source/funkin/modding/base/ScriptedFlxUIState.hx
+++ /dev/null
@@ -1,8 +0,0 @@
-package funkin.modding.base;
-
-/**
- * A script that can be tied to an FlxUIState.
- * Create a scripted class that extends FlxUIState to use this.
- */
-@:hscriptClass
-class ScriptedFlxUIState extends flixel.addons.ui.FlxUIState implements HScriptedClass {}
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index c2d9d42b3..b1ff69a3a 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -70,6 +70,8 @@ class ResultState extends MusicBeatSubState
       delay:Float
     }> = [];
 
+  var playerCharacterId:Null<String>;
+
   var rankBg:FunkinSprite;
   final cameraBG:FunkinCamera;
   final cameraScroll:FunkinCamera;
@@ -164,7 +166,7 @@ class ResultState extends MusicBeatSubState
     add(soundSystem);
 
     // Fetch playable character data. Default to BF on the results screen if we can't find it.
-    var playerCharacterId:Null<String> = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
+    playerCharacterId = PlayerRegistry.instance.getCharacterOwnerId(params.characterId);
     var playerCharacter:Null<PlayableCharacter> = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? 'bf');
 
     trace('Got playable character: ${playerCharacter?.getName()}');
@@ -189,7 +191,7 @@ class ResultState extends MusicBeatSubState
           if (!(animData.looped ?? true))
           {
             // Animation is not looped.
-            animation.onAnimationFinish.add((_name:String) -> {
+            animation.onAnimationComplete.add((_name:String) -> {
               if (animation != null)
               {
                 animation.anim.pause();
@@ -198,7 +200,7 @@ class ResultState extends MusicBeatSubState
           }
           else if (animData.loopFrameLabel != null)
           {
-            animation.onAnimationFinish.add((_name:String) -> {
+            animation.onAnimationComplete.add((_name:String) -> {
               if (animation != null)
               {
                 animation.playAnimation(animData.loopFrameLabel ?? ''); // unpauses this anim, since it's on PlayOnce!
@@ -207,7 +209,7 @@ class ResultState extends MusicBeatSubState
           }
           else if (animData.loopFrame != null)
           {
-            animation.onAnimationFinish.add((_name:String) -> {
+            animation.onAnimationComplete.add((_name:String) -> {
               if (animation != null)
               {
                 animation.anim.curFrame = animData.loopFrame ?? 0;
@@ -742,6 +744,7 @@ class ResultState extends MusicBeatSubState
                 FlxG.switchState(FreeplayState.build(
                   {
                     {
+                      character: playerCharacterId ?? "bf",
                       fromResults:
                         {
                           oldRank: Scoring.calculateRank(params?.prevScoreData),
@@ -799,8 +802,9 @@ typedef ResultsStateParams =
 
   /**
    * The character ID for the song we just played.
+   * @default `bf`
    */
-  var characterId:String;
+  var ?characterId:String;
 
   /**
    * Whether the displayed score is a new highscore
diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx
index ed58b92b5..32a4e765c 100644
--- a/source/funkin/play/character/AnimateAtlasCharacter.hx
+++ b/source/funkin/play/character/AnimateAtlasCharacter.hx
@@ -109,8 +109,6 @@ class AnimateAtlasCharacter extends BaseCharacter
     var loop:Bool = animData.looped;
 
     this.mainSprite.playAnimation(prefix, restart, ignoreOther, loop);
-
-    animFinished = false;
   }
 
   public override function hasAnimation(name:String):Bool
@@ -124,17 +122,17 @@ class AnimateAtlasCharacter extends BaseCharacter
    */
   public override function isAnimationFinished():Bool
   {
-    return animFinished;
+    return mainSprite.isAnimationFinished();
   }
 
   function loadAtlasSprite():FlxAtlasSprite
   {
     trace('[ATLASCHAR] Loading sprite atlas for ${characterId}.');
 
-    var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath, 'shared'));
+    var sprite:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas(_data.assetPath));
 
-    sprite.onAnimationFinish.removeAll();
-    sprite.onAnimationFinish.add(this.onAnimationFinished);
+    // sprite.onAnimationComplete.removeAll();
+    sprite.onAnimationComplete.add(this.onAnimationFinished);
 
     return sprite;
   }
@@ -152,7 +150,6 @@ class AnimateAtlasCharacter extends BaseCharacter
       // Make the game hold on the last frame.
       this.mainSprite.cleanupAnimation(prefix);
       // currentAnimName = null;
-      animFinished = true;
 
       // Fallback to idle!
       // playAnimation('idle', true, false);
@@ -165,6 +162,11 @@ class AnimateAtlasCharacter extends BaseCharacter
 
     this.mainSprite = sprite;
 
+    // This forces the atlas to recalcuate its width and height
+    this.mainSprite.alpha = 0.0001;
+    this.mainSprite.draw();
+    this.mainSprite.alpha = 1.0;
+
     var feetPos:FlxPoint = feetPosition;
     this.updateHitbox();
 
diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx
index 41c96fbfa..e23afe18c 100644
--- a/source/funkin/play/character/MultiSparrowCharacter.hx
+++ b/source/funkin/play/character/MultiSparrowCharacter.hx
@@ -62,7 +62,7 @@ class MultiSparrowCharacter extends BaseCharacter
       }
     }
 
-    var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath, 'shared');
+    var texture:FlxAtlasFrames = Paths.getSparrowAtlas(_data.assetPath);
 
     if (texture == null)
     {
@@ -76,7 +76,7 @@ class MultiSparrowCharacter extends BaseCharacter
 
     for (asset in assetList)
     {
-      var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset, 'shared');
+      var subTexture:FlxAtlasFrames = Paths.getSparrowAtlas(asset);
       // If we don't do this, the unused textures will be removed as soon as they're loaded.
 
       if (subTexture == null)
diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx
index 22edbe339..5d004606c 100644
--- a/source/funkin/play/character/PackerCharacter.hx
+++ b/source/funkin/play/character/PackerCharacter.hx
@@ -30,7 +30,7 @@ class PackerCharacter extends BaseCharacter
   {
     trace('[PACKERCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
 
-    var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath, 'shared');
+    var tex:FlxFramesCollection = Paths.getPackerAtlas(_data.assetPath);
     if (tex == null)
     {
       trace('Could not load Packer sprite: ${_data.assetPath}');
diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx
index 81d98b138..eacf799d8 100644
--- a/source/funkin/play/character/SparrowCharacter.hx
+++ b/source/funkin/play/character/SparrowCharacter.hx
@@ -33,7 +33,7 @@ class SparrowCharacter extends BaseCharacter
   {
     trace('[SPARROWCHAR] Loading spritesheet ${_data.assetPath} for ${characterId}');
 
-    var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath, 'shared');
+    var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath);
     if (tex == null)
     {
       trace('Could not load Sparrow sprite: ${_data.assetPath}');
diff --git a/source/funkin/ui/charSelect/CharSelectPlayer.hx b/source/funkin/ui/charSelect/CharSelectPlayer.hx
index 9322369ba..9052c60e9 100644
--- a/source/funkin/ui/charSelect/CharSelectPlayer.hx
+++ b/source/funkin/ui/charSelect/CharSelectPlayer.hx
@@ -9,7 +9,7 @@ class CharSelectPlayer extends FlxAtlasSprite
   {
     super(x, y, Paths.animateAtlas("charSelect/bfChill"));
 
-    onAnimationFinish.add(function(animLabel:String) {
+    onAnimationComplete.add(function(animLabel:String) {
       switch (animLabel)
       {
         case "slidein":
diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx
index 14a5b36e0..8b1f050f5 100644
--- a/source/funkin/ui/charSelect/CharSelectSubState.hx
+++ b/source/funkin/ui/charSelect/CharSelectSubState.hx
@@ -600,7 +600,7 @@ class CharSelectSubState extends MusicBeatSubState
     playerChill.visible = false;
     playerChillOut.visible = true;
     playerChillOut.anim.goToFrameLabel("slideout");
-    playerChillOut.anim.callback = (_, frame:Int) -> {
+    playerChillOut.onAnimationFrame.add((_, frame:Int) -> {
       if (frame == playerChillOut.anim.getFrameLabel("slideout").index + 1)
       {
         playerChill.visible = true;
@@ -612,7 +612,7 @@ class CharSelectSubState extends MusicBeatSubState
         playerChillOut.switchChar(value);
         playerChillOut.visible = false;
       }
-    };
+    });
     return value;
   }
 
diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx
index 04784a5b7..7bb42c89e 100644
--- a/source/funkin/ui/debug/anim/DebugBoundingState.hx
+++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx
@@ -1,33 +1,26 @@
 package funkin.ui.debug.anim;
 
+import flixel.addons.display.FlxBackdrop;
 import flixel.addons.display.FlxGridOverlay;
-import flixel.addons.ui.FlxInputText;
-import flixel.addons.ui.FlxUIDropDownMenu;
 import flixel.FlxCamera;
 import flixel.FlxSprite;
 import flixel.FlxState;
-import flixel.graphics.frames.FlxAtlasFrames;
 import flixel.graphics.frames.FlxFrame;
 import flixel.group.FlxGroup;
 import flixel.math.FlxPoint;
 import flixel.text.FlxText;
 import flixel.util.FlxColor;
-import flixel.util.FlxSpriteUtil;
-import flixel.util.FlxTimer;
-import funkin.audio.FunkinSound;
 import funkin.input.Cursor;
 import funkin.play.character.BaseCharacter;
 import funkin.play.character.CharacterData;
 import funkin.play.character.CharacterData.CharacterDataParser;
-import funkin.play.character.SparrowCharacter;
 import funkin.ui.mainmenu.MainMenuState;
 import funkin.util.MouseUtil;
 import funkin.util.SerializerUtil;
 import funkin.util.SortUtil;
 import haxe.ui.components.DropDown;
-import haxe.ui.core.Component;
+import haxe.ui.containers.dialogs.CollapsibleDialog;
 import haxe.ui.core.Screen;
-import haxe.ui.events.ItemEvent;
 import haxe.ui.events.UIEvent;
 import haxe.ui.RuntimeComponentBuilder;
 import lime.utils.Assets as LimeAssets;
@@ -36,9 +29,6 @@ import openfl.events.Event;
 import openfl.events.IOErrorEvent;
 import openfl.geom.Rectangle;
 import openfl.net.FileReference;
-import openfl.net.URLLoader;
-import openfl.net.URLRequest;
-import openfl.utils.ByteArray;
 
 using flixel.util.FlxSpriteUtil;
 
@@ -55,10 +45,10 @@ class DebugBoundingState extends FlxState
     TODAY'S TO-DO
     - Cleaner UI
    */
-  var bg:FlxSprite;
+  var bg:FlxBackdrop;
   var fileInfo:FlxText;
 
-  var txtGrp:FlxGroup;
+  var txtGrp:FlxTypedGroup<FlxText>;
 
   var hudCam:FlxCamera;
 
@@ -66,16 +56,23 @@ class DebugBoundingState extends FlxState
 
   var spriteSheetView:FlxGroup;
   var offsetView:FlxGroup;
-  var animDropDownMenu:FlxUIDropDownMenu;
   var dropDownSetup:Bool = false;
 
   var onionSkinChar:FlxSprite;
   var txtOffsetShit:FlxText;
 
-  var uiStuff:Component;
+  var offsetEditorDialog:CollapsibleDialog;
+  var offsetAnimationDropdown:DropDown;
 
   var haxeUIFocused(get, default):Bool = false;
 
+  var currentAnimationName(get, never):String;
+
+  function get_currentAnimationName():String
+  {
+    return offsetAnimationDropdown?.value?.id ?? "idle";
+  }
+
   function get_haxeUIFocused():Bool
   {
     // get the screen position, according to the HUD camera, temp default to FlxG.camera juuust in case?
@@ -87,46 +84,35 @@ class DebugBoundingState extends FlxState
   {
     Paths.setCurrentLevel('week1');
 
-    // lv.
-    // lv.onChange = function(e:UIEvent)
-    // {
-    // 	trace(e.type);
-    // 	// trace(e.data.curView);
-    // 	// var item:haxe.ui.core.ItemRenderer = cast e.target;
-    // 	trace(e.target);
-    // 	// if (e.type == "change")
-    // 	// {
-    // 	// 	curView = cast e.data;
-    // 	// }
-    // };
-
     hudCam = new FlxCamera();
     hudCam.bgColor.alpha = 0;
 
-    bg = FlxGridOverlay.create(10, 10);
-    // bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.GREEN);
-
-    bg.scrollFactor.set();
+    bg = new FlxBackdrop(FlxGridOverlay.createGrid(10, 10, FlxG.width, FlxG.height, true, 0xffe7e6e6, 0xffd9d5d5));
     add(bg);
 
     // we are setting this as the default draw camera only temporarily, to trick haxeui
     FlxG.cameras.add(hudCam);
 
     var str = Paths.xml('ui/animation-editor/offset-editor-view');
-    uiStuff = RuntimeComponentBuilder.fromAsset(str);
+    offsetEditorDialog = cast RuntimeComponentBuilder.fromAsset(str);
 
-    // uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
-    var dropdown:DropDown = cast uiStuff.findComponent("swapper");
-    dropdown.onChange = function(e:UIEvent) {
+    // offsetEditorDialog.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
+    var viewDropdown:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
+    viewDropdown.onChange = function(e:UIEvent) {
       trace(e.type);
       curView = cast e.data.curView;
       trace(e.data);
       // trace(e.data);
     };
 
-    uiStuff.cameras = [hudCam];
+    offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown);
 
-    add(uiStuff);
+    offsetEditorDialog.cameras = [hudCam];
+
+    add(offsetEditorDialog);
+
+    // Anchor to the right side by default
+    // offsetEditorDialog.x = FlxG.width - offsetEditorDialog.width;
 
     // sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf
     FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true);
@@ -159,7 +145,7 @@ class DebugBoundingState extends FlxState
 
     generateOutlines(tex.frames);
 
-    txtGrp = new FlxGroup();
+    txtGrp = new FlxTypedGroup<FlxText>();
     txtGrp.cameras = [hudCam];
     spriteSheetView.add(txtGrp);
 
@@ -168,64 +154,6 @@ class DebugBoundingState extends FlxState
     addInfo('Height', bf.height);
 
     spriteSheetView.add(swagOutlines);
-
-    FlxG.stage.window.onDropFile.add(function(path:String) {
-      // WACKY ASS TESTING SHIT FOR WEB FILE LOADING??
-      #if web
-      var swagList:FileList = cast path;
-
-      var objShit = js.html.URL.createObjectURL(swagList.item(0));
-      trace(objShit);
-
-      var funnysound = new FunkinSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false,
-        null, function() {
-          trace('LOADED SHIT??');
-      });
-
-      funnysound.volume = 1;
-      funnysound.play();
-
-      var urlShit = new URLLoader(new URLRequest(objShit));
-
-      new FlxTimer().start(3, function(tmr:FlxTimer) {
-        // music lol!
-        if (urlShit.dataFormat == BINARY)
-        {
-          // var daSwagBytes:ByteArray = urlShit.data;
-
-          // FlxG.sound.playMusic();
-
-          // trace('is binary!!');
-        }
-        trace(urlShit.dataFormat);
-      });
-
-      // remove(bf);
-      // FlxG.bitmap.removeByKey(Paths.image('characters/temp'));
-      // Assets.cache.clear();
-
-      // bf.loadGraphic(objShit);
-      // add(bf);
-
-      // trace(swagList.item(0).name);
-      // var urlShit = js.html.URL.createObjectURL(path);
-      #end
-
-      #if sys
-      trace("DROPPED FILE FROM: " + Std.string(path));
-      var newPath = "./" + Paths.image('characters/temp');
-      File.copy(path, newPath);
-
-      var swag = Paths.image('characters/temp');
-
-      if (bf != null) remove(bf);
-      FlxG.bitmap.removeByKey(Paths.image('characters/temp'));
-      Assets.cache.clear();
-
-      bf.loadGraphic(Paths.image('characters/temp'));
-      add(bf);
-      #end
-    });
   }
 
   function generateOutlines(frameShit:Array<FlxFrame>):Void
@@ -260,15 +188,9 @@ class DebugBoundingState extends FlxState
     txtOffsetShit = new FlxText(20, 20, 0, "", 20);
     txtOffsetShit.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
     txtOffsetShit.cameras = [hudCam];
+    txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
     offsetView.add(txtOffsetShit);
 
-    animDropDownMenu = new FlxUIDropDownMenu(0, 0, FlxUIDropDownMenu.makeStrIdLabelArray(['weed'], true));
-    animDropDownMenu.cameras = [hudCam];
-    // Move to bottom right corner
-    animDropDownMenu.x = FlxG.width - animDropDownMenu.width - 20;
-    animDropDownMenu.y = FlxG.height - animDropDownMenu.height - 20;
-    offsetView.add(animDropDownMenu);
-
     var characters:Array<String> = CharacterDataParser.listCharacterIds();
     characters = characters.filter(function(charId:String) {
       var char = CharacterDataParser.fetchCharacterData(charId);
@@ -276,7 +198,7 @@ class DebugBoundingState extends FlxState
     });
     characters.sort(SortUtil.alphabetically);
 
-    var charDropdown:DropDown = cast uiStuff.findComponent('characterDropdown');
+    var charDropdown:DropDown = offsetEditorDialog.findComponent('characterDropdown', DropDown);
     for (char in characters)
     {
       charDropdown.dataSource.add({text: char});
@@ -289,32 +211,47 @@ class DebugBoundingState extends FlxState
 
   public var mouseOffset:FlxPoint = FlxPoint.get(0, 0);
   public var oldPos:FlxPoint = FlxPoint.get(0, 0);
+  public var movingCharacter:Bool = false;
 
   function mouseOffsetMovement()
   {
     if (swagChar != null)
     {
-      if (FlxG.mouse.justPressed)
+      if (FlxG.mouse.justPressed && !haxeUIFocused)
       {
+        movingCharacter = true;
         mouseOffset.set(FlxG.mouse.x - -swagChar.animOffsets[0], FlxG.mouse.y - -swagChar.animOffsets[1]);
       }
 
+      if (!movingCharacter) return;
+
       if (FlxG.mouse.pressed)
       {
         swagChar.animOffsets = [(FlxG.mouse.x - mouseOffset.x) * -1, (FlxG.mouse.y - mouseOffset.y) * -1];
 
-        swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, swagChar.animOffsets);
+        swagChar.animationOffsets.set(offsetAnimationDropdown.value.id, swagChar.animOffsets);
 
         txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
+        txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
+      }
+
+      if (FlxG.mouse.justReleased)
+      {
+        movingCharacter = false;
       }
     }
   }
 
   function addInfo(str:String, value:Dynamic)
   {
-    var swagText:FlxText = new FlxText(10, 10 + (28 * txtGrp.length));
+    var swagText:FlxText = new FlxText(10, FlxG.height - 32);
     swagText.setFormat(Paths.font("vcr.ttf"), 26, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK);
     swagText.scrollFactor.set();
+
+    for (text in txtGrp.members)
+    {
+      text.y -= swagText.height;
+    }
     txtGrp.add(swagText);
 
     swagText.text = str + ": " + Std.string(value);
@@ -345,14 +282,14 @@ class DebugBoundingState extends FlxState
   {
     if (FlxG.keys.justPressed.ONE)
     {
-      var lv:DropDown = cast uiStuff.findComponent("swapper");
+      var lv:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
       lv.selectedIndex = 0;
       curView = SPRITESHEET;
     }
 
     if (FlxG.keys.justReleased.TWO)
     {
-      var lv:DropDown = cast uiStuff.findComponent("swapper");
+      var lv:DropDown = offsetEditorDialog.findComponent("swapper", DropDown);
       lv.selectedIndex = 1;
       curView = ANIMATIONS;
       if (swagChar != null)
@@ -368,12 +305,14 @@ class DebugBoundingState extends FlxState
         spriteSheetView.visible = true;
         offsetView.visible = false;
         offsetView.active = false;
+        offsetAnimationDropdown.visible = false;
       case ANIMATIONS:
         spriteSheetView.visible = false;
         offsetView.visible = true;
         offsetView.active = true;
+        offsetAnimationDropdown.visible = true;
         offsetControls();
-        if (!haxeUIFocused) mouseOffsetMovement();
+        mouseOffsetMovement();
     }
 
     if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
@@ -395,24 +334,36 @@ class DebugBoundingState extends FlxState
   {
     if (FlxG.keys.justPressed.RBRACKET || FlxG.keys.justPressed.E)
     {
-      if (Std.parseInt(animDropDownMenu.selectedId) + 1 <= animDropDownMenu.length)
-        animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId)
-        + 1);
+      if (offsetAnimationDropdown.selectedIndex + 1 <= offsetAnimationDropdown.dataSource.size)
+      {
+        offsetAnimationDropdown.selectedIndex += 1;
+      }
       else
-        animDropDownMenu.selectedId = Std.string(0);
-      playCharacterAnimation(animDropDownMenu.selectedId, true);
+      {
+        offsetAnimationDropdown.selectedIndex = 0;
+      }
+      trace(offsetAnimationDropdown.selectedIndex);
+      trace(offsetAnimationDropdown.dataSource.size);
+      trace(offsetAnimationDropdown.value);
+      trace(currentAnimationName);
+      playCharacterAnimation(currentAnimationName, true);
     }
     if (FlxG.keys.justPressed.LBRACKET || FlxG.keys.justPressed.Q)
     {
-      if (Std.parseInt(animDropDownMenu.selectedId) - 1 >= 0) animDropDownMenu.selectedId = Std.string(Std.parseInt(animDropDownMenu.selectedId) - 1);
+      if (offsetAnimationDropdown.selectedIndex - 1 >= 0)
+      {
+        offsetAnimationDropdown.selectedIndex -= 1;
+      }
       else
-        animDropDownMenu.selectedId = Std.string(animDropDownMenu.length - 1);
-      playCharacterAnimation(animDropDownMenu.selectedId, true);
+      {
+        offsetAnimationDropdown.selectedIndex = offsetAnimationDropdown.dataSource.size - 1;
+      }
+      playCharacterAnimation(currentAnimationName, true);
     }
 
     // Keyboards controls for general WASD "movement"
-    // modifies the animDropDownMenu so that it's properly updated and shit
-    // and then it's just played and updated from the animDropDownMenu callback, which is set in the loadAnimShit() function probabbly
+    // modifies the animDrooffsetAnimationDropdownpDownMenu so that it's properly updated and shit
+    // and then it's just played and updated from the offsetAnimationDropdown callback, which is set in the loadAnimShit() function probabbly
     if (FlxG.keys.justPressed.W || FlxG.keys.justPressed.S || FlxG.keys.justPressed.D || FlxG.keys.justPressed.A)
     {
       var suffix:String = '';
@@ -425,18 +376,19 @@ class DebugBoundingState extends FlxState
       if (FlxG.keys.justPressed.A) targetLabel = 'singLEFT$suffix';
       if (FlxG.keys.justPressed.D) targetLabel = 'singRIGHT$suffix';
 
-      if (targetLabel != animDropDownMenu.selectedLabel)
+      if (targetLabel != currentAnimationName)
       {
+        offsetAnimationDropdown.value = {id: targetLabel, text: targetLabel};
+
         // Play the new animation if the IDs are the different.
         // Override the onion skin.
-        animDropDownMenu.selectedLabel = targetLabel;
-        playCharacterAnimation(animDropDownMenu.selectedId, true);
+        playCharacterAnimation(currentAnimationName, true);
       }
       else
       {
         // Replay the current animation if the IDs are the same.
         // Don't override the onion skin.
-        playCharacterAnimation(animDropDownMenu.selectedId, false);
+        playCharacterAnimation(currentAnimationName, false);
       }
     }
 
@@ -448,16 +400,20 @@ class DebugBoundingState extends FlxState
     // Plays the idle animation
     if (FlxG.keys.justPressed.SPACE)
     {
-      animDropDownMenu.selectedLabel = 'idle';
-      playCharacterAnimation(animDropDownMenu.selectedId, true);
+      offsetAnimationDropdown.value = {id: 'idle', text: 'idle'};
+
+      playCharacterAnimation(currentAnimationName, true);
     }
 
     // Playback the animation
-    if (FlxG.keys.justPressed.ENTER) playCharacterAnimation(animDropDownMenu.selectedId, false);
+    if (FlxG.keys.justPressed.ENTER)
+    {
+      playCharacterAnimation(currentAnimationName, false);
+    }
 
     if (FlxG.keys.justPressed.RIGHT || FlxG.keys.justPressed.LEFT || FlxG.keys.justPressed.UP || FlxG.keys.justPressed.DOWN)
     {
-      var animName = animDropDownMenu.selectedLabel;
+      var animName = currentAnimationName;
       var coolValues:Array<Float> = swagChar.animationOffsets.get(animName).copy();
 
       var multiplier:Int = 5;
@@ -471,10 +427,11 @@ class DebugBoundingState extends FlxState
       else if (FlxG.keys.justPressed.UP) coolValues[1] += 1 * multiplier;
       else if (FlxG.keys.justPressed.DOWN) coolValues[1] -= 1 * multiplier;
 
-      swagChar.animationOffsets.set(animDropDownMenu.selectedLabel, coolValues);
+      swagChar.animationOffsets.set(currentAnimationName, coolValues);
       swagChar.playAnimation(animName);
 
       txtOffsetShit.text = 'Offset: ' + coolValues;
+      txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
 
       trace(animName);
     }
@@ -529,7 +486,7 @@ class DebugBoundingState extends FlxState
     swagChar = CharacterDataParser.fetchCharacter(char);
     swagChar.x = 100;
     swagChar.y = 100;
-    // swagChar.debugMode = true;
+    swagChar.debug = true;
     offsetView.add(swagChar);
 
     if (swagChar == null || swagChar.frames == null)
@@ -554,11 +511,25 @@ class DebugBoundingState extends FlxState
       trace(swagChar.animationOffsets[i]);
     }
 
-    animDropDownMenu.setData(FlxUIDropDownMenu.makeStrIdLabelArray(characterAnimNames, true));
-    animDropDownMenu.callback = function(str:String) {
-      playCharacterAnimation(str, true);
-    };
+    offsetAnimationDropdown.dataSource.clear();
+
+    for (charAnim in characterAnimNames)
+    {
+      trace('Adding ${charAnim} to HaxeUI dropdown');
+      offsetAnimationDropdown.dataSource.add({id: charAnim, text: charAnim});
+    }
+
+    offsetAnimationDropdown.selectedIndex = 0;
+
+    trace('Added ${offsetAnimationDropdown.dataSource.size} to HaxeUI dropdown');
+
+    offsetAnimationDropdown.onChange = function(event:UIEvent) {
+      trace('Selected animation ${event?.data?.id}');
+      playCharacterAnimation(event.data.id, true);
+    }
+
     txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
+    txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
     dropDownSetup = true;
   }
 
@@ -575,11 +546,13 @@ class DebugBoundingState extends FlxState
       onionSkinChar.alpha = 0.6;
     }
 
-    var animName = characterAnimNames[Std.parseInt(str)];
+    // var animName = characterAnimNames[Std.parseInt(str)];
+    var animName = str;
     swagChar.playAnimation(animName, true); // trace();
     trace(swagChar.animationOffsets.get(animName));
 
     txtOffsetShit.text = 'Offset: ' + swagChar.animOffsets;
+    txtOffsetShit.y = FlxG.height - 20 - txtOffsetShit.height;
   }
 
   var _file:FileReference;
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index 09a06661d..ebfbe5eac 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -5699,7 +5699,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
     // TODO: Rework asset system so we can remove this jank.
     switch (currentSongStage)
     {
-      case 'mainStage':
+      case 'mainStage' | 'mainStageErect':
         PlayStatePlaylist.campaignId = 'week1';
       case 'spookyMansion' | 'spookyMansionErect':
         PlayStatePlaylist.campaignId = 'week2';
diff --git a/source/funkin/ui/freeplay/AlbumRoll.hx b/source/funkin/ui/freeplay/AlbumRoll.hx
index 49c588722..36dba0054 100644
--- a/source/funkin/ui/freeplay/AlbumRoll.hx
+++ b/source/funkin/ui/freeplay/AlbumRoll.hx
@@ -37,6 +37,7 @@ class AlbumRoll extends FlxSpriteGroup
   }
 
   var newAlbumArt:FlxAtlasSprite;
+  var albumTitle:FunkinSprite;
 
   var difficultyStars:DifficultyStars;
   var _exitMovers:Null<FreeplayState.ExitMoverData>;
@@ -59,24 +60,27 @@ class AlbumRoll extends FlxSpriteGroup
   {
     super();
 
-    newAlbumArt = new FlxAtlasSprite(0, 0, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
+    newAlbumArt = new FlxAtlasSprite(640, 350, Paths.animateAtlas("freeplay/albumRoll/freeplayAlbum"));
     newAlbumArt.visible = false;
-    newAlbumArt.onAnimationFinish.add(onAlbumFinish);
+    newAlbumArt.onAnimationComplete.add(onAlbumFinish);
 
     add(newAlbumArt);
 
     difficultyStars = new DifficultyStars(140, 39);
     difficultyStars.visible = false;
     add(difficultyStars);
+
+    buildAlbumTitle("freeplay/albumRoll/volume1-text");
+    albumTitle.visible = false;
   }
 
   function onAlbumFinish(animName:String):Void
   {
     // Play the idle animation for the current album.
-    newAlbumArt.playAnimation(animNames.get('$albumId-idle'), false, false, true);
-
-    // End on the last frame and don't continue until playAnimation is called again.
-    // newAlbumArt.anim.pause();
+    if (animName != "idle")
+    {
+      // newAlbumArt.playAnimation('idle', true);
+    }
   }
 
   /**
@@ -104,6 +108,12 @@ class AlbumRoll extends FlxSpriteGroup
       return;
     };
 
+    // Update the album art.
+    var albumGraphic = Paths.image(albumData.getAlbumArtAssetKey());
+    newAlbumArt.replaceFrameGraphic(0, albumGraphic);
+
+    buildAlbumTitle(albumData.getAlbumTitleAssetKey());
+
     applyExitMovers();
 
     refresh();
@@ -146,19 +156,57 @@ class AlbumRoll extends FlxSpriteGroup
    */
   public function playIntro():Void
   {
+    albumTitle.visible = false;
     newAlbumArt.visible = true;
-    newAlbumArt.playAnimation(animNames.get('$albumId-active'), false, false, false);
+    newAlbumArt.playAnimation('intro', true);
 
     difficultyStars.visible = false;
     new FlxTimer().start(0.75, function(_) {
-      // showTitle();
+      showTitle();
       showStars();
+      albumTitle.animation.play('switch');
     });
   }
 
   public function skipIntro():Void
   {
-    newAlbumArt.playAnimation(animNames.get('$albumId-trans'), false, false, false);
+    // Weird workaround
+    newAlbumArt.playAnimation('switch', true);
+    albumTitle.animation.play('switch');
+  }
+
+  public function showTitle():Void
+  {
+    albumTitle.visible = true;
+  }
+
+  public function buildAlbumTitle(assetKey:String):Void
+  {
+    if (albumTitle != null)
+    {
+      remove(albumTitle);
+      albumTitle = null;
+    }
+
+    albumTitle = FunkinSprite.createSparrow(925, 500, assetKey);
+    albumTitle.visible = albumTitle.frames != null && newAlbumArt.visible;
+    albumTitle.animation.addByPrefix('idle', 'idle0', 24, true);
+    albumTitle.animation.addByPrefix('switch', 'switch0', 24, false);
+    add(albumTitle);
+
+    albumTitle.animation.finishCallback = (function(name) {
+      if (name == 'switch') albumTitle.animation.play('idle');
+    });
+    albumTitle.animation.play('idle');
+
+    albumTitle.zIndex = 1000;
+
+    if (_exitMovers != null) _exitMovers.set([albumTitle],
+      {
+        x: FlxG.width,
+        speed: 0.4,
+        wait: 0
+      });
   }
 
   public function setDifficultyStars(?difficulty:Int):Void
diff --git a/source/funkin/ui/freeplay/FreeplayDJ.hx b/source/funkin/ui/freeplay/FreeplayDJ.hx
index 72eddd0ca..317a52308 100644
--- a/source/funkin/ui/freeplay/FreeplayDJ.hx
+++ b/source/funkin/ui/freeplay/FreeplayDJ.hx
@@ -43,7 +43,7 @@ class FreeplayDJ extends FlxAtlasSprite
 
     super(x, y, playableCharData.getAtlasPath());
 
-    anim.callback = function(name, number) {
+    onAnimationFrame.add(function(name, number) {
       if (name == playableCharData.getAnimationPrefix('cartoon'))
       {
         if (number == playableCharData.getCartoonSoundClickFrame())
@@ -55,12 +55,12 @@ class FreeplayDJ extends FlxAtlasSprite
           runTvLogic();
         }
       }
-    };
+    });
 
     FlxG.debugger.track(this);
     FlxG.console.registerObject("dj", this);
 
-    anim.onComplete = onFinishAnim;
+    onAnimationComplete.add(onFinishAnim);
 
     FlxG.console.registerFunction("freeplayCartoon", function() {
       currentState = Cartoon;
@@ -96,7 +96,7 @@ class FreeplayDJ extends FlxAtlasSprite
         var animPrefix = playableCharData.getAnimationPrefix('idle');
         if (getCurrentAnimation() != animPrefix)
         {
-          playFlashAnimation(animPrefix, true);
+          playFlashAnimation(animPrefix, true, false, true);
         }
 
         if (getCurrentAnimation() == animPrefix && this.isLoopFinished())
@@ -120,7 +120,7 @@ class FreeplayDJ extends FlxAtlasSprite
         if (getCurrentAnimation() != animPrefix) playFlashAnimation('Boyfriend DJ fist pump', false);
         if (getCurrentAnimation() == animPrefix && anim.curFrame >= 4)
         {
-          anim.play("Boyfriend DJ fist pump", true, false, 0);
+          playAnimation("Boyfriend DJ fist pump", true, false, false, 0);
         }
       case FistPump:
 
@@ -135,9 +135,12 @@ class FreeplayDJ extends FlxAtlasSprite
         timeIdling = 0;
       case Cartoon:
         var animPrefix = playableCharData.getAnimationPrefix('cartoon');
-        if (animPrefix == null) {
+        if (animPrefix == null)
+        {
           currentState = IdleEasterEgg;
-        } else {
+        }
+        else
+        {
           if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true);
           timeIdling = 0;
         }
@@ -145,6 +148,7 @@ class FreeplayDJ extends FlxAtlasSprite
         // I shit myself.
     }
 
+    #if debug
     if (FlxG.keys.pressed.CONTROL)
     {
       if (FlxG.keys.justPressed.LEFT)
@@ -167,16 +171,17 @@ class FreeplayDJ extends FlxAtlasSprite
         this.offsetY += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0);
       }
 
-      if (FlxG.keys.justPressed.SPACE)
+      if (FlxG.keys.justPressed.C)
       {
         currentState = (currentState == Idle ? Cartoon : Idle);
       }
     }
+    #end
   }
 
-  function onFinishAnim():Void
+  function onFinishAnim(name:String):Void
   {
-    var name = anim.curSymbol.name;
+    // var name = anim.curSymbol.name;
 
     if (name == playableCharData.getAnimationPrefix('intro'))
     {
@@ -220,7 +225,7 @@ class FreeplayDJ extends FlxAtlasSprite
         // runTvLogic();
       }
       trace('Replay idle: ${frame}');
-      anim.play(playableCharData.getAnimationPrefix('cartoon'), true, false, frame);
+      playAnimation(playableCharData.getAnimationPrefix('cartoon'), true, false, false, frame);
       // trace('Finished confirm');
     }
     else
@@ -266,7 +271,7 @@ class FreeplayDJ extends FlxAtlasSprite
   function loadCartoon()
   {
     cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() {
-      anim.play("Boyfriend DJ watchin tv OG", true, false, 60);
+      playAnimation("Boyfriend DJ watchin tv OG", true, false, false, 60);
     });
 
     // Fade out music to 40% volume over 1 second.
@@ -304,13 +309,13 @@ class FreeplayDJ extends FlxAtlasSprite
   public function pumpFist():Void
   {
     currentState = FistPump;
-    anim.play("Boyfriend DJ fist pump", true, false, 4);
+    playAnimation("Boyfriend DJ fist pump", true, false, false, 4);
   }
 
   public function pumpFistBad():Void
   {
     currentState = FistPump;
-    anim.play("Boyfriend DJ loss reaction 1", true, false, 4);
+    playAnimation("Boyfriend DJ loss reaction 1", true, false, false, 4);
   }
 
   override public function getCurrentAnimation():String
@@ -319,9 +324,9 @@ class FreeplayDJ extends FlxAtlasSprite
     return this.anim.curSymbol.name;
   }
 
-  public function playFlashAnimation(id:String, ?Force:Bool = false, ?Reverse:Bool = false, ?Frame:Int = 0):Void
+  public function playFlashAnimation(id:String, Force:Bool = false, Reverse:Bool = false, Loop:Bool = false, Frame:Int = 0):Void
   {
-    anim.play(id, Force, Reverse, Frame);
+    playAnimation(id, Force, Reverse, Loop, Frame);
     applyAnimOffset();
   }
 
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 690e3b910..03eb12ead 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -1,7 +1,6 @@
 package funkin.ui.freeplay;
 
 import flixel.addons.transition.FlxTransitionableState;
-import flixel.addons.ui.FlxInputText;
 import flixel.FlxCamera;
 import flixel.FlxSprite;
 import flixel.group.FlxGroup;
@@ -1795,7 +1794,7 @@ class FreeplayState extends MusicBeatSubState
     confirmGlow.visible = true;
     confirmGlow2.visible = true;
 
-    backingTextYeah.anim.play("BF back card confirm raw", false, false, 0);
+    backingTextYeah.playAnimation("BF back card confirm raw", false, false, false, 0);
     confirmGlow2.alpha = 0;
     confirmGlow.alpha = 0;