diff --git a/components/micropython/usermodule/mp_leds.c b/components/micropython/usermodule/mp_leds.c
index f98e8cfb3445e1cd5194b1267261582d3e80243d..ed18607cfefb1c2dd92b0cdc7645d6f9e5110275 100644
--- a/components/micropython/usermodule/mp_leds.c
+++ b/components/micropython/usermodule/mp_leds.c
@@ -58,6 +58,11 @@ STATIC mp_obj_t mp_leds_get_slew_rate() {
 STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_leds_get_slew_rate_obj,
                                  mp_leds_get_slew_rate);
 
+STATIC mp_obj_t mp_leds_get_steady() {
+    return mp_obj_new_bool(st3m_leds_get_steady());
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_leds_get_steady_obj, mp_leds_get_steady);
+
 STATIC mp_obj_t mp_led_set_rgb(size_t n_args, const mp_obj_t *args) {
     uint8_t index = mp_obj_get_int(args[0]);
     float red = mp_obj_get_float(args[1]);
@@ -165,6 +170,7 @@ STATIC const mp_rom_map_elem_t mp_module_leds_globals_table[] = {
       MP_ROM_PTR(&mp_leds_get_slew_rate_obj) },
     { MP_ROM_QSTR(MP_QSTR_set_slew_rate),
       MP_ROM_PTR(&mp_leds_set_slew_rate_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_steady), MP_ROM_PTR(&mp_leds_get_steady_obj) },
 };
 
 STATIC MP_DEFINE_CONST_DICT(mp_module_leds_globals,
diff --git a/components/st3m/st3m_leds.c b/components/st3m/st3m_leds.c
index 500d508dcba32e066b5e39f949e0da4ea677f08b..aa495b6715aaf0996c58eb430fe2ac7d847aa2fa 100644
--- a/components/st3m/st3m_leds.c
+++ b/components/st3m/st3m_leds.c
@@ -38,6 +38,7 @@ typedef struct {
     uint8_t brightness;
     uint8_t slew_rate;
     bool auto_update;
+    bool is_steady;
     uint8_t timer;
 
     st3m_leds_gamma_table_t gamma_red;
@@ -67,7 +68,7 @@ static void set_single_led(uint8_t index, st3m_u8_rgb_t c) {
 
 static uint16_t led_get_slew(uint16_t old, uint16_t new, uint16_t slew) {
     new = new << 8;
-    if (slew == 255) return new;
+    if (slew == 255 || (old == new)) return new;
     int16_t bonus = ((int16_t)slew) - 225;
     slew = 30 + (slew << 2) + ((slew * slew) >> 3);
     if (bonus > 0) {
@@ -105,13 +106,17 @@ void st3m_leds_update_hardware() {
     if (state.auto_update) leds_update_target();
 
     bool is_different = false;
+    bool is_steady = true;
 
     for (int i = 0; i < 40; i++) {
         st3m_u8_rgb_t ret = state.target[i];
         st3m_u16_rgb_t c;
-        c.r = led_get_slew(state.slew_output[i].r, ret.r, state.slew_rate);
-        c.g = led_get_slew(state.slew_output[i].g, ret.g, state.slew_rate);
-        c.b = led_get_slew(state.slew_output[i].b, ret.b, state.slew_rate);
+        st3m_u16_rgb_t prev = state.slew_output[i];
+        c.r = led_get_slew(prev.r, ret.r, state.slew_rate);
+        c.g = led_get_slew(prev.g, ret.g, state.slew_rate);
+        c.b = led_get_slew(prev.b, ret.b, state.slew_rate);
+        if ((prev.r != c.r) || (prev.g != c.g) || (prev.b != c.b))
+            is_steady = false;
         state.slew_output[i] = c;
 
         c.r = ((uint32_t)c.r * state.brightness) >> 8;
@@ -129,6 +134,7 @@ void st3m_leds_update_hardware() {
             is_different = true;
         }
     }
+    state.is_steady = is_steady;
     UNLOCK;
 
     if (is_different || (state.timer > 10)) {
@@ -164,6 +170,7 @@ void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
     state.target_buffer[index].r = (uint8_t)(red * 255);
     state.target_buffer[index].g = (uint8_t)(green * 255);
     state.target_buffer[index].b = (uint8_t)(blue * 255);
+    state.is_steady = false;
     UNLOCK_INCOMING;
 }
 
@@ -277,6 +284,13 @@ uint8_t st3m_leds_get_slew_rate() {
     return res;
 }
 
+bool st3m_leds_get_steady() {
+    LOCK;
+    uint8_t res = state.is_steady;
+    UNLOCK;
+    return res;
+}
+
 void st3m_leds_set_auto_update(bool on) {
     LOCK;
     state.auto_update = on;
diff --git a/components/st3m/st3m_leds.h b/components/st3m/st3m_leds.h
index ba05cbd0a14cf5ac12a67c55081cc792a0bdc19b..66f762173e1341547d58e069bb41e3f2a98c5c2e 100644
--- a/components/st3m/st3m_leds.h
+++ b/components/st3m/st3m_leds.h
@@ -44,9 +44,7 @@ uint8_t st3m_leds_get_brightness();
 // to 255 to disable. Currently clocks at 50Hz.
 void st3m_leds_set_slew_rate(uint8_t slew_rate);
 uint8_t st3m_leds_get_slew_rate();
-
-void st3m_leds_set_max_slew_rate(uint8_t slew_rate);
-uint8_t st3m_leds_get_max_slew_rate();
+bool st3m_leds_get_steady();
 
 // Update LEDs. Ie., copy the LED state from the first buffer into the second
 // buffer, effectively scheduling the LED state to be presented to the user.
diff --git a/python_payload/apps/appearance/__init__.py b/python_payload/apps/appearance/__init__.py
index e9ce5824ef5029f35f0d4c62f90e50b4122546f5..d61e360e5f5411a8b220c7a0104a431ffd151746 100644
--- a/python_payload/apps/appearance/__init__.py
+++ b/python_payload/apps/appearance/__init__.py
@@ -30,7 +30,6 @@ class App(Application):
         self.mid_x = 42
         self.led_accumulator_ms = 0
         self.blueish = False
-        self.half_time = 600
 
     def draw_widget(self, label):
         ctx = self.ctx
@@ -213,10 +212,6 @@ class App(Application):
             tmp = 0
         elif tmp > 255:
             tmp = 255
-        if tmp < 120:
-            self.half_time = 600 + (120 - tmp) * (120 - tmp) / 2
-        else:
-            self.half_time = 600
         if tmp != settings.num_leds_speed.value:
             settings.num_leds_speed.set_value(tmp)
             leds.set_slew_rate(settings.num_leds_speed.value)
@@ -237,8 +232,7 @@ class App(Application):
     def think(self, ins, delta_ms):
         super().think(ins, delta_ms)
         self.delta_ms += delta_ms
-        if self.focused_widget == 2:
-            self.led_accumulator_ms += delta_ms
+
         if (
             self.input.buttons.app.right.pressed
             or self.input.buttons.app.right.repeated
@@ -249,9 +243,13 @@ class App(Application):
         if self.input.buttons.app.middle.pressed:
             self.select_pressed = True
 
-        while self.led_accumulator_ms > self.half_time:
-            self.led_accumulator_ms = self.led_accumulator_ms % self.half_time
+        if self.focused_widget == 3 and leds.get_steady():
+            self.led_accumulator_ms += delta_ms
+
+        if self.led_accumulator_ms > 1000:
+            self.led_accumulator_ms = 0
             led_patterns.shift_all_hsv(h=0.8)
+            leds.update()
 
     def on_enter(self, vm):
         super().on_enter(vm)
diff --git a/python_payload/apps/shoegaze/__init__.py b/python_payload/apps/shoegaze/__init__.py
index 822383b1c859a46c26c5071c2ddac2acb8306a90..2e90f9330841fba3f258da57198bc3f1dfd44988 100644
--- a/python_payload/apps/shoegaze/__init__.py
+++ b/python_payload/apps/shoegaze/__init__.py
@@ -3,6 +3,7 @@ from st3m.goose import Dict, Any, List, Optional
 from st3m.ui.view import View, ViewManager
 from st3m.input import InputState
 from ctx import Context
+from st3m.ui import colours, led_patterns
 
 import json
 import errno
@@ -45,6 +46,8 @@ class ShoegazeApp(Application):
         self._rand_rot = 0.0
         self.delay_on = True
         self.organ_on = False
+        self.hue_change = False
+        self.hue = 0
         self._set_chord(3, force_update=True)
 
     def _build_synth(self) -> None:
@@ -146,11 +149,10 @@ class ShoegazeApp(Application):
         self._update_connections()
 
     def _set_chord(self, i: int, force_update=False) -> None:
-        hue = int(54 * (i + 0.5)) % 360
         if i != self.chord_index or force_update:
+            self.hue = (54 * (i + 0.5)) * math.tau / 360
             self.chord_index = i
-            leds.set_all_hsv(hue, 1, 0.7)
-            leds.update()
+            self.hue_change = True
             if self.organ_on and self._organ_chords[i] is not None:
                 self.chord = self._organ_chords[i]
             else:
@@ -255,10 +257,22 @@ class ShoegazeApp(Application):
             self.bass_string.decay = 1000
             self.bass_string.signals.trigger.start()
 
+        if self.hue_change:
+            leds.set_slew_rate(min(self.max_slew_rate, 200))
+            leds.set_all_rgb(*colours.hsv_to_rgb(self.hue, 1, 0.7))
+            leds.update()
+            self.hue_change = False
+        elif leds.get_steady():
+            leds.set_slew_rate(min(self.max_slew_rate, 50))
+            led_patterns.pretty_pattern()
+            leds.set_all_rgba(*colours.hsv_to_rgb(self.hue, 1, 0.7), 0.75)
+            leds.update()
+
     def on_enter(self, vm: Optional[ViewManager]) -> None:
         if self.blm is None:
             self._build_synth()
         self.blm.foreground = True
+        self.max_slew_rate = leds.get_slew_rate()
         self._set_chord(self.chord_index, force_update=True)
 
     def on_exit(self) -> None:
diff --git a/python_payload/mypystubs/leds.pyi b/python_payload/mypystubs/leds.pyi
index a09c4b634a23fca99bac8041c3d23e5d57aab8b9..9d0acc4a5d44347c904c4d19834a937c715bc572 100644
--- a/python_payload/mypystubs/leds.pyi
+++ b/python_payload/mypystubs/leds.pyi
@@ -22,6 +22,16 @@ def get_slew_rate() -> int:
     Get maximum change rate of brightness. See set_slew_rate()
     """
 
+def get_steady() -> bool:
+    """
+    Returns true if the LED engine is certain that there is no further color change
+    until user input occurs, meaning that the last animation has finished and there
+    is no unprocessed data is queued up. The LED task consumes data at 50Hz.
+
+    If you use this function to time next animation frames be sure to set a maximum
+    rate so that users with a high slew rate setting will not get strobelighted!
+    """
+
 def set_rgb(i: int, r: float, g: float, b: float) -> None:
     """Set LED i to rgb value r, g, b
 
diff --git a/python_payload/st3m/ui/led_patterns.py b/python_payload/st3m/ui/led_patterns.py
index a5aa9ccdb175092793ccdf1e8aa43e060da02435..77dacdd3d2c776d74a2a2f60d31017d93cc5d61d 100644
--- a/python_payload/st3m/ui/led_patterns.py
+++ b/python_payload/st3m/ui/led_patterns.py
@@ -15,6 +15,19 @@ def _clip(val):
     return val
 
 
+def _hue_blend(h1, h2, n):
+    """
+    mixes two hues together while accounting for overflow.
+    n in range [0..1] blends between [h1..h2]
+    """
+    if abs(h2 - h1) < (math.tau / 2):
+        return h2 * n + h1 * (1 - n)
+    elif h2 > h1:
+        return h2 * n + (h1 + math.tau) * (1 - n)
+    else:
+        return (h2 + math.tau) * n + h1 * (1 - n)
+
+
 def set_menu_colors():
     """
     set all LEDs to the configured menu colors if provided in
@@ -65,12 +78,7 @@ def pretty_pattern():
             hsv_old = colours.rgb_to_hsv(*leds.get_rgb(g))
             hsv_mixed = [0.0, 0.0, 0.0]
             k = (i - 39) / 8
-            if abs(hsv[0] - hsv_old[0]) < math.tau / 2:
-                hsv_mixed[0] = hsv_old[0] * k + hsv[0] * (1 - k)
-            elif hsv[0] > hsv_old[0]:
-                hsv_mixed[0] = (hsv_old[0] + math.tau) * k + hsv[0] * (1 - k)
-            else:
-                hsv_mixed[0] = hsv_old[0] * k + (hsv[0] + math.tau) * (1 - k)
+            hsv_mixed[0] = _hue_blend(hsv[0], hsv_old[0], k)
 
             for h in range(1, 3):
                 hsv_mixed[h] = hsv_old[h] * k + hsv[h] * (1 - k)
diff --git a/sim/fakes/leds.py b/sim/fakes/leds.py
index 6b268f462005fd535dfd612f164dbf012f6f01e6..bbffc0eee932edeb362427e7844c919d5c5dc630 100644
--- a/sim/fakes/leds.py
+++ b/sim/fakes/leds.py
@@ -17,6 +17,10 @@ def get_rgb(ix):
     return 0, 0, 0
 
 
+def get_steady():
+    return False
+
+
 def set_all_rgb(r, g, b):
     for i in range(40):
         set_rgb(i, r, g, b)