diff --git a/harrogate/.cargo/config b/harrogate/.cargo/config.toml
similarity index 100%
rename from harrogate/.cargo/config
rename to harrogate/.cargo/config.toml
diff --git a/harrogate/Cargo.toml b/harrogate/Cargo.toml
index 6f06af1..b6bb288 100644
--- a/harrogate/Cargo.toml
+++ b/harrogate/Cargo.toml
@@ -26,6 +26,7 @@ halloween-2020 = ["replace-default"]
 halloween-2021 = ["replace-default"]
 halloween-2022 = ["replace-default"]
 halloween-2023 = ["replace-default"]
+halloween-2024 = ["replace-default"]
 new-years-eve-2020 = ["replace-default"]
 november-2020 = ["replace-default"]
 
diff --git a/harrogate/src/halloween_2024.rs b/harrogate/src/halloween_2024.rs
new file mode 100644
index 0000000..ff7941e
--- /dev/null
+++ b/harrogate/src/halloween_2024.rs
@@ -0,0 +1,74 @@
+//! purple-and-orange with chaotically-varying background
+
+use super::delay;
+use house::{Harrogate, PORCH_BACK_LEN, PORCH_FRONT_LEN};
+use lights::{murmurf, rgb::Rgb, HardwareRgb, Lights};
+
+const BLACK: Rgb = Rgb(0, 0, 0);
+const ORANGE: Rgb = Rgb(255, 150, 0);
+const PURPLE: Rgb = Rgb(100, 0, 128);
+
+const COLORS: &[Rgb] = &[
+    Rgb(255, 0, 0),
+    Rgb(0, 1, 0),
+    Rgb(150, 150, 150),
+    Rgb(0, 1, 0),
+    Rgb(255, 150, 0),
+    Rgb(0, 1, 0),
+    Rgb(150, 0, 255),
+    Rgb(0, 1, 0),
+];
+
+/*const RAINBOW: &[Rgb] = &[
+    Rgb(255, 0, 0),
+    Rgb(255, 150, 0),
+    Rgb(255, 255, 0),
+    Rgb(150, 255, 0),
+    Rgb(0, 255, 0),
+    Rgb(0, 255, 255),
+    Rgb(0, 0, 255),
+    Rgb(150, 0, 255),
+    Rgb(255, 0, 150),
+];*/
+
+// a hundredth of a second
+const TIC: u32 = 48_000_0;
+
+#[allow(dead_code)]
+#[inline(always)]
+pub fn run(lights: &mut impl Lights<Pixel = HardwareRgb>) -> ! {
+    let mut rng = 0x642u32;
+
+    let mut bar = 0;
+    let mut color_index: usize = 0;
+
+    let mut back_buffer = [BLACK; PORCH_BACK_LEN];
+
+    let mut draw = |back_buffer: &[Rgb]| {
+        let front_pattern = [PURPLE, ORANGE].iter().cycle().cloned();
+        Harrogate {
+            porch_front: front_pattern.take(PORCH_FRONT_LEN),
+            porch_back: back_buffer.iter().cloned(),
+        }
+        .render_to(lights);
+    };
+
+    loop {
+        // animate rainbow bars
+        if bar == 0 {
+            color_index = (color_index + 1 + (murmurf(&mut rng) as usize % 6)) % COLORS.len();
+            bar = 10;
+        }
+        bar -= 1;
+
+        // push into buffer
+        back_buffer.rotate_right(1);
+        back_buffer[0] = COLORS[color_index];
+        back_buffer[PORCH_BACK_LEN - 1].1 = 255;
+
+        // render
+        draw(&back_buffer);
+
+        delay(TIC);
+    }
+}
diff --git a/harrogate/src/main.rs b/harrogate/src/main.rs
index 592eb60..f179ede 100644
--- a/harrogate/src/main.rs
+++ b/harrogate/src/main.rs
@@ -17,6 +17,7 @@ mod halloween_2020;
 mod halloween_2021;
 mod halloween_2022;
 mod halloween_2023;
