diff --git a/osu-framework b/osu-framework
index c6f030d6f1..8baad1b948 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit c6f030d6f1ab65a48de9ff1d0c424acb686e0149
+Subproject commit 8baad1b9484b9f35724e2f965c18cfe710907d80
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
new file mode 100644
index 0000000000..4cda14559f
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDirect.cs
@@ -0,0 +1,199 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Database;
+using osu.Game.Overlays;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    public class TestCaseDirect : TestCase
+    {
+        public override string Description => @"osu!direct overlay";
+
+        private DirectOverlay direct;
+        private RulesetDatabase rulesets;
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            Add(direct = new DirectOverlay());
+            newBeatmaps();
+
+            AddStep(@"toggle", direct.ToggleVisibility);
+            AddStep(@"result counts", () => direct.ResultAmounts = new DirectOverlay.ResultCounts(1, 4, 13));
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(RulesetDatabase rulesets)
+        {
+            this.rulesets = rulesets;
+        }
+
+        private void newBeatmaps()
+        {
+            var ruleset = rulesets.GetRuleset(0);
+
+            direct.BeatmapSets = new[]
+            {
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"OrVid",
+                        Artist = @"An",
+                        Author = @"RLC",
+                        Source = @"",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.35f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/578332/covers/cover.jpg?1494591390" },
+                                Preview = @"https://b.ppy.sh/preview/578332.mp3",
+                                PlayCount = 97,
+                                FavouriteCount = 72,
+                            },
+                        },
+                    },
+                },
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"tiny lamp",
+                        Artist = @"fhana",
+                        Author = @"Sotarks",
+                        Source = @"ぎんぎつね",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.81f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/599627/covers/cover.jpg?1494539318" },
+                                Preview = @"https//b.ppy.sh/preview/599627.mp3",
+                                PlayCount = 3082,
+                                FavouriteCount = 14,
+                            },
+                        },
+                    },
+                },
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"At Gwanghwamun",
+                        Artist = @"KYUHYUN",
+                        Author = @"Cerulean Veyron",
+                        Source = @"",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 0.9f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/513268/covers/cover.jpg?1494502863" },
+                                Preview = @"https//b.ppy.sh/preview/513268.mp3",
+                                PlayCount = 2762,
+                                FavouriteCount = 15,
+                            },
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 1.1f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.02f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.49f,
+                        },
+                    },
+                },
+                new BeatmapSetInfo
+                {
+                    Metadata = new BeatmapMetadata
+                    {
+                        Title = @"RHAPSODY OF BLUE SKY",
+                        Artist = @"fhana",
+                        Author = @"[Kamiya]",
+                        Source = @"小林さんちのメイドラゴン",
+                    },
+                    Beatmaps = new List<BeatmapInfo>
+                    {
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 1.26f,
+                            Metadata = new BeatmapMetadata(),
+                            OnlineInfo = new BeatmapOnlineInfo
+                            {
+                                Covers = new[] { @"https://assets.ppy.sh//beatmaps/586841/covers/cover.jpg?1494052741" },
+                                Preview = @"https//b.ppy.sh/preview/586841.mp3",
+                                PlayCount = 62317,
+                                FavouriteCount = 161,
+                            },
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.01f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 2.87f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.76f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 3.93f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 4.37f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.13f,
+                        },
+                        new BeatmapInfo
+                        {
+                            Ruleset = ruleset,
+                            StarDifficulty = 5.42f,
+                        },
+                    },
+                },
+            };
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
new file mode 100644
index 0000000000..de58323abe
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseDrawableRoom.cs
@@ -0,0 +1,74 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Screens.Multiplayer;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Users;
+using osu.Game.Database;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    internal class TestCaseDrawableRoom : TestCase
+    {
+        public override string Description => @"Select your favourite room";
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            DrawableRoom first;
+            DrawableRoom second;
+            Add(new FillFlowContainer
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                AutoSizeAxes = Axes.Y,
+                Width = 500f,
+                Direction = FillDirection.Vertical,
+                Children = new Drawable[]
+                {
+                    first = new DrawableRoom(new Room()),
+                    second = new DrawableRoom(new Room()),
+                }
+            });
+
+            first.Room.Name.Value = @"Great Room Right Here";
+            first.Room.Host.Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" }};
+            first.Room.Status.Value = new RoomStatusOpen();
+            first.Room.Beatmap.Value = new BeatmapMetadata { Title = @"Seiryu", Artist = @"Critical Crystal" };
+
+            second.Room.Name.Value = @"Relax It's The Weekend";
+            second.Room.Host.Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" }};
+            second.Room.Status.Value = new RoomStatusPlaying();
+            second.Room.Beatmap.Value = new BeatmapMetadata { Title = @"ZAQ", Artist = @"Serendipity" };
+
+            AddStep(@"change state", () =>
+            {
+                first.Room.Status.Value = new RoomStatusPlaying();
+            });
+
+            AddStep(@"change name", () =>
+            {
+                first.Room.Name.Value = @"I Changed Name";
+            });
+
+            AddStep(@"change host", () =>
+            {
+                first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } };
+            });
+
+            AddStep(@"change beatmap", () =>
+            {
+                first.Room.Beatmap.Value = null;
+            });
+
+            AddStep(@"change state", () =>
+            {
+                first.Room.Status.Value = new RoomStatusOpen();
+            });
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
new file mode 100644
index 0000000000..513bf24e0d
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseUserPanel.cs
@@ -0,0 +1,57 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Testing;
+using osu.Framework.Graphics;
+using osu.Game.Users;
+using osu.Framework.Graphics.Containers;
+using OpenTK;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    internal class TestCaseUserPanel : TestCase
+    {
+        public override string Description => @"Panels for displaying a user's status";
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            UserPanel flyte;
+            UserPanel peppy;
+            Add(new FillFlowContainer
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                AutoSizeAxes = Axes.Both,
+                Spacing = new Vector2(10f),
+                Children = new[]
+                {
+                    flyte = new UserPanel(new User
+                    {
+                        Username = @"flyte",
+                        Id = 3103765,
+                        Country = new Country { FlagName = @"JP" },
+                        CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3103765/5b012e13611d5761caa7e24fecb3d3a16e1cf48fc2a3032cfd43dd444af83d82.jpeg"
+                    }) { Width = 300 },
+                    peppy = new UserPanel(new User
+                    {
+                        Username = @"peppy",
+                        Id = 2,
+                        Country = new Country { FlagName = @"AU" },
+                        CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/08cad88747c235a64fca5f1b770e100f120827ded1ffe3b66bfcd19c940afa65.jpeg"
+                    }) { Width = 300 },
+                },
+            });
+
+            flyte.Status.Value = new UserStatusOnline();
+            peppy.Status.Value = new UserStatusSoloGame();
+
+            AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); });
+            AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); });
+            AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); });
+            AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); });
+            AddStep(@"null status", () => { flyte.Status.Value = null; });
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index dbb1750b72..7b7997063b 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -220,6 +220,9 @@
     <Compile Include="Tests\TestCaseLeaderboard.cs" />
     <Compile Include="Beatmaps\TestWorkingBeatmap.cs" />
     <Compile Include="Tests\TestCaseBeatmapDetailArea.cs" />
+    <Compile Include="Tests\TestCaseDrawableRoom.cs" />
+    <Compile Include="Tests\TestCaseUserPanel.cs" />
+    <Compile Include="Tests\TestCaseDirect.cs" />
   </ItemGroup>
   <ItemGroup />
   <ItemGroup />
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index eff60ba935..6ba499739a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -24,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects
             base.ApplyDefaults(controlPointInfo, difficulty);
 
             SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
+
+            // spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
+            SpinsRequired = (int)(SpinsRequired * 0.6);
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
index 9f91488fe3..3ea05b6558 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs
@@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Game.Graphics.Backgrounds;
 using OpenTK.Graphics;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Framework.Audio.Track;
 
 namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 {
@@ -22,6 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
         public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f;
         public const float SYMBOL_BORDER = 8;
         public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER;
+        private const double pre_beat_transition_time = 80;
 
         /// <summary>
         /// The colour of the inner circle and outer glows.
@@ -63,6 +66,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 
         public CirclePiece(bool isStrong = false)
         {
+            EarlyActivationMilliseconds = pre_beat_transition_time;
+
             AddInternal(new Drawable[]
             {
                 background = new CircularContainer
@@ -139,14 +144,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
             Content.Width = 1 / Content.Scale.X;
         }
 
+        private const float edge_alpha_kiai = 0.5f;
+
         private void resetEdgeEffects()
         {
             background.EdgeEffect = new EdgeEffect
             {
                 Type = EdgeEffectType.Glow,
-                Colour = AccentColour,
-                Radius = KiaiMode ? 50 : 8
+                Colour = AccentColour.Opacity(KiaiMode ? edge_alpha_kiai : 1f),
+                Radius = KiaiMode ? 32 : 8
             };
         }
+
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        {
+            if (!effectPoint.KiaiMode)
+                return;
+
+            if (beatIndex % (int)timingPoint.TimeSignature != 0)
+                return;
+
+            double duration = timingPoint.BeatLength * 2;
+
+            background.FadeEdgeEffectTo(1, pre_beat_transition_time, EasingTypes.OutQuint);
+            using (background.BeginDelayedSequence(pre_beat_transition_time))
+                background.FadeEdgeEffectTo(edge_alpha_kiai, duration, EasingTypes.OutQuint);
+        }
     }
 }
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
index 83b2e59e44..5e7e9e6350 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs
@@ -1,14 +1,14 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Framework.Graphics.Containers;
 using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
 {
-    public class TaikoPiece : Container, IHasAccentColour
+    public class TaikoPiece : BeatSyncedContainer, IHasAccentColour
     {
         private Color4 accentColour;
         /// <summary>
@@ -17,10 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
         public virtual Color4 AccentColour
         {
             get { return accentColour; }
-            set
-            {
-                accentColour = value;
-            }
+            set { accentColour = value; }
         }
 
         private bool kiaiMode;
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 82ceaeed1a..79bb901112 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// <summary>
         /// Diameter of a circle relative to the size of the <see cref="TaikoPlayfield"/>.
         /// </summary>
-        public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f;
+        public const float PLAYFIELD_RELATIVE_DIAMETER = 0.45f;
 
         /// <summary>
         /// Scale multiplier for a strong circle.
         /// </summary>
-        public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f;
+        public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.4f;
 
         /// <summary>
         /// Default circle diameter.
diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
index 2ebdeaa5b0..c0c329c870 100644
--- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs
@@ -18,22 +18,23 @@ namespace osu.Game.Rulesets.Taiko.UI
     /// </summary>
     internal class HitExplosion : CircularContainer
     {
-        /// <summary>
-        /// The judgement this hit explosion visualises.
-        /// </summary>
         public readonly TaikoJudgement Judgement;
 
         private readonly Box innerFill;
 
-        public HitExplosion(TaikoJudgement judgement)
-        {
-            Judgement = judgement;
+        private readonly bool isRim;
 
-            Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
+        public HitExplosion(TaikoJudgement judgement, bool isRim)
+        {
+            this.isRim = isRim;
+
+            Judgement = judgement;
 
             Anchor = Anchor.Centre;
             Origin = Anchor.Centre;
 
+            Size = new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_CIRCLE_DIAMETER);
+
             RelativePositionAxes = Axes.Both;
 
             BorderColour = Color4.White;
@@ -54,22 +55,14 @@ namespace osu.Game.Rulesets.Taiko.UI
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            switch (Judgement.TaikoResult)
-            {
-                case TaikoHitResult.Good:
-                    innerFill.Colour = colours.Green;
-                    break;
-                case TaikoHitResult.Great:
-                    innerFill.Colour = colours.Blue;
-                    break;
-            }
+            innerFill.Colour = isRim ? colours.BlueDarker : colours.PinkDarker;
         }
 
         protected override void LoadComplete()
         {
             base.LoadComplete();
 
-            ScaleTo(5f, 1000, EasingTypes.OutQuint);
+            ScaleTo(3f, 1000, EasingTypes.OutQuint);
             FadeOut(500);
 
             Expire();
@@ -80,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// </summary>
         public void VisualiseSecondHit()
         {
-            ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50);
+            ResizeTo(new Vector2(TaikoPlayfield.HIT_TARGET_OFFSET + TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), 50);
         }
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
new file mode 100644
index 0000000000..e0da3ed3db
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs
@@ -0,0 +1,68 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Taiko.Judgements;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.UI
+{
+    public class KiaiHitExplosion : CircularContainer
+    {
+        public readonly TaikoJudgement Judgement;
+
+        private readonly bool isRim;
+
+        public KiaiHitExplosion(TaikoJudgement judgement, bool isRim)
+        {
+            this.isRim = isRim;
+
+            Judgement = judgement;
+
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
+
+            RelativeSizeAxes = Axes.Y;
+            Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER, 1);
+
+            Masking = true;
+            Alpha = 0.25f;
+
+            Children = new[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Alpha = 0,
+                    AlwaysPresent = true
+                }
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Glow,
+                Colour = isRim ? colours.BlueDarker : colours.PinkDarker,
+                Radius = 60,
+            };
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            ScaleTo(new Vector2(1, 3f), 500, EasingTypes.OutQuint);
+            FadeOut(250);
+
+            Expire();
+        }
+    }
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 2278158506..c7bd4a6704 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -24,12 +24,12 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// <summary>
         /// The default play field height.
         /// </summary>
-        public const float DEFAULT_PLAYFIELD_HEIGHT = 168f;
+        public const float DEFAULT_PLAYFIELD_HEIGHT = 178f;
 
         /// <summary>
         /// The offset from <see cref="left_area_size"/> which the center of the hit target lies at.
         /// </summary>
-        private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
+        public const float HIT_TARGET_OFFSET = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40;
 
         /// <summary>
         /// The size of the left area of the playfield. This area contains the input drum.
@@ -39,15 +39,18 @@ namespace osu.Game.Rulesets.Taiko.UI
         protected override Container<Drawable> Content => hitObjectContainer;
 
         private readonly Container<HitExplosion> hitExplosionContainer;
+        private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
         private readonly Container<DrawableBarLine> barLineContainer;
         private readonly Container<DrawableTaikoJudgement> judgementContainer;
 
         private readonly Container hitObjectContainer;
         private readonly Container topLevelHitContainer;
-        private readonly Container leftBackgroundContainer;
-        private readonly Container rightBackgroundContainer;
-        private readonly Box leftBackground;
-        private readonly Box rightBackground;
+
+        private readonly Container overlayBackgroundContainer;
+        private readonly Container backgroundContainer;
+
+        private readonly Box overlayBackground;
+        private readonly Box background;
 
         public TaikoPlayfield()
         {
@@ -59,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                     Height = DEFAULT_PLAYFIELD_HEIGHT,
                     Children = new[]
                     {
-                        rightBackgroundContainer = new Container
+                        backgroundContainer = new Container
                         {
                             Name = "Transparent playfield background",
                             RelativeSizeAxes = Axes.Both,
@@ -73,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.UI
                             },
                             Children = new Drawable[]
                             {
-                                rightBackground = new Box
+                                background = new Box
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                     Alpha = 0.6f
@@ -82,24 +85,23 @@ namespace osu.Game.Rulesets.Taiko.UI
                         },
                         new Container
                         {
-                            Name = "Transparent playfield elements",
+                            Name = "Right area",
                             RelativeSizeAxes = Axes.Both,
-                            Padding = new MarginPadding { Left = left_area_size },
+                            Margin = new MarginPadding { Left = left_area_size },
                             Children = new Drawable[]
                             {
                                 new Container
                                 {
-                                    Name = "Hit target container",
-                                    X = hit_target_offset,
+                                    Name = "Masked elements",
                                     RelativeSizeAxes = Axes.Both,
+                                    Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
+                                    Masking = true,
                                     Children = new Drawable[]
                                     {
                                         hitExplosionContainer = new Container<HitExplosion>
                                         {
-                                            Anchor = Anchor.CentreLeft,
-                                            Origin = Anchor.Centre,
                                             RelativeSizeAxes = Axes.Y,
-                                            BlendingMode = BlendingMode.Additive
+                                            BlendingMode = BlendingMode.Additive,
                                         },
                                         barLineContainer = new Container<DrawableBarLine>
                                         {
@@ -114,23 +116,32 @@ namespace osu.Game.Rulesets.Taiko.UI
                                         {
                                             RelativeSizeAxes = Axes.Both,
                                         },
-                                        judgementContainer = new Container<DrawableTaikoJudgement>
-                                        {
-                                            RelativeSizeAxes = Axes.Y,
-                                            BlendingMode = BlendingMode.Additive
-                                        },
-                                    },
+                                    }
+                                },
+                                kiaiExplosionContainer = new Container<KiaiHitExplosion>
+                                {
+                                    Name = "Kiai hit explosions",
+                                    RelativeSizeAxes = Axes.Y,
+                                    Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
+                                    BlendingMode = BlendingMode.Additive
+                                },
+                                judgementContainer = new Container<DrawableTaikoJudgement>
+                                {
+                                    Name = "Judgements",
+                                    RelativeSizeAxes = Axes.Y,
+                                    Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
+                                    BlendingMode = BlendingMode.Additive
                                 },
                             }
                         },
-                        leftBackgroundContainer = new Container
+                        overlayBackgroundContainer = new Container
                         {
                             Name = "Left overlay",
                             Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT),
                             BorderThickness = 1,
                             Children = new Drawable[]
                             {
-                                leftBackground = new Box
+                                overlayBackground = new Box
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                 },
@@ -164,11 +175,11 @@ namespace osu.Game.Rulesets.Taiko.UI
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            leftBackgroundContainer.BorderColour = colours.Gray0;
-            leftBackground.Colour = colours.Gray1;
+            overlayBackgroundContainer.BorderColour = colours.Gray0;
+            overlayBackground.Colour = colours.Gray1;
 
-            rightBackgroundContainer.BorderColour = colours.Gray1;
-            rightBackground.Colour = colours.Gray0;
+            backgroundContainer.BorderColour = colours.Gray1;
+            background.Colour = colours.Gray0;
         }
 
         public override void Add(DrawableHitObject<TaikoHitObject, TaikoJudgement> h)
@@ -204,6 +215,8 @@ namespace osu.Game.Rulesets.Taiko.UI
             if (!wasHit)
                 return;
 
+            bool isRim = judgedObject.HitObject is RimHit;
+
             if (!secondHit)
             {
                 if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell))
@@ -212,7 +225,11 @@ namespace osu.Game.Rulesets.Taiko.UI
                     topLevelHitContainer.Add(judgedObject.CreateProxy());
                 }
 
-                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement));
+                hitExplosionContainer.Add(new HitExplosion(judgedObject.Judgement, isRim));
+
+                if (judgedObject.HitObject.Kiai)
+                    kiaiExplosionContainer.Add(new KiaiHitExplosion(judgedObject.Judgement, isRim));
+
             }
             else
                 hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
