diff --git a/xtrn/minesweeper/graphics.ppm b/xtrn/minesweeper/graphics.ppm
new file mode 100644
index 0000000000000000000000000000000000000000..a723d98ffb831ed78d38b8d6eb9cc49a28d8d874
Binary files /dev/null and b/xtrn/minesweeper/graphics.ppm differ
diff --git a/xtrn/minesweeper/minesweeper.js b/xtrn/minesweeper/minesweeper.js
index a3dd700f79a9d6c63cf3adf016d35111e82c6ad5..2d07bbbae799f30d4b2eead36d0eaa4387f615ce 100644
--- a/xtrn/minesweeper/minesweeper.js
+++ b/xtrn/minesweeper/minesweeper.js
@@ -6,7 +6,7 @@
 
 const title = "Synchronet Minesweeper";
 const ini_section = "minesweeper";
-const REVISION = "$Revision: 2.15 $".split(' ')[1];
+const REVISION = "$Revision: 2.16 $".split(' ')[1];
 const author = "Digital Man";
 const header_height = 4;
 const winners_list = js.exec_dir + "winners.jsonl";
@@ -33,6 +33,26 @@ const winner_subject = "Winner";
 const highscores_subject = "High Scores";
 const tear_line = "\r\n--- " + js.exec_file + " " + REVISION + "\r\n";
 const selectors = ["()", "[]", "<>", "{}", "--", "  "];
+const gfile = "graphics.ppm";
+const mfile = "selmask.pbm";
+const image = {
+	'selected': 208
+};
+image[attr_count + '1'] = 0;
+image[attr_count + '2'] = 16;
+image[attr_count + '3'] = 32;
+image[attr_count + '4'] = 48;
+image[attr_count + '5'] = 64;
+image[attr_count + '6'] = 80;
+image[attr_count + '7'] = 96;
+image[attr_count + '8'] = 112;
+image[char_empty] = 128;
+image[char_covered] = 240;
+image[char_mine] = 144;
+image[char_flag] = 160;
+image[char_unsure] = 176;
+image[char_badflag] = 192;
+image[char_detonated_mine] = 224;
 
 require("sbbsdefs.js", "K_NONE");
 require("mouse_getkey.js", "mouse_getkey");
@@ -87,6 +107,7 @@ var win_rank = false;
 var view_details = false;
 var cell_width;	// either 3 or 2
 var best = null;
+var graph = false;
 
 log(LOG_DEBUG, title + " options: " + JSON.stringify(options));
 
@@ -544,7 +565,18 @@ function draw_cell(x, y)
 		left = "\x01n\x01h" + selectors[selector%selectors.length][0];
 		right = "\x01n\x01h" + selectors[selector%selectors.length][1];
 	}
-	console.print(left + val + right);
+	if (graph) {
+		const margin = Math.floor((console.screen_columns - (game.width * cell_width)) / 2);
+		var xpos = (x * cell_width + margin) * 8;
+		var ypos = (header_height + y + top) * 16;
+		console.write('\x1b_SyncTERM:P;Paste;SX='+image[val]+';SY=0;SW=16;SH=16;DX='+xpos+';DY='+ypos+';B=0\x1b\\');
+		if (selected.x == x && selected.y == y) {
+			console.write('\x1b_SyncTERM:P;Paste;SX='+image['selected']+';SY=0;SW=16;SH=16;DX='+xpos+';DY='+ypos+';MBUF;B=0\x1b\\');
+		}
+	}
+	else {
+		console.print(left + val + right);
+	}
 }
 
 // Return total number of surrounding flags
