diff --git a/README.md b/README.md
index 34f7873..6916839 100644
--- a/README.md
+++ b/README.md
@@ -59,9 +59,9 @@ To render a 3D maze as a .STL file, do this:
 
 ```bash
 # Generate the OpenSCAD file
-lua main.lua maze3d_openscad -w 91 -h 91 -d 71 -f 91x91x71.scad
+lua main.lua maze3d_openscad -w 15 -h 15 -d 17 -f 15x15x17.scad
 # Convert the OpenSCAD file to an STL
-openscad -o 91x91x71.stl 91x91x71.scad
+openscad -o 15x15x17.stl 15x15x17.scad
 ```
 
 ![(Horizontal line)](https://starbeamrainbowlabs.com/blog/images/20200831-mazes/hr.png)
diff --git a/main.lua b/main.lua
index 6b52e61..2cecea9 100644
--- a/main.lua
+++ b/main.lua
@@ -1,3 +1,4 @@
+local a = require("./utils/ansi")
 local strings = require("./utils/strings")
 local debug = require("./utils/print_r")
 local calculations = require("./utils/calculations")
@@ -5,6 +6,12 @@ local maze2d = require("./maze")
 local maze3d = require("./maze3d")
 local openscad = require("./openscad")
 
+
+-- ANSI escape sequence helpers
+function a.header(str) return a.fblue(a.hicol(str)) end
+function a.item(str) return a.fyellow(str) end
+function a.value(str) return a.fgreen(str) end
+
 local settings = {
 	mode = "help",
 	extras = {},
@@ -65,29 +72,35 @@ end
 
 local function help()
 	print([[
-multimaze: Multidimensional maze generator
-	By Starbeamrainbowlabs
+]]..a.fgreen(a.hicol("multimaze: "))..a.fyellow(a.hicol("Multidimensional maze generator"))..[[
 
-Usage:
-	lua main.lua {mode} [{options}]
+	]]..a.locol("By Starbeamrainbowlabs")..[[
 
-Modes:
-	help				Displays this message
-	maze				Generates a 2d maze
-	maze3d				Generates a 3d maze
-	maze3d_openscad		Generates a 3d maze as an openscad file
 
-Options:
-	-w	--width {int}		Sets the width of the maze
-	-h	--height {int}		Sets the height of the maze
-	-d	--depth {int}		Sets the depth of the maze [3d only]
-	-s	--seed {string}		Sets the seed
-	-f	--filename {string}	Sets the output filename
-		--no-invert			Don't invert the output [openscad output only]
-		--scale {int}		Sets the scale of the output [openscad output only]
-		--path-length {int}	Sets the path length
-		--path-width {int}	Sets the path width
-		--path-depth {int}	Sets the path depth [3d only]
+]]..a.header("Usage:")..[[
+
+	lua main.lua ]]..a.item("{mode} [{options}]")..[[
+
+
+]]..a.header("Modes:")..[[
+
+	]]..a.item("help")..[[             Displays this message
+	]]..a.item("maze")..[[             Generates a 2d maze
+	]]..a.item("maze3d")..[[           Generates a 3d maze
+	]]..a.item("maze3d_openscad")..[[  Generates a 3d maze as an openscad file
+
+]]..a.header("Options:")..[[
+
+	]]..a.item("-w	--width")..a.value(" {int}")..[[		Sets the width of the maze
+	]]..a.item("-h	--height")..a.value(" {int}")..[[		Sets the height of the maze
+	]]..a.item("-d	--depth")..a.value(" {int}")..[[		Sets the depth of the maze [3d only]
+	]]..a.item("-s	--seed")..a.value(" {string}")..[[		Sets the seed
+	]]..a.item("-f	--filename")..a.value(" {string}")..[[	Sets the output filename
+		]]..a.item("--no-invert")..[[		Don't invert the output [openscad output only]
+		]]..a.item("--scale")..a.value(" {int}")..[[		Sets the scale of the output [openscad output only]
+		]]..a.item("--path-length")..a.value(" {int}")..[[	Sets the path length
+		]]..a.item("--path-width")..a.value(" {int}")..[[	Sets the path width
+		]]..a.item("--path-depth")..a.value(" {int}")..[[	Sets the path depth [3d only]
 ]])
 end
 
@@ -141,7 +154,7 @@ local function maze3d_openscad()
 		settings.filename,
 		openscad_settings
 	)
-	print("Maze written to "..settings.filename)
+	print("Maze written to "..a.fgreen(a.hicol(settings.filename)))
 end
 
 local mode_functions = {
@@ -153,7 +166,8 @@ local mode_functions = {
 
 local selection = mode_functions[settings.mode]
 if not selection then
-	print("Error: Unrecognised mode '"..settings.mode.."'")
+	print(a.fred(a.hicol("Error: Unrecognised mode '"..a.FBLE..settings.mode..a.FRED.."'")))
+	print()
 	selection = mode_functions["help"]
 end
 
diff --git a/utils/ansi.lua b/utils/ansi.lua
new file mode 100644
index 0000000..0eab04e
--- /dev/null
+++ b/utils/ansi.lua
@@ -0,0 +1,97 @@
+--[[
+VT100 ANSI escape sequences.
+Ported from the Lantern Build Engine, which si written in Bash.
+Other versions:
+ - Bash, in the Lantern Build Engine: https://gitlab.com/sbrl/lantern-build-engine/-/blob/master/lantern.sh
+ - Javascript: massCode/Ansi.mjs
+ - C♯: massCode/Ansi.cs
+
+Changelog:
+	6th September 2020:
+		Initial release - port complete!
+]]--
+
+
+local ansi
+if os.getenv("NO_COLOR") ~= nil then
+	ansi = {
+		RS		= "",
+		HC		= "",
+		UL		= "",
+		INV		= "",
+		LC		= "",
+		FBLK	= "",
+		FRED	= "",
+		FGRN	= "",
+		FYEL	= "",
+		FBLE	= "",
+		FMAG	= "",
+		FCYN	= "",
+		FWHT	= "",
+		BBLK	= "",
+		BRED	= "",
+		BGRN	= "",
+		BYEL	= "",
+		BBLE	= "",
+		BMAG	= "",
+		BCYN	= "",
+		BWHT	= "",
+	}
+else
+	ansi = {
+		RS		= "\27[0m",		-- reset
+		HC		= "\27[1m",		-- hicolor
+		UL		= "\27[4m",		-- underline
+		INV		= "\27[7m",		-- inverse background and foreground
+		LC		= "\27[2m",		-- locolor / dim
+		FBLK	= "\27[30m",	-- foreground black
+		FRED	= "\27[31m",	-- foreground red
+		FGRN	= "\27[32m",	-- foreground green
+		FYEL	= "\27[33m",	-- foreground yellow
+		FBLE	= "\27[34m",	-- foreground blue
+		FMAG	= "\27[35m",	-- foreground magenta
+		FCYN	= "\27[36m",	-- foreground cyan
+		FWHT	= "\27[37m",	-- foreground white
+		BBLK	= "\27[40m",	-- background black
+		BRED	= "\27[41m",	-- background red
+		BGRN	= "\27[42m",	-- background green
+		BYEL	= "\27[43m",	-- background yellow
+		BBLE	= "\27[44m",	-- background blue
+		BMAG	= "\27[45m",	-- background magenta
+		BCYN	= "\27[46m",	-- background cyan
+		BWHT	= "\27[47m",	-- background white
+		
+		
+		-- Invalid escape sequence, apparently - we'll need to look into how to do it properly
+		-- Perhaps knowing the hex code for the character we want would be helpful
+		-- URL_START			= '\e]8;;',
+		-- URL_DISPLAY_TEXT	= '\e\\',
+		-- URL_END				= '\e]8;;\e\\'
+	}
+end
+
+function ansi.wrap(str, start)
+	return start..str..ansi.RS
+end
+function ansi.hicol(str) return ansi.wrap(str, ansi.HC) end
+function ansi.locol(str) return ansi.wrap(str, ansi.LC) end
+function ansi.uline(str) return ansi.wrap(str, ansi.UL) end
+function ansi.invert(str) return ansi.wrap(str, ansi.INV) end
+function ansi.fblack(str) return ansi.wrap(str, ansi.FBLK) end
+function ansi.fred(str) return ansi.wrap(str, ansi.FRED) end
+function ansi.fgreen(str) return ansi.wrap(str, ansi.FGRN) end
+function ansi.fyellow(str) return ansi.wrap(str, ansi.FYEL) end
+function ansi.fblue(str) return ansi.wrap(str, ansi.FBLE) end
+function ansi.fmagenta(str) return ansi.wrap(str, ansi.FMAG) end
+function ansi.fcyan(str) return ansi.wrap(str, ansi.FCYN) end
+function ansi.fwhite(str) return ansi.wrap(str, ansi.FWHT) end
+function ansi.bblack(str) return ansi.wrap(str, ansi.FBLK) end
+function ansi.bred(str) return ansi.wrap(str, ansi.BRED) end
+function ansi.bgreen(str) return ansi.wrap(str, ansi.BGRN) end
+function ansi.byellow(str) return ansi.wrap(str, ansi.BYEL) end
+function ansi.bblue(str) return ansi.wrap(str, ansi.BBLE) end
+function ansi.bmagenta(str) return ansi.wrap(str, ansi.BMAG) end
+function ansi.bcyan(str) return ansi.wrap(str, ansi.BCYN) end
+function ansi.bwhite(str) return ansi.wrap(str, ansi.BWHT) end
+
+return ansi