@@ -221,7 +238,7 @@ namespace osu.Game.Rulesets.Taiko.UI
         /// <summary>
         /// This is a very special type of container. It serves a similar purpose to <see cref="FillMode.Fit"/>, however unlike <see cref="FillMode.Fit"/>,
         /// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent.
-        /// 
+        ///
         /// <para>
         /// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable
         /// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this
diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
index 983dc72d9e..8d6fcb503c 100644
--- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
+++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj
@@ -87,6 +87,7 @@
     <Compile Include="Scoring\TaikoScoreProcessor.cs" />
     <Compile Include="UI\HitTarget.cs" />
     <Compile Include="UI\InputDrum.cs" />
+    <Compile Include="UI\KiaiHitExplosion.cs" />
     <Compile Include="UI\DrawableTaikoJudgement.cs" />
     <Compile Include="UI\HitExplosion.cs" />
     <Compile Include="UI\TaikoHitRenderer.cs" />
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 5740c961b1..acf90931ac 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps.ControlPoints
         /// </summary>
         /// <param name="time">The time to find the timing control point at.</param>
         /// <returns>The timing control point.</returns>
-        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time);
+        public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault());
 
         /// <summary>
         /// Finds the maximum BPM represented by any timing control point.
@@ -75,14 +75,21 @@ namespace osu.Game.Beatmaps.ControlPoints
         public double BPMMode =>
             60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
 
-        private T binarySearch<T>(SortedList<T> list, double time)
+        /// <summary>
+        /// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
+        /// </summary>
+        /// <param name="list">The list to search.</param>
+        /// <param name="time">The time to find the control point at.</param>
+        /// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
+        /// <returns>The active control point at <paramref name="time"/>.</returns>
+        private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
             where T : ControlPoint, new()
         {
             if (list.Count == 0)
                 return new T();
 
             if (time < list[0].Time)
-                return new T();
+                return prePoint ?? new T();
 
             int index = list.BinarySearch(new T() { Time = time });
 
@@ -92,8 +99,8 @@ namespace osu.Game.Beatmaps.ControlPoints
 
             index = ~index;
 
-            if (index == list.Count)
-                return list[list.Count - 1];
+            // BinarySearch will return the index of the first element _greater_ than the search
+            // This is the inactive point - the active point is the one before it (index - 1)
             return list[index - 1];
         }
     }
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
index 7c0aa49d2a..e91b52565a 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs
@@ -9,7 +9,7 @@ using OpenTK.Graphics;
 
 namespace osu.Game.Beatmaps.Drawables
 {
-    internal class DifficultyColouredContainer : Container, IHasAccentColour
+    public class DifficultyColouredContainer : Container, IHasAccentColour
     {
         public Color4 AccentColour { get; set; }
 
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index a8b63c2502..9df1f0f284 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -11,7 +11,7 @@ using OpenTK.Graphics;
 namespace osu.Game.Beatmaps.Drawables
 {
 
-    internal class DifficultyIcon : DifficultyColouredContainer
+    public class DifficultyIcon : DifficultyColouredContainer
     {
         private readonly BeatmapInfo beatmap;
 
diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs
index c2e35d64a8..9f253f6055 100644
--- a/osu.Game/Database/BeatmapInfo.cs
+++ b/osu.Game/Database/BeatmapInfo.cs
@@ -43,6 +43,9 @@ namespace osu.Game.Database
         [Ignore]
         public BeatmapMetrics Metrics { get; set; }
 
+        [Ignore]
+        public BeatmapOnlineInfo OnlineInfo { get; set; }
+
         public string Path { get; set; }
 
         [JsonProperty("file_md5")]
diff --git a/osu.Game/Database/BeatmapOnlineInfo.cs b/osu.Game/Database/BeatmapOnlineInfo.cs
new file mode 100644
index 0000000000..bcb2ef16ac
--- /dev/null
+++ b/osu.Game/Database/BeatmapOnlineInfo.cs
@@ -0,0 +1,38 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace osu.Game.Database
+{
+    /// <summary>
+    /// Beatmap info retrieved for previewing locally without having the beatmap downloaded.
+    /// </summary>
+    public class BeatmapOnlineInfo
+    {
+        /// <summary>
+        /// The different sizes of cover art for this beatmap: cover, cover@2x, card, card@2x, list, list@2x.
+        /// </summary>
+        [JsonProperty(@"covers")]
+        public IEnumerable<string> Covers { get; set; }
+
+        /// <summary>
+        /// A small sample clip of this beatmap's song.
+        /// </summary>
+        [JsonProperty(@"previewUrl")]
+        public string Preview { get; set; }
+
+        /// <summary>
+        /// The amount of plays this beatmap has.
+        /// </summary>
+        [JsonProperty(@"play_count")]
+        public int PlayCount { get; set; }
+
+        /// <summary>
+        /// The amount of people who have favourited this map.
+        /// </summary>
+        [JsonProperty(@"favourite_count")]
+        public int FavouriteCount { get; set; }
+    }
+}
diff --git a/osu.Game/Database/OnlineWorkingBeatmap.cs b/osu.Game/Database/OnlineWorkingBeatmap.cs
new file mode 100644
index 0000000000..1465c59434
--- /dev/null
+++ b/osu.Game/Database/OnlineWorkingBeatmap.cs
@@ -0,0 +1,37 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Database
+{
+    internal class OnlineWorkingBeatmap : WorkingBeatmap
+    {
+        private readonly TextureStore textures;
+        private readonly TrackManager tracks;
+
+        public OnlineWorkingBeatmap(BeatmapInfo beatmapInfo, TextureStore textures, TrackManager tracks) : base(beatmapInfo)
+        {
+            this.textures = textures;
+            this.tracks = tracks;
+        }
+
+        protected override Beatmap GetBeatmap()
+        {
+            return new Beatmap();
+        }
+
+        protected override Texture GetBackground()
+        {
+            return textures.Get(BeatmapInfo.OnlineInfo.Covers.FirstOrDefault());
+        }
+
+        protected override Track GetTrack()
+        {
+            return tracks.Get(BeatmapInfo.OnlineInfo.Preview);
+        }
+    }
+}
diff --git a/osu.Game/Database/RankStatus.cs b/osu.Game/Database/RankStatus.cs
new file mode 100644
index 0000000000..f2a7d67a40
--- /dev/null
+++ b/osu.Game/Database/RankStatus.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.ComponentModel;
+
+namespace osu.Game.Database
+{
+    public enum RankStatus
+    {
+        Any = 7,
+        [Description("Ranked & Approved")]
+        RankedApproved = 0,
+        Approved = 1,
+        Loved = 8,
+        Favourites = 2,
+        [Description("Mod Requests")]
+        ModRequests = 3,
+        Pending = 4,
+        Graveyard = 5,
+        [Description("My Maps")]
+        MyMaps = 6,
+    }
+}
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index 830d0adc97..9a19819af8 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -1,20 +1,29 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
 using osu.Framework.MathUtils;
 using OpenTK;
 using OpenTK.Graphics;
 using System;
+using osu.Framework.Graphics.OpenGL;
+using osu.Framework.Graphics.Shaders;
+using osu.Framework.Graphics.Textures;
+using OpenTK.Graphics.ES30;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Allocation;
+using System.Collections.Generic;
+using osu.Framework.Graphics.Batches;
+using osu.Framework.Lists;
 
 namespace osu.Game.Graphics.Backgrounds
 {
-    public class Triangles : Container<Triangle>
+    public class Triangles : Drawable
     {
+        private const float triangle_size = 100;
+
         public override bool HandleInput => false;
 
         public Color4 ColourLight = Color4.White;
@@ -44,6 +53,33 @@ namespace osu.Game.Graphics.Backgrounds
         /// </summary>
         public bool HideAlphaDiscrepancies = true;
 
+        /// <summary>
+        /// The relative velocity of the triangles. Default is 1.
+        /// </summary>
+        public float Velocity = 1;
+
+        private readonly SortedList<TriangleParticle> parts = new SortedList<TriangleParticle>(Comparer<TriangleParticle>.Default);
+
+        private Shader shader;
+        private readonly Texture texture;
+
+        public Triangles()
+        {
+            texture = Texture.WhitePixel;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(ShaderManager shaders)
+        {
+            shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED);
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            addTriangles(true);
+        }
+
         public float TriangleScale
         {
             get { return triangleScale; }
@@ -52,42 +88,71 @@ namespace osu.Game.Graphics.Backgrounds
                 float change = value / triangleScale;
                 triangleScale = value;
 
-                if (change != 1)
-                    Children.ForEach(t => t.Scale *= change);
+                for (int i = 0; i < parts.Count; i++)
+                {
+                    TriangleParticle newParticle = parts[i];
+                    newParticle.Scale *= change;
+                    parts[i] = newParticle;
+                }
             }
         }
 
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-
-            addTriangles(true);
-        }
-
-        private int aimTriangleCount => (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
-
         protected override void Update()
         {
             base.Update();
 
-            float adjustedAlpha = HideAlphaDiscrepancies ?
-                // Cubically scale alpha to make it drop off more sharply.
-                (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
-                1;
+            Invalidate(Invalidation.DrawNode, shallPropagate: false);
 
-            foreach (var t in Children)
+            for (int i = 0; i < parts.Count; i++)
             {
-                t.Alpha = adjustedAlpha;
-                t.Position -= new Vector2(0, (float)(t.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 950)) / triangleScale);
-                if (ExpireOffScreenTriangles && t.DrawPosition.Y + t.DrawSize.Y * t.Scale.Y < 0)
-                    t.Expire();
+                TriangleParticle newParticle = parts[i];
+
+                float adjustedAlpha = HideAlphaDiscrepancies ?
+                    // Cubically scale alpha to make it drop off more sharply.
+                    (float)Math.Pow(DrawInfo.Colour.AverageColour.Linear.A, 3) :
+                    1;
+
+
+                newParticle.Position += new Vector2(0, -(parts[i].Scale * (50 / DrawHeight)) / triangleScale * Velocity) * ((float)Time.Elapsed / 950);
+                newParticle.Colour.A = adjustedAlpha;
+
+                parts[i] = newParticle;
+
+                if (!CreateNewTriangles)
+                    continue;
+
+                float bottomPos = parts[i].Position.Y + triangle_size * parts[i].Scale * 0.866f / DrawHeight;
+
+                if (bottomPos < 0)
+                    parts.RemoveAt(i);
             }
 
-            if (CreateNewTriangles)
-                addTriangles(false);
+            addTriangles(false);
         }
 
-        protected virtual Triangle CreateTriangle()
+        private void addTriangles(bool randomY)
+        {
+            int aimTriangleCount = (int)(DrawWidth * DrawHeight * 0.002f / (triangleScale * triangleScale) * SpawnRatio);
+
+            for (int i = 0; i < aimTriangleCount - parts.Count; i++)
+                parts.Add(createTriangle(randomY));
+        }
+
+        private TriangleParticle createTriangle(bool randomY)
+        {
+            TriangleParticle particle = CreateTriangle();
+
+            particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1);
+            particle.Colour = CreateTriangleShade();
+
+            return particle;
+        }
+
+        /// <summary>
+        /// Creates a triangle particle with a random scale.
+        /// </summary>
+        /// <returns>The triangle particle.</returns>
+        protected virtual TriangleParticle CreateTriangle()
         {
             const float std_dev = 0.16f;
             const float mean = 0.5f;
@@ -97,32 +162,100 @@ namespace osu.Game.Graphics.Backgrounds
             float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1)
             var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2)
 
-            const float size = 100;
-
-            return new EquilateralTriangle
-            {
-                Origin = Anchor.TopCentre,
-                RelativePositionAxes = Axes.Both,
-                Size = new Vector2(size),
-                Scale = new Vector2(scale),
-                EdgeSmoothness = new Vector2(1),
-                Colour = GetTriangleShade(),
-                Depth = scale,
-            };
+            return new TriangleParticle { Scale = scale };
         }
 
-        protected virtual Color4 GetTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
+        /// <summary>
+        /// Creates a shade of colour for the triangles.
+        /// </summary>
+        /// <returns>The colour.</returns>
+        protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1);
 
-        private void addTriangles(bool randomY)
+        protected override DrawNode CreateDrawNode() => new TrianglesDrawNode();
+
+        private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData();
+        protected override void ApplyDrawNode(DrawNode node)
         {
-            int addCount = aimTriangleCount - Children.Count();
-            for (int i = 0; i < addCount; i++)
+            base.ApplyDrawNode(node);
+
+            var trianglesNode = (TrianglesDrawNode)node;
+
+            trianglesNode.Shader = shader;
+            trianglesNode.Texture = texture;
+            trianglesNode.Size = DrawSize;
+            trianglesNode.Shared = sharedData;
+
+            trianglesNode.Parts.Clear();
+            trianglesNode.Parts.AddRange(parts);
+        }
+
+        private class TrianglesDrawNodeSharedData
+        {
+            public readonly LinearBatch<TexturedVertex2D> VertexBatch = new LinearBatch<TexturedVertex2D>(100 * 3, 10, PrimitiveType.Triangles);
+        }
+
+        private class TrianglesDrawNode : DrawNode
+        {
+            public Shader Shader;
+            public Texture Texture;
+
+            public TrianglesDrawNodeSharedData Shared;
+
+            public readonly List<TriangleParticle> Parts = new List<TriangleParticle>();
+            public Vector2 Size;
+
+            public override void Draw(Action<TexturedVertex2D> vertexAction)
             {
-                var sprite = CreateTriangle();
-                float triangleHeight = sprite.DrawHeight / DrawHeight;
-                sprite.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() * (1 + triangleHeight) - triangleHeight : 1);
-                Add(sprite);
+                base.Draw(vertexAction);
+
+                Shader.Bind();
+                Texture.TextureGL.Bind();
+
+                foreach (TriangleParticle particle in Parts)
+                {
+                    var offset = new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f);
+
+                    var triangle = new Triangle(
+                        particle.Position * Size * DrawInfo.Matrix,
+                        (particle.Position * Size + offset * triangle_size) * DrawInfo.Matrix,
+                        (particle.Position * Size + new Vector2(-offset.X, offset.Y) * triangle_size) * DrawInfo.Matrix
+                    );
+
+                    ColourInfo colourInfo = DrawInfo.Colour;
+                    colourInfo.ApplyChild(particle.Colour);
+
+                    Texture.DrawTriangle(triangle, colourInfo, null, Shared.VertexBatch.Add);
+                }
+
+                Shader.Unbind();
             }
         }