@@ -711,7 +743,9 @@ function draw_board(full)
 			draw_border();
 		for(var x = 0; x < game.width; x++) {
 			if(full || board[y][x].changed !== false) {
-				if(console.term_supports(USER_ANSI))
+				if (graph) {
+				}
+				else if(console.term_supports(USER_ANSI))
 					console.gotoxy((x * cell_width) + margin + 1, header_height + y + top + 1);
 				else {
 					console.creturn();
@@ -743,7 +777,9 @@ function draw_board(full)
 		console.attributes = LIGHTGRAY;
 	}
 	if(redraw_selection) { // We need to draw/redraw the selected cell last in this case
-		if(console.term_supports(USER_ANSI))
+		if (graph) {
+		}
+		else if(console.term_supports(USER_ANSI))
 			console.gotoxy(margin + (selected.x * cell_width) + 1, header_height + selected.y + top + 1);
 		else {
 			console.up(height - (selected.y + 1));
@@ -885,7 +921,7 @@ function init_game(difficulty)
 	game.width = game.height;
 	game.height = Math.min(game.height, console.screen_rows - header_height);
 	game.width += game.width - game.height;
-	if(game.width > 10 && (game.width * 3) + 2 > (console.screen_columns - 20))
+	if(graph || (game.width > 10 && (game.width * 3) + 2 > (console.screen_columns - 20)))
 		cell_width = 2;
 	else
 		cell_width = 3;
@@ -946,8 +982,181 @@ function screen_to_board(mouse)
 	return true;
 }
 
+function read_apc()
+{
+	var ret = '';
+	var ch;
+	var state = 0;
+
+	for(;;) {
+		ch = console.getbyte(1000);
+		if (ch === null)
+			return undefined;
+		switch(state) {
+			case 0:
+				if (ch == 0x1b) {
+					state++;
+					break;
+				}
+				break;
+			case 1:
+				if (ch == 95) {
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 2:
+				if (ch == 0x1b) {
+					state++;
+					break;
+				}
+				ret += ascii(ch);
+				break;
+			case 3:
+				if (ch == 92) {
+					return ret;
+				}
+				return undefined;
+		}
+	}
+	return undefined;
+}
+
+
+function pixel_capability()
+{
+	var ret = false;
+	var ch;
+	var state = 0;
+	var optval = 0;
+
+	for(;;) {
+		ch = console.getbyte();
+		switch(state) {
+			case 0:
+				if (ch == 0x1b) { // ESC
+					state++;
+					break;
+				}
+				break;
+			case 1:
+				if (ch == 91) {   // [
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 2:
+				if (ch == 60) {   // <
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 3:
+				if (ch == 48) {   // 0
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 4:
+				if (ch == 59) {   // ;
+					state++;
+					break;
+				}
+				state = 0;
+				break;
+			case 5:
+				if (ch >= ascii('0') && ch <= ascii('9')) {
+					optval = optval * 10 + (ch - ascii('0'));
+					break;
+				}
+				else if(ch == 59) {
+					if (optval === 3)
+						ret = true;
+					optval = 0;
+					break;
+				}
+				else if (ch === 99) { // c
+					if (optval === 3)
+						ret = true;
+					return ret;
+				}
+				state = 0;
+				break;
+		}
+	}
+	return ret;
+}
+
+function detect_graphics()
+{
+	var tmpckpt;
+	var f;
+	var md5;
+	var lst;
+	var m;
+	var b;
+
+	// Detect PPM graphics and load the cache
+	graph = false;
+	tmpckpt = console.ctrlkey_passthru;
+	if (console.cterm_version >= 1316) {
+		console.ctrlkey_passthru = '+[';
+		console.write('\x1b[<c');
+		graph = pixel_capability();
+	}
+
+	if (graph) {
+		// Load up cache...
+		f = new File(js.exec_dir+'/'+gfile);
+		if (f.open('rb')) {
+			md5 = f.md5_hex;
+			console.write('\x1b_SyncTERM:C;L;minesweeper/'+gfile+'\x1b\\');
+			lst = read_apc();
+			m = lst.match(/\ngraphics.ppm\t([0-9a-f]+)\n/);
+			if (m == null || m[1] !== md5) {
+				// Store in cache...
+				console.write('\x1b_SyncTERM:C;S;minesweeper/'+gfile+';');
+				f.base64 = true;
+				console.write(f.read());
+				console.write('\x1b\\');
+			}
+			f.close();
+			console.write('\x1b_SyncTERM:C;LoadPPM;B=0;minesweeper/'+gfile+'\x1b\\');
+			f = new File(js.exec_dir+'/'+mfile);
+			if (f.open('rb')) {
+				md5 = f.md5_hex;
+				console.write('\x1b_SyncTERM:C;L;minesweeper/'+mfile+'\x1b\\');
+				lst = read_apc();
+				m = lst.match(/\nselmask.pbm\t([0-9a-f]+)\n/);
+				if (m == null || m[1] !== md5) {
+					// Store in cache...
+					console.write('\x1b_SyncTERM:C;S;minesweeper/'+mfile+';');
+					f.base64 = true;
+					console.write(f.read());
+					console.write('\x1b\\');
+				}
+				f.close();
+				console.write('\x1b_SyncTERM:C;LoadPBM;minesweeper/'+mfile+'\x1b\\');
+			}
+			else {
+				graph = false;
+			}
+		}
+		else {
+			graph = false;
+		}
+	}
+	console.ctrlkey_passthru = tmpckpt;
+}
+
 function play()
 {
+	if (graph)
+		detect_graphics();
 	console.clear();
 	var start = Date.now();
 	show_image(welcome_image, /* fx: */false, /* delay: */0);
@@ -1253,6 +1462,9 @@ try {
 
 		js.on_exit("console.attributes = LIGHTGRAY");
 	}
+	if(argv.indexOf("graphics") >= 0) {
+		graph = true;
+	}
 	if(argv.indexOf("winners") >= 0) {
 		if(!isNaN(numval) && numval > 0)
 			options.winners = numval;
diff --git a/xtrn/minesweeper/selmask.pbm b/xtrn/minesweeper/selmask.pbm
new file mode 100644
index 0000000000000000000000000000000000000000..9e9cc16d5b2d1463b75404add99d9c48a5c3fb89
--- /dev/null
+++ b/xtrn/minesweeper/selmask.pbm
@@ -0,0 +1,4 @@
+P4
+# Created by GIMP version 2.10.36 PNM plug-in
+16 16
+��������������������
\ No newline at end of file