diff --git a/Cargo.lock b/Cargo.lock
index 667ba497e34ae9f513eb5377569314d5a356e3c0..4ba483ef1a998450a67ee6e7653c38aa205c4084 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,6 +165,15 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
 
+[[package]]
+name = "colored"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
+dependencies = [
+ "windows-sys",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.4.2"
@@ -658,6 +667,7 @@ name = "rapidriter-cat"
 version = "0.1.0"
 dependencies = [
  "bitvec",
+ "colored",
  "image",
 ]
 
@@ -993,6 +1003,79 @@ version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
 
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
 [[package]]
 name = "winnow"
 version = "0.7.3"
diff --git a/Cargo.toml b/Cargo.toml
index 70b900052fbbd2458999dba592537f71f5ddc632..7558fae791a455350c44f595a3af78661b66bcfc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ crate-type = ["cdylib", "rlib"]
 
 [dependencies]
 bitvec = "1.0.1"
+colored = "3.0.0"
 
 [build-dependencies]
 image = "0.25.5"
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6fb8dea47216d566025804433525ddae23632f4b
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,69 @@
+use std::{
+    thread,
+    time::{Duration, Instant},
+};
+
+use bitvec::{order::Msb0, view::AsBits};
+use colored::Colorize;
+use rapidriter_cat::next_frame;
+
+const WIDTH: usize = 96;
+const HEIGHT: usize = 38;
+
+fn main() {
+    // clear screen
+    print!("\x1B[2J");
+
+    let mut frame = [0; 456];
+
+    let start = Instant::now();
+    for i in 0..100u16 {
+        let target = start + Duration::from_millis(u64::from(i) * 100);
+        let now = Instant::now();
+        if now < target {
+            thread::sleep(target - now);
+        }
+
+        if next_frame(i, frame.as_mut_ptr()) == 1 {
+            break;
+        }
+
+        draw_frame(&frame);
+    }
+}
+
+fn draw_frame(frame: &[u8; 456]) {
+    // reset cursor position to (1,1)
+    print!("\x1B[1;1H");
+
+    let bv = frame.as_bits::<Msb0>();
+
+    // top part of border
+    for _ in 0..(WIDTH + 2) {
+        print!("{}", " ".on_white());
+    }
+    println!();
+
+    for y in 0..HEIGHT {
+        print!("{}", " ".on_white());
+
+        for x in 0..WIDTH {
+            if bv[x + y * WIDTH] {
+                print!("{}", " ".on_red());
+            } else {
+                print!(" ");
+            }
+        }
+
+        print!("{}", " ".on_white());
+
+        println!();
+    }
+
+    // bottom part of border
+    for _ in 0..(WIDTH + 2) {
+        print!("{}", " ".on_white());
+    }
+
+    println!();
+}