+
+        protected struct TriangleParticle : IComparable<TriangleParticle>
+        {
+            /// <summary>
+            /// The position of the top vertex of the triangle.
+            /// </summary>
+            public Vector2 Position;
+
+            /// <summary>
+            /// The colour of the triangle.
+            /// </summary>
+            public Color4 Colour;
+
+            /// <summary>
+            /// The scale of the triangle.
+            /// </summary>
+            public float Scale;
+
+            /// <summary>
+            /// Compares two <see cref="TriangleParticle"/>s. This is a reverse comparer because when the
+            /// triangles are added to the particles list, they should be drawn from largest to smallest
+            /// such that the smaller triangles appear on top.
+            /// </summary>
+            /// <param name="other"></param>
+            /// <returns></returns>
+            public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale);
+        }
     }
 }
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 5895eabde4..c0defceac0 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -2,59 +2,65 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
 
 namespace osu.Game.Graphics.Containers
 {
     public class BeatSyncedContainer : Container
     {
-        private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
+        protected readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
 
         private int lastBeat;
         private TimingControlPoint lastTimingPoint;
 
+        /// <summary>
+        /// The amount of time before a beat we should fire <see cref="OnNewBeat(int, TimingControlPoint, EffectControlPoint, TrackAmplitudes)"/>.
+        /// This allows for adding easing to animations that may be synchronised to the beat.
+        /// </summary>
+        protected double EarlyActivationMilliseconds;
+
         protected override void Update()
         {
-            if (beatmap.Value?.Track == null)
+            if (Beatmap.Value?.Track == null)
                 return;
 
-            double currentTrackTime = beatmap.Value.Track.CurrentTime;
+            double currentTrackTime = Beatmap.Value.Track.CurrentTime + EarlyActivationMilliseconds;
 
-            TimingControlPoint timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
-            EffectControlPoint effectPoint = beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
+            TimingControlPoint timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
+            EffectControlPoint effectPoint = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
 
             if (timingPoint.BeatLength == 0)
                 return;
 
-            int beat = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
+            int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
 
             // The beats before the start of the first control point are off by 1, this should do the trick
             if (currentTrackTime < timingPoint.Time)
-                beat--;
+                beatIndex--;
 
-            if (timingPoint == lastTimingPoint && beat == lastBeat)
+            if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
                 return;
 
             double offsetFromBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
 
             using (BeginDelayedSequence(offsetFromBeat, true))
-                OnNewBeat(beat, timingPoint.BeatLength, timingPoint.TimeSignature, effectPoint.KiaiMode);
+                OnNewBeat(beatIndex, timingPoint, effectPoint, Beatmap.Value.Track.CurrentAmplitudes);
 
-            lastBeat = beat;
+            lastBeat = beatIndex;
             lastTimingPoint = timingPoint;
         }
 
         [BackgroundDependencyLoader]
         private void load(OsuGameBase game)
         {
-            beatmap.BindTo(game.Beatmap);
+            Beatmap.BindTo(game.Beatmap);
         }
 
-        protected virtual void OnNewBeat(int newBeat, double beatLength, TimeSignatures timeSignature, bool kiai)
+        protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
         {
         }
     }
diff --git a/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs b/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs
new file mode 100644
index 0000000000..2b52b06abc
--- /dev/null
+++ b/osu.Game/Graphics/Containers/ReverseDepthFillFlowContainer.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Graphics.Containers
+{
+    public class ReverseDepthFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable
+    {
+        protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
+        protected override IEnumerable<T> FlowingChildren => base.FlowingChildren.Reverse();
+    }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
index 4f286ba7b5..14483f3bfb 100644
--- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs
+++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface
 
         protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour };
 
-        private class OsuDropdownMenuItem : DropdownMenuItem<T>
+        public class OsuDropdownMenuItem : DropdownMenuItem<T>
         {
             public OsuDropdownMenuItem(string text, T current) : base(text, current)
             {
@@ -60,7 +60,7 @@ namespace osu.Game.Graphics.UserInterface
                     AutoSizeAxes = Axes.Y,
                     Children = new Drawable[]
                     {
-                        chevron = new TextAwesome
+                        Chevron = new TextAwesome
                         {
                             AlwaysPresent = true,
                             Icon = FontAwesome.fa_chevron_right,
@@ -84,12 +84,12 @@ namespace osu.Game.Graphics.UserInterface
 
             private Color4? accentColour;
 
-            private readonly TextAwesome chevron;
+            protected readonly TextAwesome Chevron;
 
             protected override void FormatForeground(bool hover = false)
             {
                 base.FormatForeground(hover);
-                chevron.Alpha = hover ? 1 : 0;
+                Chevron.Alpha = hover ? 1 : 0;
             }
 
             public Color4 AccentColour
@@ -113,13 +113,13 @@ namespace osu.Game.Graphics.UserInterface
             }
         }
 
-        protected class OsuDropdownHeader : DropdownHeader
+        public class OsuDropdownHeader : DropdownHeader
         {
-            private readonly SpriteText label;
+            protected readonly SpriteText Text;
             protected override string Label
             {
-                get { return label.Text; }
-                set { label.Text = value; }
+                get { return Text.Text; }
+                set { Text.Text = value; }
             }
 
             protected readonly TextAwesome Icon;
@@ -146,7 +146,7 @@ namespace osu.Game.Graphics.UserInterface
 
                 Foreground.Children = new Drawable[]
                 {
-                    label = new OsuSpriteText
+                    Text = new OsuSpriteText
                     {
                         Anchor = Anchor.CentreLeft,
                         Origin = Anchor.CentreLeft,
diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
new file mode 100644
index 0000000000..5de6507bb3
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.ComponentModel;
+using System.Reflection;
+using System.Collections.Generic;
+
+namespace osu.Game.Graphics.UserInterface
+{
+    public class OsuEnumDropdown<T> : OsuDropdown<T>
+    {
+        public OsuEnumDropdown()
+        {
+            if (!typeof(T).IsEnum)
+                throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
+
+            List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
+            foreach(var val in (T[])Enum.GetValues(typeof(T)))
+            {
+                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
+                items.Add(
+                    new KeyValuePair<string, T>(
+                        field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
+                        val
+                    )
+                );
+            }
+            Items = items;
+        }
+    }
+}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index a9fad0c739..4bbae4efd1 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -57,9 +57,9 @@ namespace osu.Game.Graphics.UserInterface
             }
         }
 
-        private class OsuTabItem : TabItem<T>
+        public class OsuTabItem : TabItem<T>
         {
-            private readonly SpriteText text;
+            protected readonly SpriteText Text;
             private readonly Box box;
 
             private Color4? accentColour;
@@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
                 {
                     accentColour = value;
                     if (!Active)
-                        text.Colour = value;
+                        Text.Colour = value;
                 }
             }
 
@@ -94,13 +94,13 @@ namespace osu.Game.Graphics.UserInterface
             private void fadeActive()
             {
                 box.FadeIn(transition_length, EasingTypes.OutQuint);
-                text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
+                Text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint);
             }
 
             private void fadeInactive()
             {
                 box.FadeOut(transition_length, EasingTypes.OutQuint);
-                text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
+                Text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint);
             }
 
             protected override bool OnHover(InputState state)
@@ -130,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface
 
                 Children = new Drawable[]
                 {
-                    text = new OsuSpriteText
+                    Text = new OsuSpriteText
                     {
                         Margin = new MarginPadding { Top = 5, Bottom = 5 },
                         Origin = Anchor.BottomLeft,
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
new file mode 100644
index 0000000000..c82025f902
--- /dev/null
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Configuration;
+using osu.Game.Database;
+using osu.Game.Users;
+
+namespace osu.Game.Online.Multiplayer
+{
+    public class Room
+    {
+        public Bindable<string> Name = new Bindable<string>();
+        public Bindable<User> Host = new Bindable<User>();
+        public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
+        public Bindable<BeatmapMetadata> Beatmap = new Bindable<BeatmapMetadata>();
+    }
+}
diff --git a/osu.Game/Online/Multiplayer/RoomStatus.cs b/osu.Game/Online/Multiplayer/RoomStatus.cs
new file mode 100644
index 0000000000..4f943596a7
--- /dev/null
+++ b/osu.Game/Online/Multiplayer/RoomStatus.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Online.Multiplayer
+{
+    public abstract class RoomStatus
+    {
+        public abstract string Message { get; }
+        public abstract Color4 GetAppropriateColour(OsuColour colours);
+    }
+
+    public class RoomStatusOpen : RoomStatus
+    {
+        public override string Message => @"Welcoming Players";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
+    }
+
+    public class RoomStatusPlaying : RoomStatus
+    {
+        public override string Message => @"Now Playing";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
+    }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index f13043a745..886ff4f8d1 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -41,6 +41,8 @@ namespace osu.Game
 
         private DialogOverlay dialogOverlay;
 
+        private DirectOverlay direct;
+
         private Intro intro
         {
             get
@@ -70,6 +72,8 @@ namespace osu.Game
 
         public void ToggleSettings() => settings.ToggleVisibility();
 
+        public void ToggleDirect() => direct.ToggleVisibility();
+
         [BackgroundDependencyLoader]
         private void load()
         {
@@ -160,6 +164,7 @@ namespace osu.Game
             });
 
             //overlay elements
+            LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
             LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
             LoadComponentAsync(settings = new SettingsOverlay { Depth = -1 }, overlayContent.Add);
             LoadComponentAsync(musicController = new MusicController
@@ -249,6 +254,9 @@ namespace osu.Game
                     case Key.O:
                         settings.ToggleVisibility();
                         return true;
+                    case Key.D:
+                        direct.ToggleVisibility();
+                        return true;
                 }
             }
 
@@ -280,6 +288,7 @@ namespace osu.Game
                 Toolbar.State = Visibility.Hidden;
                 musicController.State = Visibility.Hidden;
                 chat.State = Visibility.Hidden;
+                direct.State = Visibility.Hidden;
             }
             else
             {
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
new file mode 100644
index 0000000000..4be68157d7
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -0,0 +1,193 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class DirectGridPanel : DirectPanel
+    {
+        private const float horizontal_padding = 10;
+        private const float vertical_padding = 5;
+
+        private FillFlowContainer bottomPanel;
+
+        public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
+        {
+            Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
+            CornerRadius = 4;
+            Masking = true;
+
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Offset = new Vector2(0f, 1f),
+                Radius = 3f,
+                Colour = Color4.Black.Opacity(0.25f),
+            };
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            FadeInFromZero(200, EasingTypes.Out);
+            bottomPanel.LayoutDuration = 200;
+            bottomPanel.LayoutEasing = EasingTypes.Out;
+            bottomPanel.Origin = Anchor.BottomLeft;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation, TextureStore textures)
+        {
+            Children = new[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                },
+                GetBackground(textures),
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black.Opacity(0.5f),
+                },
+                bottomPanel = new FillFlowContainer
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.TopLeft,
+                    Direction = FillDirection.Vertical,
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Spacing = new Vector2(0f, vertical_padding),
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
+                            Direction = FillDirection.Vertical,
+                            Children = new[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                new OsuSpriteText
+                                {
+                                    Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                            },
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Children = new Drawable[]
+                            {
+                                new Box
+                                {
+                                    RelativeSizeAxes = Axes.Both,
+                                },
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Padding = new MarginPadding
+                                    {
+                                        Top = vertical_padding,
+                                        Bottom = vertical_padding,
+                                        Left = horizontal_padding,
+                                        Right = horizontal_padding,
+                                    },
+                                    Children = new Drawable[]
+                                    {
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Children = new[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "mapped by ",
+                                                    TextSize = 14,
+                                                    Shadow = false,
+                                                    Colour = colours.Gray5,
+                                                },
+                                                new OsuSpriteText
+                                                {
+                                                    Text = SetInfo.Metadata.Author,
+                                                    TextSize = 14,
+                                                    Font = @"Exo2.0-SemiBoldItalic",
+                                                    Shadow = false,
+                                                    Colour = colours.BlueDark,
+                                                },
+                                            },
+                                        },
+                                        new Container
+                                        {
+                                            AutoSizeAxes = Axes.X,
+                                            Height = 14,
+                                            Children = new[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = $"from {SetInfo.Metadata.Source}",
+                                                    TextSize = 14,
+                                                    Shadow = false,
+                                                    Colour = colours.Gray5,
+                                                    Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
+                                                },
+                                            },
+                                        },
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.X,
+                                            Height = 20,
+                                            Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
+                                            Children = GetDifficultyIcons(),
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                new FillFlowContainer
+                {
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    AutoSizeAxes = Axes.Both,
+                    Direction = FillDirection.Vertical,
+                    Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
+                    Children = new[]
+                    {
+                        new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
+                        {
+                            Margin = new MarginPadding { Right = 1 },
+                        },
+                        new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
+                    },
+                },
+            };
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
new file mode 100644
index 0000000000..48636a5228
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -0,0 +1,197 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Colour;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Database;
+using osu.Framework.Allocation;
+using osu.Framework.Localisation;
+using osu.Framework.Graphics.Textures;
+using System.Linq;
+using osu.Framework.Input;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class DirectListPanel : DirectPanel
+    {
+        private const float horizontal_padding = 10;
+        private const float vertical_padding = 5;
+        private const float height = 70;
+
+        public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
+        {
+            RelativeSizeAxes = Axes.X;
+            Height = height;
+            CornerRadius = 5;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Offset = new Vector2(0f, 1f),
+                Radius = 3f,
+                Colour = Color4.Black.Opacity(0.25f),
+            };
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            FadeInFromZero(200, EasingTypes.Out);
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(LocalisationEngine localisation, TextureStore textures)
+        {
+            Children = new[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black,
+                },
+                GetBackground(textures),
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    ColourInfo = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Vertical,
+                            Children = new Drawable[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                new OsuSpriteText
+                                {
+                                    Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
+                                    Font = @"Exo2.0-BoldItalic",
+                                },
+                                new FillFlowContainer
+                                {
+                                    AutoSizeAxes = Axes.X,
+                                    Height = 20,
+                                    Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
+                                    Children = GetDifficultyIcons(),
+                                },
+                            },
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.TopRight,
+                            Origin = Anchor.TopRight,
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Vertical,
+                            Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
+                            Children = new Drawable[]
+                            {
+                                new Statistic(FontAwesome.fa_play_circle, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.PlayCount ?? 0)
+                                {
+                                    Margin = new MarginPadding { Right = 1 },
+                                },
+                                new Statistic(FontAwesome.fa_heart, SetInfo.Beatmaps.FirstOrDefault()?.OnlineInfo.FavouriteCount ?? 0),
+                                new FillFlowContainer
+                                {
+                                    Anchor = Anchor.TopRight,
+                                    Origin = Anchor.TopRight,
+                                    AutoSizeAxes = Axes.Both,
+                                    Direction = FillDirection.Horizontal,
+                                    Children = new[]
+                                    {
+                                        new OsuSpriteText
+                                        {
+                                            Text = "mapped by ",
+                                            TextSize = 14,
+                                        },
+                                        new OsuSpriteText
+                                        {
+                                            Text = SetInfo.Metadata.Author,
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-SemiBoldItalic",
+                                        },
+                                    },
+                                },
+                                new OsuSpriteText
+                                {
+                                    Text = $"from {SetInfo.Metadata.Source}",
+                                    Anchor = Anchor.TopRight,
+                                    Origin = Anchor.TopRight,
+                                    TextSize = 14,
+                                    Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
+                                },
+                            },
+                        },
+                        new DownloadButton
+                        {
+                            Anchor = Anchor.TopRight,
+                            Origin = Anchor.TopRight,
+                            Size = new Vector2(height - vertical_padding * 2),
+                        },
+                    },
+                },
+            };
+        }
+
+        private class DownloadButton : ClickableContainer
+        {
+            private readonly TextAwesome icon;
+
+            public DownloadButton()
+            {
+                Children = new Drawable[]
+                {
+                    icon = new TextAwesome
+                    {
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        UseFullGlyphHeight = false,
+                        TextSize = 30,
+                        Icon = FontAwesome.fa_osu_chevron_down_o,
+                    },
+                };
+            }
+
+            protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+            {
+                icon.ScaleTo(0.9f, 1000, EasingTypes.Out);
+                return base.OnMouseDown(state, args);
+            }
+
+            protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
+            {
+                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
+                return base.OnMouseUp(state, args);
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                icon.ScaleTo(1.1f, 500, EasingTypes.OutElastic);
+                return base.OnHover(state);
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
+                icon.ScaleTo(1f, 500, EasingTypes.OutElastic);
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
new file mode 100644
index 0000000000..8a56cf392e
--- /dev/null
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -0,0 +1,88 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Overlays.Direct
+{
+    public abstract class DirectPanel : Container
+    {
+        protected readonly BeatmapSetInfo SetInfo;
+
+        protected DirectPanel(BeatmapSetInfo setInfo)
+        {
+            SetInfo = setInfo;
+        }
+
+        protected IEnumerable<DifficultyIcon> GetDifficultyIcons()
+        {
+            var icons = new List<DifficultyIcon>();
+
+            foreach (var b in SetInfo.Beatmaps)
+                icons.Add(new DifficultyIcon(b));
+
+            return icons;
+        }
+
+        protected Drawable GetBackground(TextureStore textures)
+        {
+            return new AsyncLoadWrapper(new BeatmapBackgroundSprite(new OnlineWorkingBeatmap(SetInfo.Beatmaps.FirstOrDefault(), textures, null))
+            {
+                FillMode = FillMode.Fill,
+                OnLoadComplete = d => d.FadeInFromZero(400, EasingTypes.Out),
+            }) { RelativeSizeAxes = Axes.Both };
+        }
+
+        public class Statistic : FillFlowContainer
+        {
+            private readonly SpriteText text;
+
+            private int value;
+            public int Value
+            {
+                get { return value; }
+                set
+                {
+                    this.value = value;
+                    text.Text = Value.ToString(@"N0");
+                }
+            }
+
+            public Statistic(FontAwesome icon, int value = 0)
+            {
+                Anchor = Anchor.TopRight;
+                Origin = Anchor.TopRight;
+                AutoSizeAxes = Axes.Both;
+                Direction = FillDirection.Horizontal;
+                Spacing = new Vector2(5f, 0f);
+
+                Children = new Drawable[]
+                {
+                    text = new OsuSpriteText
+                    {
+                        Font = @"Exo2.0-SemiBoldItalic",
+                    },
+                    new TextAwesome
+                    {
+                        Icon = icon,
+                        Shadow = true,
+                        TextSize = 14,
+                        Margin = new MarginPadding { Top = 1 },
+                    },
+                };
+
+                Value = value;
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
new file mode 100644
index 0000000000..735e14b8c1
--- /dev/null
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -0,0 +1,233 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class FilterControl : Container
+    {
+        public static readonly float HEIGHT = 35 + 32 + 30 + padding * 2; // search + mode toggle buttons + sort tabs + padding
+
+        private const float padding = 10;
+
+        private readonly Box tabStrip;
+        private readonly FillFlowContainer<RulesetToggleButton> modeButtons;
+
+        public readonly SearchTextBox Search;
+        public readonly SortTabControl SortTabs;
+        public readonly OsuEnumDropdown<RankStatus> RankStatusDropdown;
+        public readonly Bindable<DirectOverlay.PanelDisplayStyle> DisplayStyle = new Bindable<DirectOverlay.PanelDisplayStyle>();
+
+        protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || RankStatusDropdown.Contains(screenSpacePos);
+
+        public FilterControl()
+        {
+            RelativeSizeAxes = Axes.X;
+            Height = HEIGHT;
+            DisplayStyle.Value = DirectOverlay.PanelDisplayStyle.Grid;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"384552"),
+                    Alpha = 0.9f,
+                },
+                tabStrip = new Box
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.TopLeft,
+                    RelativeSizeAxes = Axes.X,
+                    Height = 1,
+                },
+                new FillFlowContainer
+                {
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
+                    Children = new Drawable[]
+                    {
+                        Search = new DirectSearchTextBox
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            Margin = new MarginPadding { Top = padding },
+                        },
+                        modeButtons = new FillFlowContainer<RulesetToggleButton>
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(padding, 0f),
+                            Margin = new MarginPadding { Top = padding },
+                        },
+                        SortTabs = new SortTabControl
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+                new FillFlowContainer
+                {
+                    AutoSizeAxes = Axes.Both,
+                    Anchor = Anchor.TopRight,
+                    Origin = Anchor.TopRight,
+                    Spacing = new Vector2(10f, 0f),
+                    Direction = FillDirection.Horizontal,
+                    Margin = new MarginPadding { Top = HEIGHT - SlimEnumDropdown<DirectTab>.HEIGHT - padding, Right = DirectOverlay.WIDTH_PADDING },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(5f, 0f),
+                            Direction = FillDirection.Horizontal,
+                            Children = new[]
+                            {
+                                new DisplayStyleToggleButton(FontAwesome.fa_th_large, DirectOverlay.PanelDisplayStyle.Grid, DisplayStyle),
+                                new DisplayStyleToggleButton(FontAwesome.fa_list_ul, DirectOverlay.PanelDisplayStyle.List, DisplayStyle),
+                            },
+                        },
+                        RankStatusDropdown = new SlimEnumDropdown<RankStatus>
+                        {
+                            RelativeSizeAxes = Axes.None,
+                            Width = 160f,
+                        },
+                    },
+                },
+            };
+
+            RankStatusDropdown.Current.Value = RankStatus.RankedApproved;
+            SortTabs.Current.Value = SortCriteria.Title;
+            SortTabs.Current.TriggerChange();
+        }
+
+        [BackgroundDependencyLoader(true)]
+        private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
+        {
+            tabStrip.Colour = colours.Yellow;
+            RankStatusDropdown.AccentColour = colours.BlueDark;
+
+            var b = new Bindable<RulesetInfo>(); //backup bindable incase the game is null
+            foreach (var r in rulesets.AllRulesets)
+            {
+                modeButtons.Add(new RulesetToggleButton(game?.Ruleset ?? b, r));
+            }
+        }
+
+        private class DirectSearchTextBox : SearchTextBox
+        {
+            protected override Color4 BackgroundUnfocused => backgroundColour;
+            protected override Color4 BackgroundFocused => backgroundColour;
+
+            private Color4 backgroundColour;
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                backgroundColour = colours.Gray2.Opacity(0.9f);
+            }
+        }
+
+        private class RulesetToggleButton : ClickableContainer
+        {
+            private readonly TextAwesome icon;
+
+            private RulesetInfo ruleset;
+            public RulesetInfo Ruleset
+            {
+                get { return ruleset; }
+                set
+                {
+                    ruleset = value;
+                    icon.Icon = Ruleset.CreateInstance().Icon;
+                }
+            }
+
+            private readonly Bindable<RulesetInfo> bindable;
+
+            private void Bindable_ValueChanged(RulesetInfo obj)
+            {
+                icon.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100);
+            }
+
+            public RulesetToggleButton(Bindable<RulesetInfo> bindable, RulesetInfo ruleset)
+            {
+                this.bindable = bindable;
+                AutoSizeAxes = Axes.Both;
+
+                Children = new[]
+                {
+                    icon = new TextAwesome
+                    {
+                        Origin = Anchor.TopLeft,
+                        Anchor = Anchor.TopLeft,
+                        TextSize = 32,
+                    }
+                };
+
+                Ruleset = ruleset;
+                bindable.ValueChanged += Bindable_ValueChanged;
+                Bindable_ValueChanged(bindable.Value);
+                Action = () => bindable.Value = Ruleset;
+            }
+
+            protected override void Dispose(bool isDisposing)
+            {
+                if (bindable != null)
+                    bindable.ValueChanged -= Bindable_ValueChanged;
+                base.Dispose(isDisposing);
+            }
+        }
+
+        private class DisplayStyleToggleButton : ClickableContainer
+        {
+            private readonly TextAwesome icon;
+            private readonly DirectOverlay.PanelDisplayStyle style;
+            private readonly Bindable<DirectOverlay.PanelDisplayStyle> bindable;
+
+            public DisplayStyleToggleButton(FontAwesome icon, DirectOverlay.PanelDisplayStyle style, Bindable<DirectOverlay.PanelDisplayStyle> bindable)
+            {
+                this.bindable = bindable;
+                this.style = style;
+                Size = new Vector2(SlimEnumDropdown<DirectTab>.HEIGHT);
+
+                Children = new Drawable[]
+                {
+                    this.icon = new TextAwesome
+                    {
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        Icon = icon,
+                        TextSize = 18,
+                        UseFullGlyphHeight = false,
+                        Alpha = 0.5f,
+                    },
+                };
+
+                bindable.ValueChanged += Bindable_ValueChanged;
+                Bindable_ValueChanged(bindable.Value);
+                Action = () => bindable.Value = this.style;
+            }
+
+            private void Bindable_ValueChanged(DirectOverlay.PanelDisplayStyle style)
+            {
+                icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100);
+            }
+
+            protected override void Dispose(bool isDisposing)
+            {
+                bindable.ValueChanged -= Bindable_ValueChanged;
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs
new file mode 100644
index 0000000000..8e4ede48d5
--- /dev/null
+++ b/osu.Game/Overlays/Direct/Header.cs
@@ -0,0 +1,124 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.ComponentModel;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class Header : Container
+    {
+        public static readonly float HEIGHT = 90;
+
+        private readonly Box tabStrip;
+
+        public readonly OsuTabControl<DirectTab> Tabs;
+
+        public Header()
+        {
+            Height = HEIGHT;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"252f3a"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Left = DirectOverlay.WIDTH_PADDING, Right = DirectOverlay.WIDTH_PADDING },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.CentreLeft,
+                            Origin = Anchor.BottomLeft,
+                            Position = new Vector2(-35f, 5f),
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Horizontal,
+                            Spacing = new Vector2(10f, 0f),
+                            Children = new Drawable[]
+                            {
+                                new TextAwesome
+                                {
+                                    TextSize = 25,
+                                    Icon = FontAwesome.fa_osu_chevron_down_o,
+                                },
+                                new OsuSpriteText
+                                {
+                                    TextSize = 25,
+                                    Text = @"osu!direct",
+                                },
+                            },
+                        },
+                        tabStrip = new Box
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            Width = 282, //todo: make this actually match the tab control's width instead of hardcoding
+                            Height = 1,
+                        },
+                        Tabs = new DirectTabControl
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+            };
+
+            Tabs.Current.Value = DirectTab.Search;
+            Tabs.Current.TriggerChange();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            tabStrip.Colour = colours.Green;
+        }
+
+        private class DirectTabControl : OsuTabControl<DirectTab>
+        {
+            protected override TabItem<DirectTab> CreateTabItem(DirectTab value) => new DirectTabItem(value);
+
+            public DirectTabControl()
+            {
+                Height = 25;
+                AccentColour = Color4.White;
+            }
+
+            private class DirectTabItem : OsuTabItem
+            {
+                public DirectTabItem(DirectTab value) : base(value)
+                {
+                    Text.TextSize = 15;
+                }
+            }
+        }
+    }
+
+    public enum DirectTab
+    {
+        Search,
+        [Description("Newest Maps")]
+        NewestMaps,
+        [Description("Top Rated")]
+        TopRated,
+        [Description("Most Played")]
+        MostPlayed
+    }
+}
diff --git a/osu.Game/Overlays/Direct/SlimEnumDropdown.cs b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
new file mode 100644
index 0000000000..1d12b8477b
--- /dev/null
+++ b/osu.Game/Overlays/Direct/SlimEnumDropdown.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
+    {
+        public const float HEIGHT = 25;
+
+        protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour };
+        protected override Menu CreateMenu() => new SlimMenu();
+
+        private class SlimDropdownHeader : OsuDropdownHeader
+        {
+            public SlimDropdownHeader()
+            {
+                Height = HEIGHT;
+                Icon.TextSize = 16;
+                Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
+            }
+
+            protected override void LoadComplete()
+            {
+                base.LoadComplete();
+                BackgroundColour = Color4.Black.Opacity(0.25f);
+            }
+        }
+
+        private class SlimMenu : OsuMenu
+        {
+            public SlimMenu()
+            {
+                Background.Colour = Color4.Black.Opacity(0.25f);
+            }
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Direct/SortTabControl.cs b/osu.Game/Overlays/Direct/SortTabControl.cs
new file mode 100644
index 0000000000..4d4e02d875
--- /dev/null
+++ b/osu.Game/Overlays/Direct/SortTabControl.cs
@@ -0,0 +1,117 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Direct
+{
+    public class SortTabControl : OsuTabControl<SortCriteria>
+    {
+        protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new SortTabItem(value);
+
+        public SortTabControl()
+        {
+            Height = 30;
+        }
+
+        private class SortTabItem : TabItem<SortCriteria>
+        {
+            private const float transition_duration = 100;
+
+            private readonly Box box;
+
+            public override bool Active
+            {
+                get { return base.Active; }
+                set
+                {
+                    if (Active == value) return;
+
+                    if (value)
+                        slideActive();
+                    else
+                        slideInactive();
+                    base.Active = value;
+                }
+            }
+
+            public SortTabItem(SortCriteria value) : base(value)
+            {
+                AutoSizeAxes = Axes.X;
+                RelativeSizeAxes = Axes.Y;
+
+                Children = new Drawable[]
+                {
+                    new OsuSpriteText
+                    {
+                        Margin = new MarginPadding { Top = 8, Bottom = 8 },
+                        Origin = Anchor.BottomLeft,
+                        Anchor = Anchor.BottomLeft,
+                        Text = (value as Enum).GetDescription() ?? value.ToString(),
+                        TextSize = 14,
+                        Font = @"Exo2.0-Bold",
+                    },
+                    box = new Box
+                    {
+                        RelativeSizeAxes = Axes.X,
+                        Height = 5,
+                        Scale = new Vector2(1f, 0f),
+                        Colour = Color4.White,
+                        Origin = Anchor.BottomLeft,
+                        Anchor = Anchor.BottomLeft,
+                    },
+                };
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                box.Colour = colours.Yellow;
+            }
+
+            protected override bool OnHover(InputState state)
+            {
+                if (!Active)
+                    slideActive();
+                return true;
+            }
+
+            protected override void OnHoverLost(InputState state)
+            {
+                if (!Active)
+                    slideInactive();
+            }
+
+            private void slideActive()
+            {
+                box.ScaleTo(new Vector2(1f), transition_duration);
+            }
+
+            private void slideInactive()
+            {
+                box.ScaleTo(new Vector2(1f, 0f), transition_duration);
+            }
+        }
+    }
+
+    public enum SortCriteria
+    {
+        Title,
+        Artist,
+        Creator,
+        Difficulty,
+        Ranked,
+        Rating,
+    }
+}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
new file mode 100644
index 0000000000..0930c825b6
--- /dev/null
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -0,0 +1,230 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays.Direct;
+
+using Container = osu.Framework.Graphics.Containers.Container;
+
+namespace osu.Game.Overlays
+{
+    public class DirectOverlay : WaveOverlayContainer
+    {
+        public static readonly int WIDTH_PADDING = 80;
+        private const float panel_padding = 10f;
+
+        private readonly FilterControl filter;
+        private readonly FillFlowContainer resultCountsContainer;
+        private readonly OsuSpriteText resultCountsText;
+        private readonly FillFlowContainer<DirectPanel> panels;
+
+        private IEnumerable<BeatmapSetInfo> beatmapSets;
+        public IEnumerable<BeatmapSetInfo> BeatmapSets
+        {
+            get { return beatmapSets; }
+            set
+            {
+                if (beatmapSets?.Equals(value) ?? false) return;
+                beatmapSets = value;
+
+                recreatePanels(filter.DisplayStyle.Value);
+            }
+        }
+
+        private ResultCounts resultAmounts;
+        public ResultCounts ResultAmounts
+        {
+            get { return resultAmounts; }
+            set
+            {
+                if (value == ResultAmounts) return;
+                resultAmounts = value;
+
+                updateResultCounts();
+            }
+        }
+
+        public DirectOverlay()
+        {
+            RelativeSizeAxes = Axes.Both;
+
+            // osu!direct colours are not part of the standard palette
+
+            FirstWaveColour = OsuColour.FromHex(@"19b0e2");
+            SecondWaveColour = OsuColour.FromHex(@"2280a2");
+            ThirdWaveColour = OsuColour.FromHex(@"005774");
+            FourthWaveColour = OsuColour.FromHex(@"003a4e");
+
+            Header header;
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"485e74"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Masking = true,
+                    Children = new[]
+                    {
+                        new Triangles
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            TriangleScale = 5,
+                            ColourLight = OsuColour.FromHex(@"465b71"),
+                            ColourDark = OsuColour.FromHex(@"3f5265"),
+                        },
+                    },
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = Header.HEIGHT + FilterControl.HEIGHT },
+                    Children = new[]
+                    {
+                        new ScrollContainer
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            ScrollDraggerVisible = false,
+                            Children = new Drawable[]
+                            {
+                                new FillFlowContainer
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Vertical,
+                                    Children = new Drawable[]
+                                    {
+                                        resultCountsContainer = new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.Both,
+                                            Direction = FillDirection.Horizontal,
+                                            Margin = new MarginPadding { Left = WIDTH_PADDING, Top = 6 },
+                                            Children = new Drawable[]
+                                            {
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "Found ",
+                                                    TextSize = 15,
+                                                },
+                                                resultCountsText = new OsuSpriteText
+                                                {
+                                                    TextSize = 15,
+                                                    Font = @"Exo2.0-Bold",
+                                                },
+                                            }
+                                        },
+                                        panels = new FillFlowContainer<DirectPanel>
+                                        {
+                                            RelativeSizeAxes = Axes.X,
+                                            AutoSizeAxes = Axes.Y,
+                                            Padding = new MarginPadding { Top = panel_padding, Bottom = panel_padding, Left = WIDTH_PADDING, Right = WIDTH_PADDING },
+                                            Spacing = new Vector2(panel_padding),
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                filter = new FilterControl
+                {
+                    RelativeSizeAxes = Axes.X,
+                    Margin = new MarginPadding { Top = Header.HEIGHT },
+                },
+                header = new Header
+                {
+                    RelativeSizeAxes = Axes.X,
+                },
+            };
+
+            header.Tabs.Current.ValueChanged += tab => { if (tab != DirectTab.Search) filter.Search.Current.Value = string.Empty; };
+
+            filter.Search.Exit = Hide;
+            filter.Search.Current.ValueChanged += text => { if (text != string.Empty) header.Tabs.Current.Value = DirectTab.Search; };
+            filter.DisplayStyle.ValueChanged += recreatePanels;
+
+            updateResultCounts();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            resultCountsContainer.Colour = colours.Yellow;
+        }
+
+        private void updateResultCounts()
+        {
+            resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, EasingTypes.Out);
+            if (ResultAmounts == null) return;
+
+            resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " +
+                                    pluralize("Song", ResultAmounts.Songs) + ", " +
+                                    pluralize("Tag", ResultAmounts.Tags);
+        }
+
+        private string pluralize(string prefix, int value)
+        {
+            return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s");
+        }
+
+        private void recreatePanels(PanelDisplayStyle displayStyle)
+        {
+            if (BeatmapSets == null) return;
+            panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b));
+        }
+
+        protected override bool OnFocus(InputState state)
+        {
+            filter.Search.TriggerFocus();
+            return false;
+        }
+
+        protected override void PopIn()
+        {
+            base.PopIn();
+
+            filter.Search.HoldFocus = true;
+        }
+
+        protected override void PopOut()
+        {
+            base.PopOut();
+
+            filter.Search.HoldFocus = false;
+        }
+
+        public class ResultCounts
+        {
+            public readonly int Artists;
+            public readonly int Songs;
+            public readonly int Tags;
+
+            public ResultCounts(int artists, int songs, int tags)
+            {
+                Artists = artists;
+                Songs = songs;
+                Tags = tags;
+            }
+        }
+
+        public enum PanelDisplayStyle
+        {
+            Grid,
+            List,
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
index 86a47d8a95..561f81d6c3 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs
@@ -12,15 +12,24 @@ using osu.Game.Graphics.UserInterface;
 using osu.Game.Online.API;
 using OpenTK;
 using osu.Framework.Input;
+using osu.Game.Users;
+using System.ComponentModel;
+using osu.Game.Graphics;
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+
+using Container = osu.Framework.Graphics.Containers.Container;
 
 namespace osu.Game.Overlays.Settings.Sections.General
 {
-    public class LoginSettings : SettingsSubsection, IOnlineComponent
+    public class LoginSettings : FillFlowContainer, IOnlineComponent
     {
         private bool bounding = true;
         private LoginForm form;
+        private OsuColour colours;
 
-        protected override string Header => "Account";
+        private UserPanel panel;
+        private UserDropdown dropdown;
 
         public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
 
@@ -34,9 +43,18 @@ namespace osu.Game.Overlays.Settings.Sections.General
             }
         }
 
-        [BackgroundDependencyLoader(permitNulls: true)]
-        private void load(APIAccess api)
+        public LoginSettings()
         {
+            RelativeSizeAxes = Axes.X;
+            AutoSizeAxes = Axes.Y;
+            Direction = FillDirection.Vertical;
+            Spacing = new Vector2(0f, 5f);
+        }
+
+        [BackgroundDependencyLoader(permitNulls: true)]
+        private void load(OsuColour colours, APIAccess api)
+        {
+            this.colours = colours;
             api?.Register(this);
         }
 
@@ -49,6 +67,12 @@ namespace osu.Game.Overlays.Settings.Sections.General
                 case APIState.Offline:
                     Children = new Drawable[]
                     {
+                        new OsuSpriteText
+                        {
+                            Text = "ACCOUNT",
+                            Margin = new MarginPadding { Bottom = 5 },
+                            Font = @"Exo2.0-Black",
+                        },
                         form = new LoginForm()
                     };
                     break;
@@ -66,24 +90,73 @@ namespace osu.Game.Overlays.Settings.Sections.General
                     {
                         new OsuSpriteText
                         {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
                             Text = "Connecting...",
+                            Margin = new MarginPadding { Top = 10, Bottom = 10 },
                         },
                     };
                     break;
                 case APIState.Online:
                     Children = new Drawable[]
                     {
-                        new OsuSpriteText
-                        {
-                            Text = $"Connected as {api.Username}!",
-                        },
-                        new OsuButton
+                        new FillFlowContainer
                         {
                             RelativeSizeAxes = Axes.X,
-                            Text = "Sign out",
-                            Action = api.Logout
+                            AutoSizeAxes = Axes.Y,
+                            Padding = new MarginPadding { Left = 20, Right = 20 },
+                            Direction = FillDirection.Vertical,
+                            Spacing = new Vector2(0f, 10f),
+                            Children = new Drawable[]
+                            {
+                                new Container
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Children = new[]
+                                    {
+                                        new OsuSpriteText
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            Text = "Signed in",
+                                            TextSize = 18,
+                                            Font = @"Exo2.0-Bold",
+                                            Margin = new MarginPadding { Top = 5, Bottom = 5 },
+                                        },
+                                    },
+                                },
+                                panel = new UserPanel(api.LocalUser.Value) { RelativeSizeAxes = Axes.X },
+                                dropdown = new UserDropdown { RelativeSizeAxes = Axes.X },
+                            },
+                        },
+                    };
+
+                    panel.Status.BindTo(api.LocalUser.Value.Status);
+
+                    dropdown.Current.ValueChanged += newValue =>
+                    {
+                        switch (newValue)
+                        {
+                            case UserAction.Online:
+                                api.LocalUser.Value.Status.Value = new UserStatusOnline();
+                                dropdown.StatusColour = colours.Green;
+                                break;
+                            case UserAction.DoNotDisturb:
+                                api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb();
+                                dropdown.StatusColour = colours.Red;
+                                break;
+                            case UserAction.AppearOffline:
+                                api.LocalUser.Value.Status.Value = new UserStatusOffline();
+                                dropdown.StatusColour = colours.Gray7;
+                                break;
+                            case UserAction.SignOut:
+                                api.Logout();
+                                break;
                         }
                     };