+mod halloween_2024;
 mod november_2020;
 mod new_years_eve_2020;
 mod door_light;
@@ -62,6 +63,9 @@ fn main() -> ! {
     #[cfg(feature = "halloween-2023")]
     halloween_2023::run(&mut lights);
 
+    #[cfg(feature = "halloween-2024")]
+    halloween_2024::run(&mut lights);
+
     #[cfg(feature = "new-years-eve-2020")]
     new_years_eve_2020::run(&mut lights);
 
diff --git a/itsybitsy_m0_lights/src/lib.rs b/itsybitsy_m0_lights/src/lib.rs
index 5fcc919..2888d1f 100644
--- a/itsybitsy_m0_lights/src/lib.rs
+++ b/itsybitsy_m0_lights/src/lib.rs
@@ -63,43 +63,85 @@ where
 {
     #[inline]
     pub fn write(&mut self, bit: bool) {
-        // go high
-        self.high_out.set_high().unwrap();
-
-        // experimentally, there is some unknown overhead
-        // but these timings appear to work for me
-        unsafe {
-            compiler_fence(Ordering::SeqCst);
-            asm!(
-                // 8 nops
-                "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
-            );
-            compiler_fence(Ordering::SeqCst);
-        }
-
         if bit {
             unsafe {
+                compiler_fence(Ordering::SeqCst);
+                self.high_out.set_high().unwrap();
                 compiler_fence(Ordering::SeqCst);
                 asm!(
-                    // 14 nops
+                    // 15 nops
                     "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
-                    "nop;", "nop;", "nop;", "nop;",
+                    "nop;", "nop;", "nop;", "nop;", "nop;",
+                );
+                compiler_fence(Ordering::SeqCst);
+                self.high_out.set_low().unwrap();
+                compiler_fence(Ordering::SeqCst);
+                asm!(
+                    // 8 nops
+                    "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                );
+                compiler_fence(Ordering::SeqCst);
+            }
+        } else {
+            unsafe {
+                compiler_fence(Ordering::SeqCst);
+                self.high_out.set_high().unwrap();
+                compiler_fence(Ordering::SeqCst);
+                asm!(
+                    // 8 nops
+                    "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                );
+                compiler_fence(Ordering::SeqCst);
+                self.high_out.set_low().unwrap();
+                compiler_fence(Ordering::SeqCst);
+                asm!(
+                    // 15 nops
+                    "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                    "nop;", "nop;", "nop;", "nop;", "nop;",
                 );
                 compiler_fence(Ordering::SeqCst);
             }
         }
 
-        // go low
-        self.high_out.set_low().unwrap();
+        if false {
+            // go high
+            self.high_out.set_high().unwrap();
 
-        unsafe {
-            compiler_fence(Ordering::SeqCst);
-            asm!(
-                // 15 nops
-                "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
-                "nop;", "nop;", "nop;", "nop;", "nop;",
-            );
-            compiler_fence(Ordering::SeqCst);
+            // experimentally, there is some unknown overhead
+            // but these timings appear to work for me
+            unsafe {
+                compiler_fence(Ordering::SeqCst);
+                asm!(
+                    // 8 nops
+                    "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                );
+                compiler_fence(Ordering::SeqCst);
+            }
+
+            if bit {
+                unsafe {
+                    compiler_fence(Ordering::SeqCst);
+                    asm!(
+                        // 14 nops
+                        "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                        "nop;", "nop;", "nop;", "nop;", "nop;",
+                    );
+                    compiler_fence(Ordering::SeqCst);
+                }
+            }
+
+            // go low
+            self.high_out.set_low().unwrap();
+
+            unsafe {
+                compiler_fence(Ordering::SeqCst);
+                asm!(
+                    // 15 nops
+                    "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;", "nop;",
+                    "nop;", "nop;", "nop;", "nop;", "nop;",
+                );
+                compiler_fence(Ordering::SeqCst);
+            }
         }
     }