diff --git a/Cargo.lock b/Cargo.lock
index 7b05670..0c7b8b2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2605,7 +2605,6 @@ dependencies = [
  "num",
  "plotters",
  "poise",
- "rand",
  "sqlx",
  "tokio",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 329660f..61c9553 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,6 @@ sqlx = { version = "0.8.0", features = ["sqlite", "runtime-tokio", "chrono"] }
 hypertesseract = { features=["image"], git="https://github.com/BlueGhostGH/hypertesseract.git", rev="4e05063" }
 tokio = {version="1.38.0", features=["rt-multi-thread"]}
 imageproc = "0.25.0"
-rand = "0.8.5"
 
 [profile.dev.package."*"]
 opt-level = 3
diff --git a/schema.sql b/schema.sql
index 07312b0..c4fc7d3 100644
--- a/schema.sql
+++ b/schema.sql
@@ -1,9 +1,10 @@
 # {{{ users
+# }}}
 create table IF NOT EXISTS users (
     id INTEGER NOT NULL PRIMARY KEY,
-    discord_id TEXT UNIQUE NOT NULL
+    discord_id TEXT UNIQUE NOT NULL,
+    is_pookie BOOL NOT NULL DEFAULT 0
 );
-# }}}
 # {{{ songs
 CREATE TABLE IF NOT EXISTS songs (
     id INTEGER NOT NULL PRIMARY KEY,
diff --git a/src/arcaea/jacket.rs b/src/arcaea/jacket.rs
index 2ced254..92d5e83 100644
--- a/src/arcaea/jacket.rs
+++ b/src/arcaea/jacket.rs
@@ -5,7 +5,7 @@ use num::Integer;
 
 use crate::{
 	arcaea::chart::{Difficulty, Jacket, SongCache},
-	assets::{get_assets_dir, should_skip_jacket_art},
+	assets::{get_assets_dir, should_blur_jacket_art, should_skip_jacket_art},
 	context::Error,
 	recognition::fuzzy_song_name::guess_chart_name,
 };
@@ -149,12 +149,14 @@ impl JacketCache {
 
 					let image = image::load_from_memory(contents)?;
 					jacket_vectors.push((song_id, ImageVec::from_image(&image)));
+					let mut image =
+						image.resize(BITMAP_IMAGE_SIZE, BITMAP_IMAGE_SIZE, FilterType::Nearest);
 
-					let bitmap: &'static _ = Box::leak(Box::new(
-						image
-							.resize(BITMAP_IMAGE_SIZE, BITMAP_IMAGE_SIZE, FilterType::Nearest)
-							.into_rgb8(),
-					));
+					if should_blur_jacket_art() {
+						image = image.blur(20.0);
+					}
+
+					let bitmap: &'static _ = Box::leak(Box::new(image.into_rgb8()));
 
 					if name == "base" {
 						// Inefficiently iterates over everything, but it's fine for ~1k entries
diff --git a/src/arcaea/play.rs b/src/arcaea/play.rs
index d2c1528..848a694 100644
--- a/src/arcaea/play.rs
+++ b/src/arcaea/play.rs
@@ -356,10 +356,12 @@ impl Play {
 // {{{ General functions
 pub type PlayCollection<'a> = Vec<(Play, &'a Song, &'a Chart)>;
 
-pub async fn get_b30_plays<'a>(
+pub async fn get_best_plays<'a>(
 	db: &SqlitePool,
 	song_cache: &'a SongCache,
 	user: &User,
+	min_amount: usize,
+	max_amount: usize,
 ) -> Result<Result<PlayCollection<'a>, &'static str>, Error> {
 	// {{{ DB data fetching
 	let plays: Vec<DbPlay> = query_as(
@@ -378,7 +380,7 @@ pub async fn get_b30_plays<'a>(
 	.await?;
 	// }}}
 
-	if plays.len() < 30 {
+	if plays.len() < min_amount {
 		return Ok(Err("Not enough plays found"));
 	}
 
@@ -395,7 +397,7 @@ pub async fn get_b30_plays<'a>(
 		.collect::<Result<Vec<_>, Error>>()?;
 
 	plays.sort_by_key(|(play, _, chart)| -play.score.play_rating(chart.chart_constant));
-	plays.truncate(30);
+	plays.truncate(max_amount);
 	// }}}
 
 	Ok(Ok(plays))
@@ -407,6 +409,6 @@ pub fn compute_b30_ptt(plays: &PlayCollection<'_>) -> i32 {
 		.iter()
 		.map(|(play, _, chart)| play.score.play_rating(chart.chart_constant))
 		.sum::<i32>()
-		/ 30
+		/ plays.len() as i32
 }
 // }}}
diff --git a/src/assets.rs b/src/assets.rs
index 84d2e0c..dddea81 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -1,9 +1,9 @@
-use std::{cell::RefCell, env::var, path::PathBuf, str::FromStr, sync::OnceLock};
+use std::{cell::RefCell, env::var, path::PathBuf, str::FromStr, sync::OnceLock, thread::LocalKey};
 
 use freetype::{Face, Library};
 use image::{imageops::FilterType, ImageBuffer, Rgb, Rgba};
 
-use crate::arcaea::chart::Difficulty;
+use crate::{arcaea::chart::Difficulty, timed};
 
 #[inline]
 pub fn get_data_dir() -> PathBuf {
@@ -18,18 +18,39 @@ pub fn get_assets_dir() -> PathBuf {
 
 #[inline]
 fn get_font(name: &str) -> RefCell<Face> {
-	let face = FREETYPE_LIB.with(|lib| {
-		lib.new_face(get_assets_dir().join(format!("{}.ttf", name)), 0)
-			.expect(&format!("Could not load {} font", name))
+	let face = timed!(format!("load font \"{name}\""), {
+		FREETYPE_LIB.with(|lib| {
+			lib.new_face(get_assets_dir().join(name), 0)
+				.expect(&format!("Could not load {} font", name))
+		})
 	});
 	RefCell::new(face)
 }
 
 thread_local! {
 pub static FREETYPE_LIB: Library = Library::init().unwrap();
-pub static SAIRA_FONT: RefCell<Face> = get_font("saira-variable");
-pub static EXO_FONT: RefCell<Face> = get_font("exo-variable");
-pub static GEOSANS_FONT: RefCell<Face> = get_font("geosans-light");
+pub static SAIRA_FONT: RefCell<Face> = get_font("saira-variable.ttf");
+pub static EXO_FONT: RefCell<Face> = get_font("exo-variable.ttf");
+pub static GEOSANS_FONT: RefCell<Face> = get_font("geosans-light.ttf");
+pub static KAZESAWA_FONT: RefCell<Face> = get_font("kazesawa-regular.ttf");
+pub static KAZESAWA_BOLD_FONT: RefCell<Face> = get_font("kazesawa-bold.ttf");
+pub static NOTO_SANS_FONT: RefCell<Face> = get_font("noto-sans.ttf");
+pub static ARIAL_FONT: RefCell<Face> = get_font("arial.ttf");
+pub static UNI_FONT: RefCell<Face> = get_font("unifont.otf");
+}
+
+#[inline]
+pub fn with_font<T>(
+	primary: &'static LocalKey<RefCell<Face>>,
+	f: impl FnOnce(&mut [&mut Face]) -> T,
+) -> T {
+	UNI_FONT.with_borrow_mut(|uni| {
+		// NOTO_SANS_FONT.with_borrow_mut(|noto| {
+		// ARIAL_FONT.with_borrow_mut(|arial| {
+		primary.with_borrow_mut(|primary| f(&mut [primary, uni]))
+		// })
+		// })
+	})
 }
 
 #[inline]
@@ -38,6 +59,12 @@ pub fn should_skip_jacket_art() -> bool {
 	*CELL.get_or_init(|| var("SHIMMERING_NO_JACKETS").unwrap_or_default() == "1")
 }
 
+#[inline]
+pub fn should_blur_jacket_art() -> bool {
+	static CELL: OnceLock<bool> = OnceLock::new();
+	*CELL.get_or_init(|| var("SHIMMERING_BLUR_JACKETS").unwrap_or_default() == "1")
+}
+
 pub fn get_b30_background() -> &'static ImageBuffer<Rgb<u8>, Vec<u8>> {
 	static CELL: OnceLock<ImageBuffer<Rgb<u8>, Vec<u8>>> = OnceLock::new();
 	CELL.get_or_init(|| {
@@ -46,8 +73,8 @@ pub fn get_b30_background() -> &'static ImageBuffer<Rgb<u8>, Vec<u8>> {
 
 		raw_b30_background
 			.resize(
-				3 * raw_b30_background.width(),
-				3 * raw_b30_background.height(),
+				8 * raw_b30_background.width(),
+				8 * raw_b30_background.height(),
 				FilterType::Lanczos3,
 			)
 			.blur(7.0)
diff --git a/src/bitmap.rs b/src/bitmap.rs
index d0b830b..330026c 100644
--- a/src/bitmap.rs
+++ b/src/bitmap.rs
@@ -9,8 +9,8 @@
 use freetype::{
 	bitmap::PixelMode,
 	face::{KerningMode, LoadFlag},
-	ffi::{FT_Err_Ok, FT_Set_Var_Design_Coordinates, FT_GLYPH_BBOX_PIXELS},
-	Bitmap, BitmapGlyph, Face, FtResult, Glyph, StrokerLineCap, StrokerLineJoin,
+	ffi::{FT_Set_Var_Design_Coordinates, FT_GLYPH_BBOX_PIXELS},
+	Bitmap, BitmapGlyph, Face, Glyph, StrokerLineCap, StrokerLineJoin,
 };
 use image::GenericImage;
 use num::traits::Euclid;
@@ -90,6 +90,11 @@ impl Rect {
 		Self::new(0, 0, image.width(), image.height())
 	}
 
+	#[inline]
+	pub fn scaled(&self, scale: u32) -> Self {
+		Self::new(self.x, self.y, self.width * scale, self.height * scale)
+	}
+
 	#[inline]
 	pub fn align(&self, alignment: (Align, Align), pos: Position) -> Position {
 		(
@@ -180,22 +185,29 @@ impl BitmapCanvas {
 	}
 	// }}}
 	// {{{ Draw RBG image
-	/// Draws a bitmap image
 	pub fn blit_rbg(&mut self, pos: Position, (iw, ih): (u32, u32), src: &[u8]) {
-		let height = self.height();
-		for dx in 0..iw {
-			for dy in 0..ih {
-				let x = pos.0 + dx as i32;
-				let y = pos.1 + dy as i32;
-				if x >= 0 && (x as u32) < self.width && y >= 0 && (y as u32) < height {
-					let r = src[(dx + dy * iw) as usize * 3];
-					let g = src[(dx + dy * iw) as usize * 3 + 1];
-					let b = src[(dx + dy * iw) as usize * 3 + 2];
+		let iw = iw as i32;
+		let ih = ih as i32;
+		let width = self.width as i32;
+		let height = self.height() as i32;
 
-					let color = Color(r, g, b, 0xff);
+		let x_start = 0.max(-pos.0);
+		let y_start = 0.max(-pos.1);
+		let x_end = iw.min(width - pos.0);
+		let y_end = ih.min(height - pos.1);
 
-					self.set_pixel((x as u32, y as u32), color);
-				}
+		for dx in x_start..x_end {
+			for dy in y_start..y_end {
+				let x = pos.0 + dx;
+				let y = pos.1 + dy;
+
+				let r = src[(dx + dy * iw) as usize * 3];
+				let g = src[(dx + dy * iw) as usize * 3 + 1];
+				let b = src[(dx + dy * iw) as usize * 3 + 2];
+
+				let color = Color(r, g, b, 0xff);
+
+				self.set_pixel((x as u32, y as u32), color);
 			}
 		}
 	}
@@ -203,21 +215,67 @@ impl BitmapCanvas {
 	// {{{ Draw RGBA image
 	/// Draws a bitmap image taking care of the alpha channel.
 	pub fn blit_rbga(&mut self, pos: Position, (iw, ih): (u32, u32), src: &[u8]) {
-		let height = self.height();
-		for dx in 0..iw {
-			for dy in 0..ih {
-				let x = pos.0 + dx as i32;
-				let y = pos.1 + dy as i32;
-				if x >= 0 && (x as u32) < self.width && y >= 0 && (y as u32) < height {
-					let r = src[(dx + dy * iw) as usize * 4];
-					let g = src[(dx + dy * iw) as usize * 4 + 1];
-					let b = src[(dx + dy * iw) as usize * 4 + 2];
-					let a = src[(dx + dy * iw) as usize * 4 + 3];
+		let iw = iw as i32;
+		let ih = ih as i32;
+		let width = self.width as i32;
+		let height = self.height() as i32;
 
-					let color = Color(r, g, b, a);
+		let x_start = 0.max(-pos.0);
+		let y_start = 0.max(-pos.1);
+		let x_end = iw.min(width - pos.0);
+		let y_end = ih.min(height - pos.1);
 
-					self.set_pixel((x as u32, y as u32), color);
-				}
+		for dx in x_start..x_end {
+			for dy in y_start..y_end {
+				let x = pos.0 + dx;
+				let y = pos.1 + dy;
+
+				let r = src[(dx + dy * iw) as usize * 4];
+				let g = src[(dx + dy * iw) as usize * 4 + 1];
+				let b = src[(dx + dy * iw) as usize * 4 + 2];
+				let a = src[(dx + dy * iw) as usize * 4 + 3];
+
+				let color = Color(r, g, b, a);
+
+				self.set_pixel((x as u32, y as u32), color);
+			}
+		}
+	}
+	// }}}
+	// {{{ Draw scaled up RBG image
+	pub fn blit_rbg_scaled_up(
+		&mut self,
+		pos: Position,
+		(iw, ih): (u32, u32),
+		src: &[u8],
+		scale: u32,
+	) {
+		let scale = scale as i32;
+
+		let iw = iw as i32;
+		let ih = ih as i32;
+		let width = self.width as i32;
+		let height = self.height() as i32;
+
+		let x_start = pos.0.max(0);
+		let y_start = pos.1.max(0);
+		let x_end = (pos.0 + iw * scale).min(width);
+		let y_end = (pos.1 + ih * scale).min(height);
+
+		for x in x_start..x_end {
+			for y in y_start..y_end {
+				// NOTE: I could instead keep separate counters.
+				// It would introduce an additional if statement,
+				// but would not perform division.
+				let dx = (x - pos.0) / scale;
+				let dy = (y - pos.1) / scale;
+				let r = src[(dx + dy * iw) as usize * 3];
+				let g = src[(dx + dy * iw) as usize * 3 + 1];
+				let b = src[(dx + dy * iw) as usize * 3 + 2];
+
+				let color = Color(r, g, b, 0xff);
+
+				self.set_pixel((x as u32, y as u32), color);
 			}
 		}
 	}
@@ -239,53 +297,72 @@ impl BitmapCanvas {
 	// }}}
 	// {{{ Draw text
 	pub fn plan_text_rendering(
-		&mut self,
 		pos: Position,
-		face: &mut Face,
+		faces: &mut [&mut Face],
 		style: TextStyle,
 		text: &str,
 	) -> Result<(Position, Rect, Vec<(i64, Glyph)>), Error> {
 		// {{{ Control weight
 		if let Some(weight) = style.weight {
-			unsafe {
-				let raw = face.raw_mut() as *mut _;
-				let slice = [(weight as i64) << 16];
+			for face in faces.iter_mut() {
+				unsafe {
+					let raw = face.raw_mut() as *mut _;
+					let slice = [(weight as i64) << 16];
 
-				// {{{ Debug logging
-				// let mut amaster = 0 as *mut FT_MM_Var;
-				// FT_Get_MM_Var(raw, &mut amaster as *mut _);
-				// println!("{:?}", *amaster);
-				// println!("{:?}", *(*amaster).axis);
-				// println!("{:?}", *(*amaster).namedstyle);
-				// }}}
+					// {{{ Debug logging
+					// let mut amaster = 0 as *mut FT_MM_Var;
+					// FT_Get_MM_Var(raw, &mut amaster as *mut _);
+					// println!("{:?}", *amaster);
+					// println!("{:?}", *(*amaster).axis);
+					// println!("{:?}", *(*amaster).namedstyle);
+					// }}}
 
-				// Set variable weight
-				let err = FT_Set_Var_Design_Coordinates(raw, 3, slice.as_ptr());
-				if err != FT_Err_Ok {
-					let err: FtResult<_> = Err(err.into());
-					err?;
+					// Set variable weight
+					let _err = FT_Set_Var_Design_Coordinates(raw, 3, slice.as_ptr());
+					// Some fonts are not variable, so we just ignore errors :/
+					// if err != FT_Err_Ok {
+					// 	let err: FtResult<_> = Err(err.into());
+					// 	err?;
+					// }
 				}
 			}
 		}
 		// }}}
 
-		face.set_char_size((style.size << 6) as isize, 0, 0, 0)?;
+		for face in faces.iter_mut() {
+			face.set_char_size((style.size << 6) as isize, 0, 0, 0)?;
+		}
 
 		// {{{ Compute layout
 		let mut pen_x = 0;
-		let kerning = face.has_kerning();
+		let kerning: Vec<_> = faces.iter().map(|f| f.has_kerning()).collect();
 		let mut previous = None;
 		let mut data = Vec::new();
 
 		for c in text.chars() {
-			let glyph_index = face
-				.get_char_index(c as usize)
-				.ok_or_else(|| format!("Could not get glyph index for char {:?}", c))?;
+			let c = match c {
+				'~' => '~',
+				c => c,
+			};
 
-			if let Some(previous) = previous
-				&& kerning
+			let (face_index, glyph_index) = faces
+				.iter()
+				.enumerate()
+				.find_map(|(i, face)| {
+					let glyph_index = face.get_char_index(c as usize)?;
+					Some((i, glyph_index))
+				})
+				.ok_or_else(|| {
+					format!("Could not get glyph index for char '{}' in \"{}\"", c, text)
+				})?;
+
+			let face = &mut faces[face_index];
+			if let Some((prev_face_index, prev_glyth_index)) = previous
+				&& prev_face_index == face_index
+				&& kerning[face_index]
 			{
-				let delta = face.get_kerning(previous, glyph_index, KerningMode::KerningDefault)?;
+				let delta =
+					face.get_kerning(prev_glyth_index, glyph_index, KerningMode::KerningDefault)?;
 				pen_x += delta.x >> 6; // we shift to get rid of sub-pixel accuracy
 			}
 
@@ -293,7 +370,7 @@ impl BitmapCanvas {
 
 			data.push((pen_x, face.glyph().get_glyph()?));
 			pen_x += face.glyph().advance().x >> 6;
-			previous = Some(glyph_index);
+			previous = Some((face_index, glyph_index));
 		}
 
 		// }}}
@@ -345,11 +422,11 @@ impl BitmapCanvas {
 	pub fn text(
 		&mut self,
 		pos: Position,
-		face: &mut Face,
+		faces: &mut [&mut Face],
 		style: TextStyle,
 		text: &str,
 	) -> Result<(), Error> {
-		let (pos, bbox, data) = self.plan_text_rendering(pos, face, style, text)?;
+		let (pos, bbox, data) = Self::plan_text_rendering(pos, faces, style, text)?;
 
 		// {{{ Render glyphs
 		for (pos_x, glyph) in &data {
@@ -620,12 +697,14 @@ impl LayoutManager {
 }
 
 impl LayoutDrawer {
+	#[inline]
 	pub fn new(layout: LayoutManager, canvas: BitmapCanvas) -> Self {
 		Self { layout, canvas }
 	}
 
 	// {{{ Drawing
 	// {{{ Draw pixel
+	#[inline]
 	pub fn set_pixel(&mut self, id: LayoutBoxId, pos: (u32, u32), color: Color) {
 		let pos = self
 			.layout
@@ -634,14 +713,28 @@ impl LayoutDrawer {
 	}
 	// }}}
 	// {{{ Draw RGB image
-	/// Draws a bitmap image
+	#[inline]
 	pub fn blit_rbg(&mut self, id: LayoutBoxId, pos: Position, dims: (u32, u32), src: &[u8]) {
 		let pos = self.layout.position_relative_to(id, pos);
 		self.canvas.blit_rbg(pos, dims, src);
 	}
+
+	#[inline]
+	pub fn blit_rbg_scaled_up(
+		&mut self,
+		id: LayoutBoxId,
+		pos: Position,
+		dims: (u32, u32),
+		src: &[u8],
+		scale: u32,
+	) {
+		let pos = self.layout.position_relative_to(id, pos);
+		self.canvas.blit_rbg_scaled_up(pos, dims, src, scale);
+	}
 	// }}}
 	// {{{ Draw RGBA image
 	/// Draws a bitmap image taking care of the alpha channel.
+	#[inline]
 	pub fn blit_rbga(&mut self, id: LayoutBoxId, pos: Position, dims: (u32, u32), src: &[u8]) {
 		let pos = self.layout.position_relative_to(id, pos);
 		self.canvas.blit_rbga(pos, dims, src);
@@ -649,6 +742,7 @@ impl LayoutDrawer {
 	// }}}
 	// {{{ Fill
 	/// Fills with solid color
+	#[inline]
 	pub fn fill(&mut self, id: LayoutBoxId, color: Color) {
 		let current = self.layout.lookup(id);
 		self.canvas.fill(
@@ -660,16 +754,17 @@ impl LayoutDrawer {
 	// }}}
 	// {{{ Draw text
 	/// Render text
+	#[inline]
 	pub fn text(
 		&mut self,
 		id: LayoutBoxId,
 		pos: Position,
-		face: &mut Face,
+		faces: &mut [&mut Face],
 		style: TextStyle,
 		text: &str,
 	) -> Result<(), Error> {
 		let pos = self.layout.position_relative_to(id, pos);
-		self.canvas.text(pos, face, style, text)
+		self.canvas.text(pos, faces, style, text)
 	}
 	// }}}
 	// }}}
diff --git a/src/commands/stats.rs b/src/commands/stats.rs
index 37e8be6..7f3f2e0 100644
--- a/src/commands/stats.rs
+++ b/src/commands/stats.rs
@@ -1,7 +1,7 @@
 use std::io::Cursor;
 
 use chrono::DateTime;
-use image::{ImageBuffer, Rgb};
+use image::{DynamicImage, ImageBuffer, Rgb};
 use plotters::{
 	backend::{BitMapBackend, PixelFormat, RGBPixel},
 	chart::{ChartBuilder, LabelAreaPosition},
@@ -19,20 +19,22 @@ use sqlx::query_as;
 use crate::{
 	arcaea::{
 		jacket::BITMAP_IMAGE_SIZE,
-		play::{compute_b30_ptt, get_b30_plays, DbPlay},
+		play::{compute_b30_ptt, get_best_plays, DbPlay},
 		score::Score,
 	},
+	assert_is_pookie,
 	assets::{
 		get_b30_background, get_count_background, get_difficulty_background, get_grade_background,
 		get_name_backgound, get_ptt_emblem, get_score_background, get_status_background,
-		get_top_backgound, EXO_FONT,
+		get_top_backgound, with_font, EXO_FONT,
 	},
 	bitmap::{Align, BitmapCanvas, Color, LayoutDrawer, LayoutManager, Rect},
 	context::{Context, Error},
 	get_user,
+	logs::debug_image_log,
 	recognition::fuzzy_song_name::guess_song_and_chart,
 	reply_errors,
-	user::discord_it_to_discord_user,
+	user::{discord_it_to_discord_user, User},
 };
 
 // {{{ Stats
@@ -40,7 +42,7 @@ use crate::{
 #[poise::command(
 	prefix_command,
 	slash_command,
-	subcommands("chart", "b30"),
+	subcommands("chart", "b30", "bany"),
 	subcommand_required
 )]
 pub async fn stats(_ctx: Context<'_>) -> Result<(), Error> {
@@ -229,15 +231,28 @@ pub async fn plot(
 	Ok(())
 }
 // }}}
-// {{{ B30
-/// Show the 30 best scores
-#[poise::command(prefix_command, slash_command)]
-pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
-	let user = get_user!(&ctx);
+// {{{ Render best plays
+async fn best_plays(
+	ctx: &Context<'_>,
+	user: &User,
+	grid_size: (u32, u32),
+	require_full: bool,
+) -> Result<(), Error> {
 	let user_ctx = ctx.data();
 	let plays = reply_errors!(
 		ctx,
-		get_b30_plays(&user_ctx.db, &user_ctx.song_cache, &user).await?
+		get_best_plays(
+			&user_ctx.db,
+			&user_ctx.song_cache,
+			&user,
+			if require_full {
+				grid_size.0 * grid_size.1
+			} else {
+				grid_size.0 * (grid_size.1.max(1) - 1)
+			} as usize,
+			(grid_size.0 * grid_size.1) as usize
+		)
+		.await?
 	);
 
 	// {{{ Layout
@@ -258,7 +273,8 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 	let bottom_in_area = layout.margin_xy(bottom_area, -20, -7);
 	let item_area = layout.glue_horizontally(top_area, bottom_area);
 	let item_with_margin = layout.margin_xy(item_area, 22, 17);
-	let (item_grid, item_origins) = layout.repeated_evenly(item_with_margin, (5, 6));
+	let (item_grid, item_origins) =
+		layout.repeated_evenly(item_with_margin, (grid_size.0, grid_size.1));
 	let root = layout.margin_uniform(item_grid, 30);
 	// }}}
 	// {{{ Rendering prep
@@ -271,15 +287,21 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 	// {{{ Render background
 	let bg = get_b30_background();
 
-	drawer.blit_rbg(
+	let scale = (drawer.layout.width(root) as f32 / bg.width() as f32)
+		.max(drawer.layout.height(root) as f32 / bg.height() as f32)
+		.max(1.0)
+		.ceil() as u32;
+
+	drawer.blit_rbg_scaled_up(
 		root,
 		// Align the center of the image with the center of the root
-		Rect::from_image(bg).align(
+		Rect::from_image(bg).scaled(scale).align(
 			(Align::Center, Align::Center),
 			drawer.layout.lookup(root).center(),
 		),
 		bg.dimensions(),
 		bg.as_raw(),
+		scale,
 	);
 	// }}}
 
@@ -291,7 +313,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		let top_bg = get_top_backgound();
 		drawer.blit_rbg(top_area, (0, 0), top_bg.dimensions(), top_bg);
 
-		let (play, song, chart) = &plays[i];
+		let (play, song, chart) = if let Some(item) = plays.get(i) {
+			item
+		} else {
+			break;
+		};
 
 		// {{{ Display index
 		let bg = get_count_background();
@@ -299,12 +325,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 
 		// Draw background
 		drawer.blit_rbga(item_area, (-8, jacket_margin as i32), bg.dimensions(), bg);
-
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			drawer.text(
 				item_area,
 				(bg_center.0 - 12, bg_center.1 - 3 + jacket_margin),
-				font,
+				faces,
 				crate::bitmap::TextStyle {
 					size: 25,
 					weight: Some(800),
@@ -323,7 +348,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		drawer.blit_rbg(bottom_area, (0, 0), bg.dimensions(), bg.as_raw());
 
 		// Draw text
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			let initial_size = 24;
 			let mut style = crate::bitmap::TextStyle {
 				size: initial_size,
@@ -334,9 +359,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 				drop_shadow: None,
 			};
 
-			while drawer
-				.canvas
-				.plan_text_rendering((0, 0), font, style, &song.title)?
+			while BitmapCanvas::plan_text_rendering((0, 0), faces, style, &song.title)?
 				.1
 				.width >= drawer.layout.width(bottom_in_area)
 			{
@@ -350,7 +373,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 			drawer.text(
 				bottom_in_area,
 				(0, drawer.layout.height(bottom_in_area) as i32 / 2),
-				font,
+				faces,
 				style,
 				&song.title,
 			)
@@ -397,11 +420,11 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 
 		let diff_area_center = diff_bg_area.center();
 
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			drawer.text(
 				jacket_with_border,
 				(diff_area_center.0 + x_offset, diff_area_center.1),
-				font,
+				faces,
 				crate::bitmap::TextStyle {
 					size: 25,
 					weight: Some(600),
@@ -432,14 +455,14 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		);
 		// }}}
 		// {{{ Display score text
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			drawer.text(
 				jacket_area,
 				(
 					score_bg_pos.0 + 5,
 					score_bg_pos.1 + score_bg.height() as i32 / 2,
 				),
-				font,
+				faces,
 				crate::bitmap::TextStyle {
 					size: 23,
 					weight: Some(800),
@@ -470,7 +493,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		);
 		// }}}
 		// {{{ Display status text
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			let status = play
 				.short_status(chart)
 				.ok_or_else(|| format!("Could not get status for score {}", play.score))?;
@@ -487,7 +510,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 			drawer.text(
 				jacket_area,
 				(center.0 + x_offset, center.1),
-				font,
+				faces,
 				crate::bitmap::TextStyle {
 					size: if status == 'M' { 30 } else { 36 },
 					weight: Some(if status == 'M' { 800 } else { 500 }),
@@ -516,14 +539,14 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		);
 		// }}}
 		// {{{ Display grade text
-		EXO_FONT.with_borrow_mut(|font| {
+		with_font(&EXO_FONT, |faces| {
 			let grade = play.score.grade();
 			let center = grade_bg_area.center();
 
 			drawer.text(
 				top_left_area,
 				(center.0, center.1),
-				font,
+				faces,
 				crate::bitmap::TextStyle {
 					size: 30,
 					weight: Some(650),
@@ -537,7 +560,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 		})?;
 		// }}}
 		// {{{ Display rating text
-		EXO_FONT.with_borrow_mut(|font| -> Result<(), Error> {
+		with_font(&EXO_FONT, |faces| -> Result<(), Error> {
 			let mut style = crate::bitmap::TextStyle {
 				size: 12,
 				weight: Some(600),
@@ -550,7 +573,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 			drawer.text(
 				top_left_area,
 				(top_left_center, 73),
-				font,
+				faces,
 				style,
 				"POTENTIAL",
 			)?;
@@ -561,7 +584,7 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 			drawer.text(
 				top_left_area,
 				(top_left_center, 94),
-				font,
+				faces,
 				style,
 				&format!("{:.2}", play.score.play_rating_f32(chart.chart_constant)),
 			)?;
@@ -582,11 +605,18 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 	}
 
 	let mut out_buffer = Vec::new();
-	let image: ImageBuffer<Rgb<u8>, _> =
-		ImageBuffer::from_raw(width, height, drawer.canvas.buffer).unwrap();
+	let mut image = DynamicImage::ImageRgb8(
+		ImageBuffer::from_raw(width, height, drawer.canvas.buffer.into_vec()).unwrap(),
+	);
+
+	debug_image_log(&image)?;
+
+	if image.height() > 4096 {
+		image = image.resize(4096, 4096, image::imageops::FilterType::Nearest);
+	}
 
 	let mut cursor = Cursor::new(&mut out_buffer);
-	image.write_to(&mut cursor, image::ImageFormat::Png)?;
+	image.write_to(&mut cursor, image::ImageFormat::WebP)?;
 
 	let reply = CreateReply::default()
 		.attachment(CreateAttachment::bytes(out_buffer, "b30.png"))
@@ -599,3 +629,18 @@ pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
 	Ok(())
 }
 // }}}
+// {{{ B30
+/// Show the 30 best scores
+#[poise::command(prefix_command, slash_command, user_cooldown = 30)]
+pub async fn b30(ctx: Context<'_>) -> Result<(), Error> {
+	let user = get_user!(&ctx);
+	best_plays(&ctx, &user, (5, 6), true).await
+}
+
+#[poise::command(prefix_command, slash_command, hide_in_help, global_cooldown = 5)]
+pub async fn bany(ctx: Context<'_>, width: u32, height: u32) -> Result<(), Error> {
+	let user = get_user!(&ctx);
+	assert_is_pookie!(ctx, user);
+	best_plays(&ctx, &user, (width, height), false).await
+}
+// }}}
diff --git a/src/commands/utils.rs b/src/commands/utils.rs
index 1343893..886358a 100644
--- a/src/commands/utils.rs
+++ b/src/commands/utils.rs
@@ -16,6 +16,17 @@ macro_rules! get_user {
 	}};
 }
 
+#[macro_export]
+macro_rules! assert_is_pookie {
+	($ctx:expr, $user:expr) => {{
+		if !$user.is_pookie {
+			$ctx.reply("This feature is reserved for my pookies. Sowwy :3")
+				.await?;
+			return Ok(());
+		}
+	}};
+}
+
 #[macro_export]
 macro_rules! reply_errors {
 	($ctx:expr, $value:expr) => {
diff --git a/src/context.rs b/src/context.rs
index bdbd546..d836d32 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -4,7 +4,7 @@ use sqlx::SqlitePool;
 
 use crate::{
 	arcaea::{chart::SongCache, jacket::JacketCache},
-	assets::{EXO_FONT, GEOSANS_FONT},
+	assets::{EXO_FONT, GEOSANS_FONT, KAZESAWA_BOLD_FONT, KAZESAWA_FONT},
 	recognition::{hyperglass::CharMeasurements, ui::UIMeasurements},
 };
 
@@ -24,6 +24,9 @@ pub struct UserContext {
 
 	pub geosans_measurements: CharMeasurements,
 	pub exo_measurements: CharMeasurements,
+	// TODO: do we really need both after I've fixed the bug in the ocr code?
+	pub kazesawa_measurements: CharMeasurements,
+	pub kazesawa_bold_measurements: CharMeasurements,
 }
 
 impl UserContext {
@@ -36,15 +39,16 @@ impl UserContext {
 		let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?;
 		let ui_measurements = UIMeasurements::read(&data_dir)?;
 
+		static WHITELIST: &str = "0123456789'abcdefghklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ";
+
 		let geosans_measurements = GEOSANS_FONT
-			.with_borrow_mut(|font| CharMeasurements::from_text(font, "0123456789'", None))?;
-		let exo_measurements = EXO_FONT.with_borrow_mut(|font| {
-			CharMeasurements::from_text(
-				font,
-				"0123456789'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
-				Some(700),
-			)
-		})?;
+			.with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, None))?;
+		let kazesawa_measurements = KAZESAWA_FONT
+			.with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, None))?;
+		let kazesawa_bold_measurements = KAZESAWA_BOLD_FONT
+			.with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, None))?;
+		let exo_measurements = EXO_FONT
+			.with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, Some(700)))?;
 
 		println!("Created user context");
 
@@ -56,6 +60,8 @@ impl UserContext {
 			ui_measurements,
 			geosans_measurements,
 			exo_measurements,
+			kazesawa_measurements,
+			kazesawa_bold_measurements,
 		})
 	}
 }
diff --git a/src/recognition/hyperglass.rs b/src/recognition/hyperglass.rs
index e6aefda..be8a1f8 100644
--- a/src/recognition/hyperglass.rs
+++ b/src/recognition/hyperglass.rs
@@ -234,24 +234,24 @@ impl CharMeasurements {
 	// {{{ Creation
 	pub fn from_text(face: &mut Face, string: &str, weight: Option<u32>) -> Result<Self, Error> {
 		// These are bad estimates lol
-		let char_w = 35;
-		let char_h = 60;
+		let style = TextStyle {
+			stroke: None,
+			drop_shadow: None,
+			align: (Align::Start, Align::Start),
+			size: 60,
+			color: Color::BLACK,
+			// TODO: do we want to use the weight hint for resilience?
+			weight,
+		};
+		let padding = (5, 5);
+		let planned = BitmapCanvas::plan_text_rendering(padding, &mut [face], style, &string)?;
 
-		let mut canvas = BitmapCanvas::new(10 + char_w * string.len() as u32, char_h + 10);
-		canvas.text(
-			(5, 5),
-			face,
-			TextStyle {
-				stroke: None,
-				drop_shadow: None,
-				align: (Align::Start, Align::Start),
-				size: char_h,
-				color: Color::BLACK,
-				// TODO: do we want to use the weight hint for resilience?
-				weight,
-			},
-			&string,
-		)?;
+		let mut canvas = BitmapCanvas::new(
+			(planned.0 .0) as u32 + planned.1.width + 2 * padding.0 as u32,
+			(planned.0 .1) as u32 + planned.1.height + 2 * padding.0 as u32,
+		);
+
+		canvas.text(padding, &mut [face], style, &string)?;
 		let buffer = ImageBuffer::from_raw(canvas.width, canvas.height(), canvas.buffer.to_vec())
 			.ok_or_else(|| "Failed to turn buffer into canvas")?;
 		let image = DynamicImage::ImageRgb8(buffer);
@@ -332,8 +332,8 @@ impl CharMeasurements {
 				.map(|(i, _, d)| (d.sqrt(), i))
 				.ok_or_else(|| "No chars in cache")?;
 
-			// println!("char '{}', distance {}", best_match.1, best_match.0);
-			if best_match.0 <= (IMAGE_VEC_DIM * 10) as f32 {
+			println!("char '{}', distance {}", best_match.1, best_match.0);
+			if best_match.0 <= 1.0 {
 				result.push(best_match.1);
 			}
 		}
diff --git a/src/recognition/recognize.rs b/src/recognition/recognize.rs
index ebee196..83020ef 100644
--- a/src/recognition/recognize.rs
+++ b/src/recognition/recognize.rs
@@ -134,14 +134,13 @@ impl ImageAnalyzer {
 		kind: ScoreKind,
 	) -> Result<Score, Error> {
 		let image = timed!("interp_crop_resize", {
-			self.interp_crop_resize(
+			self.interp_crop(
 				ctx,
 				image,
 				match kind {
 					ScoreKind::SongSelect => SongSelect(SongSelectRect::Score),
 					ScoreKind::ScoreScreen => ScoreScreen(ScoreScreenRect::Score),
 				},
-				(u32::MAX, 100),
 			)?
 		});
 
@@ -219,9 +218,11 @@ impl ImageAnalyzer {
 			ScoreScreen(ScoreScreenRect::Difficulty),
 		)?;
 
-		let text =
-			ctx.exo_measurements
-				.recognise(&image, "PASTPRESENTFUTUREETERNALBEYOND", None)?;
+		let text = ctx.kazesawa_bold_measurements.recognise(
+			&image,
+			"PASTPRESENTFUTUREETERNALBEYOND",
+			None,
+		)?;
 
 		let difficulty = Difficulty::DIFFICULTIES
 			.iter()
@@ -241,10 +242,10 @@ impl ImageAnalyzer {
 	) -> Result<ScoreKind, Error> {
 		let image = self.interp_crop(ctx, image, PlayKind)?;
 		let text = ctx
-			.exo_measurements
-			.recognise(&image, "resultselectasong", None)?;
+			.kazesawa_measurements
+			.recognise(&image, "ResultSelectaSong ", None)?;
 
-		let result = if edit_distance(&text, "Result") < edit_distance(&text, "Select a song") {
+		let result = if edit_distance(&text, "Result") < edit_distance(&text, "SelectaSong") {
 			ScoreKind::ScoreScreen
 		} else {
 			ScoreKind::SongSelect
@@ -344,7 +345,7 @@ impl ImageAnalyzer {
 		for i in 0..3 {
 			let image = self.interp_crop(ctx, image, ScoreScreen(KINDS[i]))?;
 			out[i] = ctx
-				.exo_measurements
+				.kazesawa_bold_measurements
 				.recognise(&image, "0123456789", Some(30))?
 				.parse()?;
 		}
diff --git a/src/user.rs b/src/user.rs
index a944528..c8d38b2 100644
--- a/src/user.rs
+++ b/src/user.rs
@@ -9,6 +9,7 @@ use crate::context::{Context, Error};
 pub struct User {
 	pub id: u32,
 	pub discord_id: String,
+	pub is_pookie: bool,
 }
 
 impl User {
@@ -22,6 +23,7 @@ impl User {
 		Ok(User {
 			id: user.id as u32,
 			discord_id: user.discord_id,
+			is_pookie: user.is_pookie,
 		})
 	}
 
@@ -33,6 +35,7 @@ impl User {
 		Ok(User {
 			id: user.id as u32,
 			discord_id: user.discord_id,
+			is_pookie: user.is_pookie,
 		})
 	}
 }