+                    dropdown.Current.TriggerChange();
+
                     break;
             }
 
@@ -170,5 +243,119 @@ namespace osu.Game.Overlays.Settings.Sections.General
                 return base.OnFocus(state);
             }
         }
+
+        private class UserDropdown : OsuEnumDropdown<UserAction>
+        {
+            protected override DropdownHeader CreateHeader() => new UserDropdownHeader { AccentColour = AccentColour };
+            protected override Menu CreateMenu() => new UserDropdownMenu();
+            protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour };
+
+            public Color4 StatusColour
+            {
+                set
+                {
+                    var h = Header as UserDropdownHeader;
+                    if (h == null) return;
+                    h.StatusColour = value;
+                }
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                AccentColour = colours.Gray5;
+            }
+
+            private class UserDropdownHeader : OsuDropdownHeader
+            {
+                public const float LABEL_LEFT_MARGIN = 20;
+
+                private readonly TextAwesome statusIcon;
+                public Color4 StatusColour
+                {
+                    set
+                    {
+                        statusIcon.FadeColour(value, 500, EasingTypes.OutQuint);
+                    }
+                }
+
+                public UserDropdownHeader()
+                {
+                    Foreground.Padding = new MarginPadding { Left = 10, Right = 10 };
+                    Margin = new MarginPadding { Bottom = 5 };
+                    Masking = true;
+                    CornerRadius = 5;
+                    EdgeEffect = new EdgeEffect
+                    {
+                        Type = EdgeEffectType.Shadow,
+                        Colour = Color4.Black.Opacity(0.25f),
+                        Radius = 4,
+                    };
+
+                    Icon.TextSize = 14;
+                    Icon.Margin = new MarginPadding(0);
+
+                    Foreground.Add(statusIcon = new TextAwesome
+                    {
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
+                        Icon = FontAwesome.fa_circle_o,
+                        TextSize = 14,
+                    });
+
+                    Text.Margin = new MarginPadding { Left = LABEL_LEFT_MARGIN };
+                }
+
+                [BackgroundDependencyLoader]
+                private void load(OsuColour colours)
+                {
+                    BackgroundColour = colours.Gray3;
+                }
+            }
+
+            private class UserDropdownMenu : OsuMenu
+            {
+                public UserDropdownMenu()
+                {
+                    Margin = new MarginPadding { Bottom = 5 };
+                    CornerRadius = 5;
+                    ItemsContainer.Padding = new MarginPadding(0);
+                    Masking = true;
+                    EdgeEffect = new EdgeEffect
+                    {
+                        Type = EdgeEffectType.Shadow,
+                        Colour = Color4.Black.Opacity(0.25f),
+                        Radius = 4,
+                    };
+                }
+
+                [BackgroundDependencyLoader]
+                private void load(OsuColour colours)
+                {
+                    Background.Colour = colours.Gray3;
+                }
+            }
+
+            private class UserDropdownMenuItem : OsuDropdownMenuItem
+            {
+                public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
+                {
+                    Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = UserDropdownHeader.LABEL_LEFT_MARGIN, Right = 5 };
+                    Chevron.Margin = new MarginPadding { Left = 2, Right = 3 };
+                    CornerRadius = 5;
+                }
+            }
+        }
+
+        private enum UserAction
+        {
+            Online,
+            [Description(@"Do not disturb")]
+            DoNotDisturb,
+            [Description(@"Appear offline")]
+            AppearOffline,
+            [Description(@"Sign out")]
+            SignOut,
+        }
     }
 }
diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
index a9f0848403..5725ee8439 100644
--- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
+++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs
@@ -1,32 +1,17 @@
 // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System;
-using System.Reflection;
-using System.ComponentModel;
-using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.UserInterface;
 
 namespace osu.Game.Overlays.Settings
 {
     public class SettingsEnumDropdown<T> : SettingsDropdown<T>
     {
-        public SettingsEnumDropdown()
+        protected override Drawable CreateControl() => new OsuEnumDropdown<T>
         {
-            if (!typeof(T).IsEnum)
-                throw new InvalidOperationException("SettingsDropdown only supports enums as the generic type argument");
-
-            List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
-            foreach(var val in (T[])Enum.GetValues(typeof(T)))
-            {
-                var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
-                items.Add(
-                    new KeyValuePair<string, T>(
-                        field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
-                        val
-                    )
-                );
-            }
-            Items = items;
-        }
+            Margin = new MarginPadding { Top = 5 },
+            RelativeSizeAxes = Axes.X,
+        };
     }
 }
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index b74ee52f9c..1a7d2d4e37 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -48,7 +48,8 @@ namespace osu.Game.Screens.Menu
                             OnSolo = delegate { Push(consumeSongSelect()); },
                             OnMulti = delegate { Push(new Lobby()); },
                             OnExit = delegate { Exit(); },
-                        }
+                        },
+                        new MenuSideFlashes(),
                     }
                 }
             };
@@ -60,6 +61,7 @@ namespace osu.Game.Screens.Menu
             LoadComponentAsync(background);
 
             buttons.OnSettings = game.ToggleSettings;
+            buttons.OnDirect = game.ToggleDirect;
 
             preloadSongSelect();
         }
diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs
new file mode 100644
index 0000000000..77239726e8
--- /dev/null
+++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs
@@ -0,0 +1,98 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using System;
+
+namespace osu.Game.Screens.Menu
+{
+    public class MenuSideFlashes : BeatSyncedContainer
+    {
+        public override bool HandleInput => false;
+
+        private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
+
+        private readonly Box leftBox;
+        private readonly Box rightBox;
+
+        private const float amplitude_dead_zone = 0.25f;
+        private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f;
+        private const float kiai_multiplier = (1 - amplitude_dead_zone * 0.95f) / 0.8f;
+
+        private const int box_max_alpha = 200;
+        private const double box_fade_in_time = 65;
+        private const int box_width = 200;
+
+        public MenuSideFlashes()
+        {
+            EarlyActivationMilliseconds = box_fade_in_time;
+
+            RelativeSizeAxes = Axes.Both;
+            Anchor = Anchor.Centre;
+            Origin = Anchor.Centre;
+            Children = new Drawable[]
+            {
+                leftBox = new Box
+                {
+                    Anchor = Anchor.CentreLeft,
+                    Origin = Anchor.CentreLeft,
+                    RelativeSizeAxes = Axes.Y,
+                    Width = box_width,
+                    Alpha = 0,
+                    BlendingMode = BlendingMode.Additive,
+                },
+                rightBox = new Box
+                {
+                    Anchor = Anchor.CentreRight,
+                    Origin = Anchor.CentreRight,
+                    RelativeSizeAxes = Axes.Y,
+                    Width = box_width,
+                    Alpha = 0,
+                    BlendingMode = BlendingMode.Additive,
+                }
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuGameBase game, OsuColour colours)
+        {
+            beatmap.BindTo(game.Beatmap);
+
+            // linear colour looks better in this case, so let's use it for now.
+            Color4 gradientDark = colours.Blue.Opacity(0).ToLinear();
+            Color4 gradientLight = colours.Blue.Opacity(0.3f).ToLinear();
+
+            leftBox.ColourInfo = ColourInfo.GradientHorizontal(gradientLight, gradientDark);
+            rightBox.ColourInfo = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
+        }
+
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        {
+            if (beatIndex < 0)
+                return;
+
+            if (effectPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % (int)timingPoint.TimeSignature == 0)
+                flash(leftBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
+            if (effectPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % (int)timingPoint.TimeSignature == 0)
+                flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes);
+        }
+
+        private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes)
+        {
+            d.FadeTo(Math.Max(0, ((d.Equals(leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time);
+            using (d.BeginDelayedSequence(box_fade_in_time))
+                d.FadeOut(beatLength, EasingTypes.In);
+        }
+    }
+}
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index e28adeacff..44b7b6bceb 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -5,13 +5,17 @@ using System;
 using osu.Framework.Allocation;
 using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.Textures;
 using osu.Framework.Input;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
 using OpenTK;
 using OpenTK.Graphics;
 
@@ -20,19 +24,23 @@ namespace osu.Game.Screens.Menu
     /// <summary>
     /// osu! logo and its attachments (pulsing, visualiser etc.)
     /// </summary>
-    public class OsuLogo : Container
+    public class OsuLogo : BeatSyncedContainer
     {
         public readonly Color4 OsuPink = OsuColour.FromHex(@"e967a1");
 
         private readonly Sprite logo;
         private readonly CircularContainer logoContainer;
         private readonly Container logoBounceContainer;
+        private readonly Container logoBeatContainer;
+        private readonly Container logoAmplitudeContainer;
         private readonly Container logoHoverContainer;
 
         private SampleChannel sampleClick;
 
         private readonly Container colourAndTriangles;
 
+        private readonly Triangles triangles;
+
         public Action Action;
 
         public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@@ -67,8 +75,12 @@ namespace osu.Game.Screens.Menu
 
         private const float default_size = 480;
 
+        private const double beat_in_time = 60;
+
         public OsuLogo()
         {
+            EarlyActivationMilliseconds = beat_in_time;
+
             Size = new Vector2(default_size);
 
             Anchor = Anchor.Centre;
@@ -78,67 +90,16 @@ namespace osu.Game.Screens.Menu
 
             Children = new Drawable[]
             {
-                logoBounceContainer = new Container
+                logoHoverContainer = new Container
                 {
                     AutoSizeAxes = Axes.Both,
                     Children = new Drawable[]
                     {
-                        logoHoverContainer = new Container
+                        logoBounceContainer = new Container
                         {
                             AutoSizeAxes = Axes.Both,
                             Children = new Drawable[]
                             {
-                                new BufferedContainer
-                                {
-                                    AutoSizeAxes = Axes.Both,
-                                    Children = new Drawable[]
-                                    {
-                                        logoContainer = new CircularContainer
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                            RelativeSizeAxes = Axes.Both,
-                                            Scale = new Vector2(0.88f),
-                                            Masking = true,
-                                            Children = new Drawable[]
-                                            {
-                                                colourAndTriangles = new Container
-                                                {
-                                                    RelativeSizeAxes = Axes.Both,
-                                                    Anchor = Anchor.Centre,
-                                                    Origin = Anchor.Centre,
-                                                    Children = new Drawable[]
-                                                    {
-                                                        new Box
-                                                        {
-                                                            RelativeSizeAxes = Axes.Both,
-                                                            Colour = OsuPink,
-                                                        },
-                                                        new Triangles
-                                                        {
-                                                            TriangleScale = 4,
-                                                            ColourLight = OsuColour.FromHex(@"ff7db7"),
-                                                            ColourDark = OsuColour.FromHex(@"de5b95"),
-                                                            RelativeSizeAxes = Axes.Both,
-                                                        },
-                                                    }
-                                                },
-                                                flashLayer = new Box
-                                                {
-                                                    RelativeSizeAxes = Axes.Both,
-                                                    BlendingMode = BlendingMode.Additive,
-                                                    Colour = Color4.White,
-                                                    Alpha = 0,
-                                                },
-                                            },
-                                        },
-                                        logo = new Sprite
-                                        {
-                                            Anchor = Anchor.Centre,
-                                            Origin = Anchor.Centre,
-                                        },
-                                    }
-                                },
                                 rippleContainer = new Container
                                 {
                                     Anchor = Anchor.Centre,
@@ -151,36 +112,101 @@ namespace osu.Game.Screens.Menu
                                             Anchor = Anchor.Centre,
                                             Origin = Anchor.Centre,
                                             BlendingMode = BlendingMode.Additive,
-                                            Alpha = 0.15f
+                                            Alpha = 0
                                         }
                                     }
                                 },
-                                impactContainer = new CircularContainer
+                                logoAmplitudeContainer = new Container
                                 {
-                                    Anchor = Anchor.Centre,
-                                    Origin = Anchor.Centre,
-                                    Alpha = 0,
-                                    BorderColour = Color4.White,
-                                    RelativeSizeAxes = Axes.Both,
-                                    BorderThickness = 10,
-                                    Masking = true,
+                                    AutoSizeAxes = Axes.Both,
                                     Children = new Drawable[]
                                     {
-                                        new Box
+                                        logoBeatContainer = new Container
                                         {
-                                            RelativeSizeAxes = Axes.Both,
-                                            AlwaysPresent = true,
-                                            Alpha = 0,
+                                            AutoSizeAxes = Axes.Both,
+                                            Children = new Drawable[]
+                                            {
+                                                new BufferedContainer
+                                                {
+                                                    AutoSizeAxes = Axes.Both,
+                                                    Children = new Drawable[]
+                                                    {
+                                                        logoContainer = new CircularContainer
+                                                        {
+                                                            Anchor = Anchor.Centre,
+                                                            Origin = Anchor.Centre,
+                                                            RelativeSizeAxes = Axes.Both,
+                                                            Scale = new Vector2(0.88f),
+                                                            Masking = true,
+                                                            Children = new Drawable[]
+                                                            {
+                                                                colourAndTriangles = new Container
+                                                                {
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                    Anchor = Anchor.Centre,
+                                                                    Origin = Anchor.Centre,
+                                                                    Children = new Drawable[]
+                                                                    {
+                                                                        new Box
+                                                                        {
+                                                                            RelativeSizeAxes = Axes.Both,
+                                                                            Colour = OsuPink,
+                                                                        },
+                                                                        triangles = new Triangles
+                                                                        {
+                                                                            TriangleScale = 4,
+                                                                            ColourLight = OsuColour.FromHex(@"ff7db7"),
+                                                                            ColourDark = OsuColour.FromHex(@"de5b95"),
+                                                                            RelativeSizeAxes = Axes.Both,
+                                                                        },
+                                                                    }
+                                                                },
+                                                                flashLayer = new Box
+                                                                {
+                                                                    RelativeSizeAxes = Axes.Both,
+                                                                    BlendingMode = BlendingMode.Additive,
+                                                                    Colour = Color4.White,
+                                                                    Alpha = 0,
+                                                                },
+                                                            },
+                                                        },
+                                                        logo = new Sprite
+                                                        {
+                                                            Anchor = Anchor.Centre,
+                                                            Origin = Anchor.Centre,
+                                                        },
+                                                    }
+                                                },
+                                                impactContainer = new CircularContainer
+                                                {
+                                                    Anchor = Anchor.Centre,
+                                                    Origin = Anchor.Centre,
+                                                    Alpha = 0,
+                                                    BorderColour = Color4.White,
+                                                    RelativeSizeAxes = Axes.Both,
+                                                    BorderThickness = 10,
+                                                    Masking = true,
+                                                    Children = new Drawable[]
+                                                    {
+                                                        new Box
+                                                        {
+                                                            RelativeSizeAxes = Axes.Both,
+                                                            AlwaysPresent = true,
+                                                            Alpha = 0,
+                                                        }
+                                                    }
+                                                },
+                                                new MenuVisualisation
+                                                {
+                                                    Anchor = Anchor.Centre,
+                                                    Origin = Anchor.Centre,
+                                                    RelativeSizeAxes = Axes.Both,
+                                                    BlendingMode = BlendingMode.Additive,
+                                                    Alpha = 0.2f,
+                                                }
+                                            }
                                         }
                                     }
-                                },
-                                new MenuVisualisation
-                                {
-                                    Anchor = Anchor.Centre,
-                                    Origin = Anchor.Centre,
-                                    RelativeSizeAxes = Axes.Both,
-                                    BlendingMode = BlendingMode.Additive,
-                                    Alpha = 0.2f,
                                 }
                             }
                         }
@@ -197,13 +223,56 @@ namespace osu.Game.Screens.Menu
             ripple.Texture = textures.Get(@"Menu/logo");
         }
 
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
+        private int lastBeatIndex;
 
-            ripple.ScaleTo(ripple.Scale * 1.1f, 500);
-            ripple.FadeOut(500);
-            ripple.Loop(300);
+        protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
+        {
+            base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
+
+            lastBeatIndex = beatIndex;
+
+            var beatLength = timingPoint.BeatLength;
+
+            float amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
+
+            if (beatIndex < 0) return;
+
+            logoBeatContainer.ScaleTo(1 - 0.02f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
+            using (logoBeatContainer.BeginDelayedSequence(beat_in_time))
+                logoBeatContainer.ScaleTo(1, beatLength * 2, EasingTypes.OutQuint);
+
+            ripple.ClearTransforms();
+
+            ripple.ScaleTo(logoAmplitudeContainer.Scale);
+            ripple.Alpha = 0.15f * amplitudeAdjust;
+
+            ripple.ScaleTo(logoAmplitudeContainer.Scale * (1 + 0.04f * amplitudeAdjust), beatLength, EasingTypes.OutQuint);
+            ripple.FadeOut(beatLength, EasingTypes.OutQuint);
+
+            if (effectPoint.KiaiMode && flashLayer.Alpha < 0.4f)
+            {
+                flashLayer.ClearTransforms();
+
+                flashLayer.FadeTo(0.2f * amplitudeAdjust, beat_in_time, EasingTypes.Out);
+                using (flashLayer.BeginDelayedSequence(beat_in_time))
+                    flashLayer.FadeOut(beatLength);
+            }
+        }
+
+        protected override void Update()
+        {
+            base.Update();
+
+            const float scale_adjust_cutoff = 0.4f;
+            const float velocity_adjust_cutoff = 0.98f;
+
+            var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value?.Track?.CurrentAmplitudes.Maximum ?? 0 : 0;
+            logoAmplitudeContainer.ScaleTo(1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 75, EasingTypes.OutQuint);
+
+            if (maxAmplitude > velocity_adjust_cutoff)
+                triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50;
+            else
+                triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, 1, 0.995f, Time.Elapsed);
         }
 
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
@@ -254,4 +323,4 @@ namespace osu.Game.Screens.Menu
             impactContainer.ScaleTo(1.12f, 250);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
new file mode 100644
index 0000000000..7365963085
--- /dev/null
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -0,0 +1,259 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Screens.Multiplayer
+{
+    public class DrawableRoom : ClickableContainer
+    {
+        private const float content_padding = 5;
+        private const float height = 90;
+
+        private readonly Box sideStrip;
+        private readonly UpdateableAvatar avatar;
+        private readonly OsuSpriteText name;
+        private readonly Container flagContainer;
+        private readonly OsuSpriteText host;
+        private readonly OsuSpriteText rankBounds;
+        private readonly OsuSpriteText status;
+        private readonly FillFlowContainer<OsuSpriteText> beatmapInfoFlow;
+        private readonly OsuSpriteText beatmapTitle;
+        private readonly OsuSpriteText beatmapDash;
+        private readonly OsuSpriteText beatmapArtist;
+
+        private OsuColour colours;
+        private LocalisationEngine localisation;
+
+        public readonly Room Room;
+
+        public DrawableRoom(Room room)
+        {
+            Room = room;
+
+            RelativeSizeAxes = Axes.X;
+            Height = height;
+            CornerRadius = 5;
+            Masking = true;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Colour = Color4.Black.Opacity(40),
+                Radius = 5,
+            };
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.Gray(34),
+                },
+                sideStrip = new Box
+                {
+                    RelativeSizeAxes = Axes.Y,
+                    Width = content_padding,
+                },
+                avatar = new UpdateableAvatar
+                {
+                    Size = new Vector2(Height - content_padding* 2),
+                    Masking = true,
+                    CornerRadius = 5f,
+                    Margin = new MarginPadding { Left = content_padding * 2, Top = content_padding },
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = content_padding, Bottom = content_padding, Left = Height + content_padding * 2, Right = content_padding },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Direction = FillDirection.Vertical,
+                            Spacing = new Vector2(5f),
+                            Children = new Drawable[]
+                            {
+                                name = new OsuSpriteText
+                                {
+                                    TextSize = 18,
+                                },
+                                new Container
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    Height = 20f,
+                                    Children = new Drawable[]
+                                    {
+                                        new FillFlowContainer
+                                        {
+                                            AutoSizeAxes = Axes.X,
+                                            RelativeSizeAxes = Axes.Y,
+                                            Direction = FillDirection.Horizontal,
+                                            Spacing = new Vector2(5f, 0f),
+                                            Children = new Drawable[]
+                                            {
+                                                flagContainer = new Container
+                                                {
+                                                    Width = 30f,
+                                                    RelativeSizeAxes = Axes.Y,
+                                                },
+                                                new Container
+                                                {
+                                                    Width = 40f,
+                                                    RelativeSizeAxes = Axes.Y,
+                                                },
+                                                new OsuSpriteText
+                                                {
+                                                    Text = "hosted by",
+                                                    Anchor = Anchor.CentreLeft,
+                                                    Origin = Anchor.CentreLeft,
+                                                    TextSize = 14,
+                                                },
+                                                host = new OsuSpriteText
+                                                {
+                                                    Anchor = Anchor.CentreLeft,
+                                                    Origin = Anchor.CentreLeft,
+                                                    TextSize = 14,
+                                                    Font = @"Exo2.0-BoldItalic",
+                                                },
+                                            },
+                                        },
+                                        rankBounds = new OsuSpriteText
+                                        {
+                                            Anchor = Anchor.CentreRight,
+                                            Origin = Anchor.CentreRight,
+                                            Text = "#0 - #0",
+                                            TextSize = 14,
+                                            Margin = new MarginPadding { Right = 10 },
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            RelativeSizeAxes = Axes.X,
+                            AutoSizeAxes = Axes.Y,
+                            Direction = FillDirection.Vertical,
+                            Margin = new MarginPadding { Bottom = content_padding },
+                            Children = new Drawable[]
+                            {
+                                status = new OsuSpriteText
+                                {
+                                    TextSize = 14,
+                                    Font = @"Exo2.0-Bold",
+                                },
+                                beatmapInfoFlow = new FillFlowContainer<OsuSpriteText>
+                                {
+                                    RelativeSizeAxes = Axes.X,
+                                    AutoSizeAxes = Axes.Y,
+                                    Direction = FillDirection.Horizontal,
+                                    Children = new[]
+                                    {
+                                        beatmapTitle = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-BoldItalic",
+                                        },
+                                        beatmapDash = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-RegularItalic",
+                                        },
+                                        beatmapArtist = new OsuSpriteText
+                                        {
+                                            TextSize = 14,
+                                            Font = @"Exo2.0-RegularItalic",
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+            };
+
+            Room.Name.ValueChanged += displayName;
+            Room.Host.ValueChanged += displayUser;
+            Room.Status.ValueChanged += displayStatus;
+            Room.Beatmap.ValueChanged += displayBeatmap;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation)
+        {
+            this.localisation = localisation;
+            this.colours = colours;
+
+            beatmapInfoFlow.Colour = rankBounds.Colour = colours.Gray9;
+            host.Colour = colours.Blue;
+
+            displayStatus(Room.Status.Value);
+        }
+
+        private void displayName(string value)
+        {
+            name.Text = value;
+        }
+
+        private void displayUser(User value)
+        {
+            avatar.User = value;
+            host.Text = value.Username;
+            flagContainer.Children = new[] { new DrawableFlag(value.Country?.FlagName ?? @"__") { RelativeSizeAxes = Axes.Both } };
+        }
+
+        private void displayStatus(RoomStatus value)
+        {
+            if (value == null) return;
+            status.Text = value.Message;
+
+            foreach (Drawable d in new Drawable[] { sideStrip, status })
+                d.FadeColour(value.GetAppropriateColour(colours), 100);
+        }
+
+        private void displayBeatmap(BeatmapMetadata value)
+        {
+            if (value != null)
+            {
+                beatmapTitle.Current = localisation.GetUnicodePreference(value.TitleUnicode, value.Title);
+                beatmapDash.Text = @" - ";
+                beatmapArtist.Current = localisation.GetUnicodePreference(value.ArtistUnicode, value.Artist);
+            }
+            else
+            {
+                beatmapTitle.Current = null;
+                beatmapArtist.Current = null;
+
+                beatmapTitle.Text = @"Changing map";
+                beatmapDash.Text = string.Empty;
+                beatmapArtist.Text = string.Empty;
+            }
+        }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            Room.Name.ValueChanged -= displayName;
+            Room.Host.ValueChanged -= displayUser;
+            Room.Status.ValueChanged -= displayStatus;
+            Room.Beatmap.ValueChanged -= displayBeatmap;
+
+            base.Dispose(isDisposing);
+        }
+    }
+}
diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs
index 92c40f6351..4a10438cdb 100644
--- a/osu.Game/Screens/Play/HUD/ModDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs
@@ -2,7 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
-using System.Linq;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -12,6 +11,7 @@ using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.UI;
 using OpenTK;
 using osu.Framework.Input;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Screens.Play.HUD
 {
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.HUD
         {
             Children = new Drawable[]
             {
-                iconsContainer = new IconFlow
+                iconsContainer = new ReverseDepthFillFlowContainer<ModIcon>
                 {
                     Anchor = Anchor.TopCentre,
                     Origin = Anchor.TopCentre,
@@ -93,12 +93,5 @@ namespace osu.Game.Screens.Play.HUD
             contract();
             base.OnHoverLost(state);
         }
-
-        private class IconFlow : FillFlowContainer<ModIcon>
-        {
-            // just reverses the depth of flow contents.
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
-            protected override IEnumerable<ModIcon> FlowingChildren => base.FlowingChildren.Reverse();
-        }
     }
 }
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 2d6d212130..130642b9c7 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -170,8 +170,8 @@ namespace osu.Game.Screens.Select
             List<BeatmapGroup> visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList();
             if (visibleGroups.Count < 1)
                 return;
-            BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
 
+            BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
             BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)];
 
             selectGroup(group, panel);
diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
index c064a0272e..be2f5196ef 100644
--- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
+++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs
@@ -2,8 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -12,6 +10,7 @@ using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
+using osu.Game.Graphics.Containers;
 
 namespace osu.Game.Screens.Select.Options
 {
@@ -71,7 +70,7 @@ namespace osu.Game.Screens.Select.Options
                     Scale = new Vector2(1, 0),
                     Colour = Color4.Black.Opacity(0.5f),
                 },
-                buttonsContainer = new ButtonFlow
+                buttonsContainer = new ReverseDepthFillFlowContainer<BeatmapOptionsButton>
                 {
                     Height = height,
                     RelativePositionAxes = Axes.X,
@@ -109,16 +108,5 @@ namespace osu.Game.Screens.Select.Options
                 HotKey = hotkey
             });
         }
-
-        private class ButtonFlow : FillFlowContainer<BeatmapOptionsButton>
-        {
-            protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
-            protected override IEnumerable<BeatmapOptionsButton> FlowingChildren => base.FlowingChildren.Reverse();
-
-            public ButtonFlow()
-            {
-                Direction = FillDirection.Horizontal;
-            }
-        }
     }
 }
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index e9ead7c9c0..41fa53e8a3 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -316,11 +316,12 @@ namespace osu.Game.Screens.Select
         /// </summary>
         private void selectionChanged(BeatmapInfo beatmap)
         {
-            bool beatmapSetChange = false;
+            selectionChangedDebounce?.Cancel();
 
             if (beatmap.Equals(Beatmap?.BeatmapInfo))
                 return;
 
+            bool beatmapSetChange = false;
             if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID)
                 sampleChangeDifficulty.Play();
             else
@@ -331,7 +332,6 @@ namespace osu.Game.Screens.Select
 
             selectionChangeNoBounce = beatmap;
 
-            selectionChangedDebounce?.Cancel();
             selectionChangedDebounce = Scheduler.AddDelayed(delegate
             {
                 Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap);
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index 1361eefcff..93933c8fe9 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using Newtonsoft.Json;
+using osu.Framework.Configuration;
 
 namespace osu.Game.Users
 {
@@ -19,6 +20,8 @@ namespace osu.Game.Users
         [JsonProperty(@"country")]
         public Country Country;
 
+        public Bindable<UserStatus> Status = new Bindable<UserStatus>();
+
         //public Team Team;
 
         [JsonProperty(@"profile_colour")]
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
new file mode 100644
index 0000000000..bdfe6d1c8e
--- /dev/null
+++ b/osu.Game/Users/UserPanel.cs
@@ -0,0 +1,214 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Users
+{
+    public class UserPanel : Container
+    {
+        private const float height = 100;
+        private const float content_padding = 10;
+        private const float status_height = 30;
+
+        private OsuColour colours;
+
+        private readonly Container statusBar;
+        private readonly Box statusBg;
+        private readonly OsuSpriteText statusMessage;
+
+        public readonly Bindable<UserStatus> Status = new Bindable<UserStatus>();
+
+        public UserPanel(User user)
+        {
+            Height = height - status_height;
+            Masking = true;
+            CornerRadius = 5;
+            EdgeEffect = new EdgeEffect
+            {
+                Type = EdgeEffectType.Shadow,
+                Colour = Color4.Black.Opacity(0.25f),
+                Radius = 4,
+            };
+
+            Children = new Drawable[]
+            {
+                new AsyncLoadWrapper(new CoverBackgroundSprite(user)
+                {
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.Centre,
+                    FillMode = FillMode.Fill,
+                    OnLoadComplete = d => d.FadeInFromZero(200),
+                }) { RelativeSizeAxes = Axes.Both },
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = Color4.Black.Opacity(0.7f),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Padding = new MarginPadding { Top = content_padding, Left = content_padding, Right = content_padding },
+                    Children = new Drawable[]
+                    {
+                        new UpdateableAvatar
+                        {
+                            Size = new Vector2(height - status_height - content_padding * 2),
+                            User = user,
+                            Masking = true,
+                            CornerRadius = 5,
+                            EdgeEffect = new EdgeEffect
+                            {
+                                Type = EdgeEffectType.Shadow,
+                                Colour = Color4.Black.Opacity(0.25f),
+                                Radius = 4,
+                            },
+                        },
+                        new Container
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Padding = new MarginPadding { Left = height - status_height - content_padding },
+                            Children = new Drawable[]
+                            {
+                                new OsuSpriteText
+                                {
+                                    Text = user.Username,
+                                    TextSize = 18,
+                                    Font = @"Exo2.0-SemiBoldItalic",
+                                },
+                                new FillFlowContainer
+                                {
+                                    Anchor = Anchor.BottomLeft,
+                                    Origin = Anchor.BottomLeft,
+                                    AutoSizeAxes = Axes.X,
+                                    Height = 20f,
+                                    Direction = FillDirection.Horizontal,
+                                    Spacing = new Vector2(5f, 0f),
+                                    Children = new Drawable[]
+                                    {
+                                        new DrawableFlag(user.Country?.FlagName ?? @"__")
+                                        {
+                                            Width = 30f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                        new Container
+                                        {
+                                            Width = 40f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                        new CircularContainer
+                                        {
+                                            Width = 20f,
+                                            RelativeSizeAxes = Axes.Y,
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                    },
+                },
+                statusBar = new Container
+                {
+                    Anchor = Anchor.BottomLeft,
+                    Origin = Anchor.BottomLeft,
+                    RelativeSizeAxes = Axes.X,
+                    Alpha = 0f,
+                    Children = new Drawable[]
+                    {
+                        statusBg = new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Alpha = 0.5f,
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            AutoSizeAxes = Axes.Both,
+                            Spacing = new Vector2(5f, 0f),
+                            Children = new[]
+                            {
+                                new TextAwesome
+                                {
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    Icon = FontAwesome.fa_circle_o,
+                                    Shadow = true,
+                                    TextSize = 14,
+                                },
+                                statusMessage = new OsuSpriteText
+                                {
+                                    Anchor = Anchor.CentreLeft,
+                                    Origin = Anchor.CentreLeft,
+                                    Font = @"Exo2.0-Semibold",
+                                },
+                            },
+                        },
+                    },
+                },
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            this.colours = colours;
+            Status.ValueChanged += displayStatus;
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            Status.TriggerChange();
+        }
+
+        private void displayStatus(UserStatus status)
+        {
+            const float transition_duration = 500;
+
+            if (status == null)
+            {
+                statusBar.ResizeHeightTo(0f, transition_duration, EasingTypes.OutQuint);
+                statusBar.FadeOut(transition_duration, EasingTypes.OutQuint);
+                ResizeHeightTo(height - status_height, transition_duration, EasingTypes.OutQuint);
+            }
+            else
+            {
+                statusBar.ResizeHeightTo(status_height, transition_duration, EasingTypes.OutQuint);
+                statusBar.FadeIn(transition_duration, EasingTypes.OutQuint);
+                ResizeHeightTo(height, transition_duration, EasingTypes.OutQuint);
+
+                statusBg.FadeColour(status.GetAppropriateColour(colours), 500, EasingTypes.OutQuint);
+                statusMessage.Text = status.Message;
+            }
+        }
+
+        private class CoverBackgroundSprite : Sprite
+        {
+            private readonly User user;
+
+            public CoverBackgroundSprite(User user)
+            {
+                this.user = user;
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(TextureStore textures)
+            {
+                if (!string.IsNullOrEmpty(user.CoverUrl))
+                    Texture = textures.Get(user.CoverUrl);
+            }
+        }
+    }
+}
diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs
new file mode 100644
index 0000000000..461008db0f
--- /dev/null
+++ b/osu.Game/Users/UserStatus.cs
@@ -0,0 +1,67 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Users
+{
+    public abstract class UserStatus
+    {
+        public abstract string Message { get; }
+        public abstract Color4 GetAppropriateColour(OsuColour colours);
+    }
+
+    public abstract class UserStatusAvailable : UserStatus
+    {
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.BlueDarker;
+    }
+
+    public abstract class UserStatusBusy : UserStatus
+    {
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDark;
+    }
+
+    public class UserStatusOffline : UserStatus
+    {
+        public override string Message => @"Offline";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7;
+    }
+
+    public class UserStatusOnline : UserStatusAvailable
+    {
+        public override string Message => @"Online";
+    }
+
+    public class UserStatusSpectating : UserStatusAvailable
+    {
+        public override string Message => @"Spectating a game";
+    }
+
+    public class UserStatusInLobby : UserStatusAvailable
+    {
+        public override string Message => @"in Multiplayer Lobby";
+    }
+
+    public class UserStatusSoloGame :  UserStatusBusy
+    {
+        public override string Message => @"Solo Game";
+    }
+
+    public class UserStatusMultiplayerGame: UserStatusBusy
+    {
+        public override string Message => @"Multiplaying";
+    }
+
+    public class UserStatusModding : UserStatus
+    {
+        public override string Message => @"Modding a map";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark;
+    }
+
+    public class UserStatusDoNotDisturb : UserStatus
+    {
+        public override string Message => @"Do not disturb";
+        public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark;
+    }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index cde7a51ef4..25b692151f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -189,6 +189,7 @@
     <Compile Include="Database\RulesetDatabase.cs" />
     <Compile Include="Rulesets\Scoring\Score.cs" />
     <Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
+    <Compile Include="Screens\Menu\MenuSideFlashes.cs" />
     <Compile Include="Screens\Play\HUD\HealthDisplay.cs" />
     <Compile Include="Screens\Play\HUDOverlay.cs" />
     <Compile Include="Screens\Play\HUD\StandardHealthDisplay.cs" />
@@ -431,6 +432,24 @@
     <Compile Include="Overlays\Music\PlaylistOverlay.cs" />
     <Compile Include="Rulesets\Replays\IAutoGenerator.cs" />
     <Compile Include="Rulesets\Replays\AutoGenerator.cs" />
+    <Compile Include="Screens\Multiplayer\DrawableRoom.cs" />
+    <Compile Include="Online\Multiplayer\Room.cs" />
+    <Compile Include="Online\Multiplayer\RoomStatus.cs" />
+    <Compile Include="Users\UserPanel.cs" />
+    <Compile Include="Users\UserStatus.cs" />
+    <Compile Include="Overlays\DirectOverlay.cs" />
+    <Compile Include="Overlays\Direct\FilterControl.cs" />
+    <Compile Include="Overlays\Direct\Header.cs" />
+    <Compile Include="Overlays\Direct\SortTabControl.cs" />
+    <Compile Include="Graphics\UserInterface\OsuEnumDropdown.cs" />
+    <Compile Include="Overlays\Direct\DirectPanel.cs" />
+    <Compile Include="Overlays\Direct\DirectGridPanel.cs" />
+    <Compile Include="Overlays\Direct\DirectListPanel.cs" />
+    <Compile Include="Database\OnlineWorkingBeatmap.cs" />
+    <Compile Include="Database\BeatmapOnlineInfo.cs" />
+    <Compile Include="Overlays\Direct\SlimEnumDropdown.cs" />
+    <Compile Include="Graphics\Containers\ReverseDepthFillFlowContainer.cs" />
+    <Compile Include="Database\RankStatus.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj">