./readme.txt
This page is meant to display code in an aesthetic way. 

Each language has its own color scheme, and features syntax hilighting.
The color scheme is controlled by the css, 
and the syntax hilighting is done via a python parser program that puts in html tags. 
With this program I can quickly generate new pages out of any code that I have. 

The full code for this page is shown below, some parts of it may be a little messy,
but overall, it works. There is also code for the terminal interface to this page, 
which is a very good example of spaghetti code!

Slowly read the code and try to see what it does on the actual webpage you're on.
Sorry if it's complicated! If you find any mistakes in the code please let me know!
./terminal.html
<!DOCTYPE HTML>

<html>
	<head>
		<meta charset="UTF-8">   
		<title>Mobius Terminal</title>
		<style>
			
			body { 	
				background-color: #000; 
				cursor: crosshair;
				overflow: hidden;
			}

			canvas {
				border-style: solid;
				border-color:	#369;
				border-width: 2px;
				display: block;
				margin: auto;
				padding-left: 0;
				padding-right: 0;
				cursor: none;
			}

		</style>

	</head>
	<body>
		<canvas id = "terminal"></canvas>
		<script src="terminal.js"></script>
	</body>
</html>
./visual.html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<title>Archive</title>
		<meta name="viewport" 
			content="width=device-width,initial-scale=1">
		<meta http-equiv="author"
			  content="Boris Volkov" />
		<link rel="stylesheet" 
			href="style.css">
	</head>

	<body>
		<div class="top_menu">
			<a href="#exe" class="first">
				exe
			</a>
			<a href="#web">
				web
			</a>
			<a href="#code">
				code
			</a>
			<a href="#text" class="last">
				txt
			</a>
		</div>

		<wedge id="exe">h</wedge>
		<div class="bookmark">
			executables
		</div>
		<div class="link_box">
			<a href="./exe/game/formatted.html">2d_engine</a>
			<a href="./exe/quest/formatted.html">calculator_quest</a>
			<a href="./exe/graphics/formatted.html">rgb_colors</a>
			<a href="./exe/lines/formatted.html">drawing_drills</a>
			<a href="./exe/fifteen/formatted.html">fifteen_puzzle</a>
			<a href="./exe/gol/formatted.html">game_of_life</a>
			<a href="./exe/mandelbrot/formatted.html">mandelbrot_set</a>
			<a href="./terminal.html">mobius_terminal</a>
			<a href="./exe/sixteen/formatted.html">sixteen_puzzle</a>
			<a href="./exe/speed_typer/formatted.html">speed_typer</a>
			<a href="./exe/princess/formatted.html">the_princess's_test</a>
			<a href="./exe/territory/formatted.html">territories</a>
			<a href="./exe/controller/formatted.html">mobile_controller</a>

		</div>

		<wedge id="web">h</wedge>
		<div class="bookmark">
			web_tutorials
		</div>

		<div class="image_box">	
			<a href="./web/basic/formatted.html"/>
					<img src="./web/basic/screenshot.png">
					<p>to_begin_with</p>
			</a>
			<a href="./web/nesting/formatted.html"/>
					<img src="./web/nesting/screenshot.png">
					<p>div_nesting</p>
			</a>
			<a href="./web/divs/formatted.html"/>
					<img src="./web/divs/screenshot.png">
					<p>flexboxes</p>
			</a>
			<a href="./formatted.html"/>
					<img src="./screenshot.png">
					<p>this</p>
			</a>
			<a href="./web/button/formatted.html"/>
					<img src="./web/button/screenshot.png">
					<p>button</p>
			</a>
			<a href="./web/sizing/formatted.html"/>
					<img src="./web/sizing/screenshot.png">
					<p>sizing</p>
			</a>
		</div>



		<wedge id="code">h</wedge>
		<div class="bookmark">
			code
		</div>
		<div class="link_box">
			<a href="./code/twenty_four/formatted.html">24_solver</a>
			<a href="./code/js_audio/formatted.html">audio_in_javascript</a>
			<a href="./code/chaos/formatted.html">chaos_graph</a>
			<a href="./code/colors/formatted.html">color_printing_in_python</a>
			<a href="./code/connect_four/formatted.html">connect_four</a>
			<a href="./pre_terminal/formatted.html">i/o_terminal_in_javascript</a>
			<a href="./code/multiplication/formatted.html">math_drills</a>
			<a href="./code/stocks/formatted.html">stock_trader_sim</a>
			<a href="./code/turtles/formatted.html">turtles</a>
		</div>

		<wedge id="text">h</wedge>
		<div class="bookmark">
			text
		</div>
		<div class="link_box">
			<a href="./txt/tools/formatted.html">where_to_write_code</a>
		</div>
		<!-- money printer goes -->
		<br/><br/><br/><br/><br/><br/><br/><br/>
		<br/><br/><br/><br/><br/><br/><br/><br/>
		<br/><br/><br/><br/><br/><br/><br/><br/>
		<br/><br/><br/><br/><br/><br/><br/><br/>
	</body>
</html> 
./style.css
* {
	color: coral;
	font-family : Courier;
	font-weight : 600;
	box-sizing : border-box; /* to correct sizes */
}

body {
	background-color : #333942;
	background: linear-gradient(180deg, rgb(21,27,36), rgb(51,57,66))
}

pre {
	line-height : 1.5em;
	letter-spacing : 2px;
	white-space: pre-wrap;  /* to wrap text */
	word-break: break-all;	/* so words don't get broken in wrap */
	cursor: text;
}

div, pre {  				/* centering, margins, rounding */
	margin: auto;
	padding: 10px;
	padding-left: 40px;
	width: 90vw;   			/* 90% of viewport width */
	border-radius: 16px;
	border-top-left-radius: 40px;
	tab-size: 4;			/* otherwise its 8 spaces*/
	-moz-tab-size: 4;
}

div {
	padding: 30px;
}

div.image {
	background-color : #011;
	display: flex;			/* to be able to center it */
	justify-content : center;
	align-items: center;	
}

a {
	text-decoration: none;
	cursor: default;
}

.terminal_button {
	margin-top : 50px!important;
	display: flex;
	flex-direction : column;
	margin: auto;
	height: 5vw;
	width: 5vw;
	padding: 0;
	text-align:center;
	border-radius : 50%;
	border-bottom-right-radius:0;
	border-bottom-left-radius:0;
	background-color: #445;
}

.terminal_button a {
	background-color: coral;
	padding: 0;
	display: block;
	margin: auto;
	height :3vw;
	width : 3vw;
	border-radius: 50%;
}

.terminal_button a:hover {
	background-color: #358;
}

.terminal_button .inner_button {
	display: inline;
	vertical-align : middle;
	padding: 0;
	margin: 0;
}

.top_menu {
	background-color: #445;
	opacity : 0.9;
	position : sticky; 		/* stick to top of screen */
	top : 0;
	width : 95vw;
	border-radius:10px;
	padding: 0;
	margin: auto;
	display: flex;
	flex-wrap: wrap-reverse;
	align-content : stretch;
	border-bottom-left-radius: 30px;
	border-bottom-right-radius: 30px;
}

.top_menu a {
	text-align : center;
	background-color: #445;
	flex : 1;
	color: coral;
	text-decoration: none;
	padding: 15px 20px;
}

.top_menu a.first {
	border-bottom-left-radius: 30px;
}

.top_menu a.last {
	border-bottom-right-radius: 30px;
}

.top_menu a:hover {
	background: #358;
}


.screenshot {
	max-height: 50vh;
	max-width: 85vw;
	object-fit: contain;
}

div.bookmark { 				/* the little tab above all the boxes */
	text-align: center;
	padding: 5px;
	width: 36vw;
	background-color : #345;
	margin-bottom : 0;
	border-radius: 0;
	margin-top: 70px;
	border-top-right-radius : 30px;
	border-top-left-radius : 30px;
	word-break: break-all;
}

.link_box {
	background: #345;
	margin: 0 auto 30px; 	/* top sides bottom */
}

.link_box a {
	display:block;			/* to take up full row */
	background: #456;
	padding: 10px;
	padding-left: 30px;
	margin: 5px;
	border-top-left-radius: 20px;
	border-top-right-radius: 10px;
	word-wrap: wrap;
	word-break: break-all;
}

.link_box a:hover {
	background: #358;
}

.image_box {
	background: #345;
	display: flex;
	flex-wrap: wrap;
	justify-content: space-around;
}

.image_box a {
	background: #456;
	max-width: 30%;
	border-top-left-radius: 20px;
	border-top-right-radius: 10px;
	padding: 20px;
	margin: 10px;
	display: flex;
	flex-direction : column;
	align-content : center;
	justify-content: center;
}

.image_box a p {
	background: inherit;
	text-align: center;
	word-wrap: wrap;
	word-break: break-all;
}

.image_box a:hover {
	background: #358;
}

.image_box img {
	max-width: 90%;
	object-fit: cover;
	padding:0px;
	margin-left: auto;
	margin-right: auto;
	border: 1px coral solid;
}

pre.notes {
	background-color: #357;
	color: white;
	word-break: keep-all;
}

.notes sc {
	color : coral;
}

pre.html {
	background-color: #375;
	color: #000;
}

.html kw {
	color: yellow;
}

.html sc {
	color: yellow;
}





pre.css {
	background-color: #366;
	color: #eee;	
}

.css sc {
	color : #3ff;
}



pre.py {
	background-color: rgb(15,65,75);
	color: #ddf;
}

.py sc {
	color: #bb2;
}

.py kw {
	color: coral;
}



pre.js {
	background-color: rgb(140,110,110);
	color: #321;
}

.js sc {
	color: #246;
}

.js kw {
	color: #dba;
}



sc, kw { /* special characters and key words */
	background-color:inherit;
	font-weight: bold;
}



comment {
	background-color: inherit;
}


css {
	background-color: inherit;
	color: white;
}


string {
	background-color: inherit;
	color: pink;
}

comment.css {
	color: #f64;
}

comment.js {
	color: #f95;
}

comment.html {
	color: #242;
}

comment.py {
	color: #987;
}

comment, dbl_quote, sgl_quote {
	font-weight : 100;
}

pre.py dbl_quote {
	color: #1b5;
}

pre.py sgl_quote {
	color: #1a5;
}

pre.js dbl_quote {
	color: #bca;
}

pre.js sgl_quote {
	color: #bca;
}

comment sc, comment kw, sgl_quote sc, sgl_quote kw, dbl_quote sc, dbl_quote kw{
	color : inherit!important;
	font-weight : inherit!important;
}



comment sc, comment kw { /* comment overrides special symbols */
	color: inherit!important;
}

wedge { /*for correct spacing when linking within page */
	height: 80px;
	padding-top 10px;
	visibility:hidden;
}


./terminal.js
// A fully custom terminal interface with some very strange capabilities.

/*
 * TODO
 * keep a stack of commands and run their inveres
 * make function-inverse map
 * have it play back as an animation.
 **/

//----------------------------------------------------------------Initialization of canvas and grid
var canvas = document.getElementById('terminal');
var context = canvas.getContext('2d');
const urlParams = new URLSearchParams(window.location.search);
var num_rows = parseInt( urlParams.get('rows'));
if (isNaN(num_rows)) { num_rows = 32; }
var num_cols = parseInt( urlParams.get('cols'));
if (isNaN(num_cols)) { num_cols = 48; }
// next stuff prevents touch scrolling on mobile/ipad
function preventDefault(e){
    e.preventDefault();
}
document.body.addEventListener('touchmove', preventDefault, { passive: false });
//-------------------------------------------------------stylus drawing listeners/handlers
var drawing_mode = true;
var pen_down = false;
var bb; // bounding box, to adjust x,y coordinates within the terminal.

canvas.onpointerdown = (event) => {
	if (drawing_mode){
		pen_down = true;
		bb = canvas.getBoundingClientRect(); 
		let x = (event.clientX-bb.left)*(canvas.width/bb.width);
		let y = (event.clientY-bb.top)*(canvas.height/bb.height);
		context.moveTo(x,y);
		context.strokeStyle = "#369";
	}
}

canvas.onpointermove = (event) => {
	if (pen_down){
			let x = (event.clientX-bb.left)*(canvas.width/bb.width);
			let y = (event.clientY-bb.top)*(canvas.height/bb.height);
			context.lineTo(x,y);
			context.stroke();
			context.moveTo(x,y);
	}
}

canvas.onpointerup = () => {
		context.closePath();
		pen_down = false;
}
//---------------------------------------------Text matrix and color matrix with mapping

// backward and forward scroll stacks
var term_memory = [];
var forward_memory = [];

// backups for when going to text mode
var backup_history = [];
var backup_future = [];

var text_matrix = Array(num_rows); 
function initialize_text_matrix(){
	for (let i = 0; i < num_rows; i++)
		text_matrix[i] = Array(num_cols).fill(-1);
}

var grid = Array(num_rows);
function initialize_grid(){
	for (let i = 0; i < num_rows; i++){
		grid[i] = Array(num_cols);
		for (let j = 0; j < num_cols; j++){
			grid[i][j] = [i,j];
		}
	}	
}

// for mapping from the grid to the text_matrix:
function text_map(i,j) { 
	return text_matrix[grid[i][j][0]][grid[i][j][1]];
}

//--------------------------------------------Printing and cursor functions:

// to fix negative cursor indices...
Number.prototype.mod = function(n) {
	return ((this%n)+n)%n;
};

function cursor_up(){
	if (cursor_row == 0 && term_memory.length)
		old_line();
	else cursor_row = Math.max(cursor_row - 1, 0);
}
function cursor_down(){
	if (cursor_row == (num_rows - 1) && forward_memory.length)
		back_from_the_future();
	else cursor_row = Math.min(cursor_row + 1, num_rows - 1);
}
function cursor_right(){
	cursor_col = (cursor_col+1)%num_cols;
}
function cursor_left(){
	cursor_col = (cursor_col-1).mod(num_cols);
}

function back_space(){
	if (cursor_col > 0)
		text_matrix[cursor_row][--cursor_col] = -1;
}

function delete_row(){
	//for (let i = 0; i < num_cols; i++)
	//	text_matrix[cursor_row][i] = -1;
	fit_canvas();
}

function tab(){
	cursor_col = (cursor_col+4)%num_cols;
}

function clear(){
	term_memory = [];
	forward_memory = [];
	initialize_text_matrix();
	cursor_row = 0;
	cursor_col = 0;
}

var cursor_col = 0;
var cursor_row = 0;
var terminal_mode = true;
var text_mode = false;

function new_line() {
	term_memory.push(text_matrix.shift());
	text_matrix.push(Array(num_cols).fill(-1));
}

function old_line() {
	if (term_memory.length)
		text_matrix.unshift(term_memory.pop());
	forward_memory.push(text_matrix.pop());
}

function back_from_the_future() {
	if (forward_memory.length){
		term_memory.push(text_matrix.shift());
		text_matrix.push(forward_memory.pop());
	}
}

function write(key) {
	text_matrix[cursor_row][cursor_col] = key;
	if (cursor_col == num_cols - 1){
		cursor_col = 0;
		if ((cursor_row + 1) == num_rows)
			new_line();
		else cursor_row = (cursor_row+1)%num_rows;
	} else {
		cursor_col += 1;
	}
}

function echo(buffer) {
	for (let i = 0; i < buffer.length; i++)
		write(buffer[i]);
	if (cursor_col > 0){ // linebreak after printing
		if ((cursor_row + 1) == num_rows)
			new_line();
		else cursor_row++;
		cursor_col = 0;
	}
}


var ps1 = "$";
function prompt(){
	if (ps1){
		write(ps1); 
		cursor_col++;
	}
}

const invalid_usage = "● Invalid usage ●";

function help(title, list){ //TODO does not handle long lines
	title = "::" + title + "::";
	echo(title + " ".repeat(num_cols-title.length));
	echo('●' + '―'.repeat(num_cols-2) + "●");
	for (let i = 0; i < list.length; i++)
		echo("| " + list[i] + " ".repeat(num_cols-list[i].length - 3) + "|");
	echo('●' + '―'.repeat(num_cols-2) + "●");
}


initialize_text_matrix();
initialize_grid();

function ls(){
	help("File Listing", Object.keys(file_list));
}

function cat(file_name) {
	if (file_list.hasOwnProperty(file_name))
		help(file_name, file_list[file_name]);
	else {
		echo(invalid_usage);
		echo("::cat must be called on a file");
		echo("::example usage: cat about");
		echo("::use ls to see a file listing");
		//echo("::use help cat to learn more");
	}
}
	
//---------------------------Everything related to color manipulation in phase mode: 
function Color(name){
	this.name = name;
	this.amp = 0;
	this.freq = 6.283/num_rows;
	this.phase = 0;
	this.center = 0;
	this.toString = function () {
		return [ this.name   , 	'Mag:', this.amp, '|',
			'Frq:', Math.round(num_rows*this.freq/6.28*100)/100, '|',
			'Pha:', this.phase,    '|', 
			'Cen:', this.center].join(' ');
	};

	this.sin = function (x) {
		return Math.round(Math.sin(this.freq*(x+this.phase))*this.amp+this.center);
	};
};

var gradient = {
	red 		: new Color('[Red]'),
	grn 		: new Color('[Grn]'),
	blu 		: new Color('[Blu]'),
	rgb_codes	: new Array(num_rows),

	generate_codes  : function() {
		for (var i = 0; i < num_rows; ++i){
			this.rgb_codes[i] = ('rgb('+this.red.sin(i)+','+
				this.grn.sin(i)+','+
				this.blu.sin(i)+')');}
	}
}

function waves(){
	echo(gradient.red.toString().replace(/\s/g,''));
	echo(gradient.grn.toString().replace(/\s/g,''));
	echo(gradient.blu.toString().replace(/\s/g,''));
}




function invert_color(string){
	return;
}

function reset_colors() {		
		gradient.red 		= new Color('[Red]'),
		gradient.grn 		= new Color('[Grn]'),
		gradient.blu 		= new Color('[Blu]'),
		gradient.rgb_codes	= new Array(num_rows),

		gradient.generate_codes  = function() {
			for (var i = 0; i < num_rows; ++i){
				this.rgb_codes[i] = ('rgb('+this.red.sin(i)+','+
					this.grn.sin(i)+','+
					this.blu.sin(i)+')');}
				}

		gradient.generate_codes();
}

function set_text_color(rgb) {
	if (isHexColor(rgb))
		text_color = "#" + rgb;
	else {
		echo(invalid_usage);
		echo("::txtcolor must be called with an RGB hex code");
		echo("::example usage: txtcolor fff or txtcolor 3366ff");
	}
}

function isHexColor (hex) {
  return typeof hex === 'string'
      && (hex.length === 3 || hex.length === 6)
      && !isNaN(Number('0x' + hex))
}

function rgb(){ // to be depracated ?
	alert(gradient.rgb_codes);
} 
reset_colors();
//--------------------------------------------------------------Drawing to canvas.
var display = false;
var text_hidden = false;
var cursor_color = '#47a';
var text_color = '#69c';
function grid_to_canvas(){
	var ver_div = canvas.width/num_cols;
	var hor_div = canvas.height/num_rows;
	for (var i = 0; i < num_rows; i++){
		for (var j = 0; j < num_cols; j++){
			let x = j * hor_div;
			let y = i * ver_div;
			let color = gradient.rgb_codes[grid[i][j][0]]; // color element
			context.fillStyle = color;
			if (text_hidden){ // fill background no matter what
				context.fillRect(x , y, hor_div, ver_div);	
			} else { // otherwise fill background and text on top of it
				if (terminal_mode && cursor_row == grid[i][j][0] && cursor_col == grid[i][j][1])
					context.fillStyle = cursor_color;

				context.fillRect(x , y, hor_div, ver_div);
				let character = text_map(i,j);
				if (character != -1){ // need to draw char.
					context.fillStyle = text_color;
					if ("●◷◵◴◶→←―|↑↓_".includes(character)) // orange special chars
						context.fillStyle = '#f60';
					if (":$₽".includes(character))   // blue special chars
						context.fillStyle = "#369";
					if ("[](){}><+=-*/".includes(character))  // yellow special chars
						context.fillStyle = "#990";
					context.fillText(character, Math.round(x+hor_div/4), Math.round(y+ver_div/5));
				}
			}
		}
	}

	//context.rotate(0.1);
	//// investigate this! this is how you get rotated characters bro!
	//context.translate(x,y)
	//context.transform(a,b,c,d,e,f) most advanced transformations
	if (display){ // this is if color information display is turned on
		context.fillStyle = "#000";
		context.fillRect(Math.round(canvas.width/5) ,ver_div/2, Math.round(3*canvas.width/5), ver_div*4);
		context.textBaseline = "top";
		context.textAlign = "center";
		context.fillStyle = "#a33";
		context.fillText(gradient.red.toString(), canvas.width/2,   ver_div);
		context.fillStyle = "#3a3";
		context.fillText(gradient.grn.toString(), canvas.width/2, 2*ver_div);
		context.fillStyle = "#36f";
		context.fillText(gradient.blu.toString(), canvas.width/2, 3*ver_div);
		context.textAlign = "start";
	}
}	
//-------------------------------------------key event listener & key -> function maps

var keys_down = {
	'r': false, 	'g': false,  	'b': false, 	'p': false,
	'f': false,		'm': false, 	'c': false, 	'e': false,
	'h': false,
};
const key_function_map = {
	'i': torus_up,		'k': torus_down,	'j': torus_left,
	'l': torus_right,	'w': mobius_up,		's': mobius_down,
	'a': mobius_left,	'd': mobius_right,      't': transpose,
	'.': () => { display ^= true; }, 
	'`': () => { alert(gradient.rgb_codes); },
	'h': () => { text_hidden ^= true; },
	'Escape': () => { 
		if (!terminal_mode && !text_mode){ // means phase_mode 
			terminal_mode = true; 
			text_hidden = false; 
			display = false;
		}
	},
	'=' :  reset_colors,

	'ArrowUp' : () => 	{if (keys_down['r']){ 
					if (keys_down['f'])
						gradient.red.freq *= 1.01;
					if (keys_down['p'])
						gradient.red.phase += 1;
					if (keys_down['m'])
						gradient.red.amp += 5;
					if (keys_down['c'])
						gradient.red.center += 5;
				}
				if (keys_down['g']){ 
					if (keys_down['f'])
						gradient.grn.freq *= 1.01;
					if (keys_down['p'])
						gradient.grn.phase += 1;
					if (keys_down['m'])
						gradient.grn.amp += 5;
					if (keys_down['c'])
						gradient.grn.center += 5;
				}
				if (keys_down['b']){ 
					if (keys_down['f'])
						gradient.blu.freq *= 1.01;
					if (keys_down['p'])
						gradient.blu.phase += 1;
					if (keys_down['m'])
						gradient.blu.amp += 5;
					if (keys_down['c'])
						gradient.blu.center += 5;
				}
			},

	'ArrowDown' : () => 	{if (keys_down['r']){ 
					if (keys_down['f'])
						gradient.red.freq /= 1.01;
					if (keys_down['p'])
						gradient.red.phase -= 1;
					if (keys_down['m'])
						gradient.red.amp -= 5;
					if (keys_down['c'])
						gradient.red.center -= 5;
				}
					if (keys_down['g']){ 
						if (keys_down['f'])
							gradient.grn.freq /= 1.01;
						if (keys_down['p'])
							gradient.grn.phase -= 1;
						if (keys_down['m'])
							gradient.grn.amp -= 5;
						if (keys_down['c'])
							gradient.grn.center -= 5;
					}
					if (keys_down['b']){ 
						if (keys_down['f'])
							gradient.blu.freq /= 1.01;
						if (keys_down['p'])
							gradient.blu.phase -= 1;
						if (keys_down['m'])
							gradient.blu.amp -= 5;
						if (keys_down['c'])
							gradient.blu.center -= 5;
					}
				}
};
//---------------------------------------------------------------Commands and maps
command_list = ["about", "cat", "clear", "codes", "echo", 
				"help", "ls", "programs", "refresh", "rgb",
				"txtcolor", "visual",
				"____________________________________________",
				"◶ SPECIAL MODES    (press [esc] to return) ◵",
				"text",
				"phase"
				];
// TODO pull from properties of command map rather than actually writing this list

program_list = ["quest", "princess", "fifteen", "sixteen", "game", "territory", "life", "mandelbrot"]

// put these in a function map
function execute_command(buffer) {
	let command = buffer.join("");
	if (command == "clear") 		clear();
	else if (command.startsWith("echo")) 	echo(buffer.slice(5));
	else if (command.startsWith("txtcolor"))	set_text_color(buffer.slice(9).join(''));
	else if (command.startsWith("cat"))    cat(buffer.slice(4).join(''));
	else if (command == "help") 			help("Commands Available", command_list);
	else if (command == "programs")		help("Program Listing", program_list);
	else if (command == "codes") 		rgb();
	else if (command == "about")        cat("about");
	else if (command == "unscramble") 	initialize_grid();
	else if (command == "reset") 		reset_colors();
	else if (command == "rgb") 			waves();
	else if (command == "rubles") 		ps1 = "₽";
	else if (command == "dollars") 		ps1 = "$";
	//else if (command == "undo") 		grid = JSON.parse(JSON.stringify(b_grid));
	else if (command == "quest") 		quest();
	else if (command == "game" )     	game();
	else if (command == "princess") 	princess();
	else if (command == "sixteen")		sixteen();
	else if (command == "visual")		visual();
	else if (command == "phase")		{terminal_mode = false;display=true;text_hidden=true;}
	else if (command == "text")			enter_text_mode();
	else if (command == "fifteen")		fifteen();
	else if (command == "life")			life();
	else if (command == "mandelbrot")   mandelbrot();
	else if (command == "territory")    territory();
	else if (command == "refresh")		location.reload();
	else if (command == "rmps1")			ps1 = "";
	else if (command == "ls")			ls();
	else echo ("● Invalid command ● Type help ●");
}

//----------------------------------------------------------------------------------Out-links

function quest()	 {location.assign('./exe/quest/main.html');}
function princess()	 {location.assign('./exe/princess/main.html');}
function sixteen()	 {location.assign('./exe/sixteen/main.html');}
function fifteen()	 {location.assign('./exe/fifteen/main.html');}
function game() 	 {location.assign('./exe/game/main.html'); }
function territory() {location.assign('./exe/territory/main.html'); }
function life()      {location.assign('./exe/gol/main.html'); }
function mandelbrot(){location.assign('./exe/mandelbrot/main.html'); }
function visual()    {location.assign('./visual.html'); }


var cursor_backup;
var b_grid; //backup grid

// everyone reccomends this for deep copy
function backup(grid) {//TODO decide whether this is multi-purpose or not
	b_grid = JSON.parse(JSON.stringify(grid));
	backup_history = JSON.parse(JSON.stringify(term_memory));
	backup_future = JSON.parse(JSON.stringify(forward_memory));
	term_memory = [];
	forward_memory = [];
	return 0;
}

function enter_text_mode(){
	backup(text_matrix);
	cursor_backup = [cursor_row, cursor_col]
	clear();
	ps1 = "";
	text_mode = true;
}

function exit_text_mode(){
	ps1 = "$";
	text_mode = false;
	text_matrix = b_grid;
	cursor_row = cursor_backup[0];
	cursor_col = cursor_backup[1];
	term_memory = backup_history;
	forward_memory = backup_future;
}

//TODO need separate handler for every mode this is getting bloated
var buffer = [] /* IMPORTANT : input buffer  */
window.addEventListener('keydown', (event) => {

	if (text_mode && event.key == "Escape"){
		exit_text_mode();
		prompt();
	}

	if (event.key == 'Delete'){
		delete_row();
	}

	if (terminal_mode == true){

		if (event.key == 'Enter'){
			cursor_col = 0;
			if ((cursor_row + 1) == num_rows)
				new_line();
			else cursor_row++;
			if (buffer.length && !text_mode){
				execute_command(Array.from(buffer));
			}
			buffer = [];
			prompt();
		}
		if (event.key == 'Backspace'){
			if (buffer.length && !text_mode)
				buffer.pop();
			back_space();
		}
		if (event.key == 'ArrowUp'){
			cursor_up();
		}
		if (event.key == 'ArrowDown'){
			cursor_down();
		}
		if (event.key == 'ArrowRight'){
			cursor_right();
		}
		if (event.key == 'ArrowLeft'){
			cursor_left();
		}
		if (event.key == 'Tab'){
			tab();
			event.preventDefault();
		}
		if (event.key.length === 1){
			event.preventDefault();
			if (!text_mode)
				buffer.push(event.key);
			write(event.key);
		}
		grid_to_canvas();
		return;
	}

	if (keys_down.hasOwnProperty(event.key)){
		keys_down[event.key] = true;
	}
	if (key_function_map.hasOwnProperty(event.key))
		key_function_map[event.key]();

	gradient.generate_codes();
	grid_to_canvas();
});

window.addEventListener('keyup', (event) => {
	if (keys_down.hasOwnProperty(event.key))
		keys_down[event.key] = false;
});

//-------------------------------------------------------Resize Event Listener
window.addEventListener("resize", fit_canvas);
window.addEventListener("onload", fit_canvas);
window.addEventListener("onload", canvas.focus);
window.addEventListener("onload", prompt());

// when it tries to get as tall as possible to fit, there end up being
// extra cols on the right side

function fit_canvas(){// resizing this way is costly and clobbers anything that was drawn. 
	context = canvas.getContext('2d');
	let available_x_pixels = window.innerWidth-32;
	let available_y_pixels = window.innerHeight-32;
	let desired_ratio = num_cols/num_rows; //width to height
	let window_ratio = available_x_pixels/available_y_pixels;
	let actual_width = 0;
	let actual_height = 0;
	if (desired_ratio > window_ratio ){ // too narrow, width dominates
		actual_width = available_x_pixels;
		actual_height = Math.round(actual_width/(desired_ratio));
		actual_width -= actual_width%(num_cols);
		actual_height -= actual_height%(num_rows); 
	} else { // too wide, height dominates
		actual_height = available_y_pixels;
		actual_width = Math.round(actual_height*(desired_ratio));
		actual_height -= actual_height%(num_rows);
		actual_width -= actual_width%(num_cols); 
	}
	canvas.setAttribute('width', actual_width.toString()); 
	canvas.setAttribute('height', actual_height.toString());
	canvas.width = canvas.width; canvas.height = canvas.height;
	context.textAlign = "start";
	context.textBaseline = "top";
	context.lineCap = "round";
	context.lineJoin = "round";
	context.lineWidth = 3;
	let font_size_pixels = Math.round(actual_height/num_rows*0.8);
	context.font = "Bold " + font_size_pixels+"px Courier";	
	grid_to_canvas();
}

function change_font(font) {
	return;
}

gradient.generate_codes();
fit_canvas();

//-----------------------------------------------------------------------------------Motion functions
function torus_up()		{
	let temp = grid.shift(); 
	grid.push(temp);
}
function torus_down()	{
	let temp = grid.pop(); 
	grid.unshift(temp);
}
function torus_left()	{
	for (let i = 0; i < num_rows; i++) {
		let temp = grid[i].shift(); grid[i].push(temp)
	}
}

function torus_right()	{
	for (let i = 0; i < num_rows; i++) {
		let temp = grid[i].pop(); grid[i].unshift(temp)
	}
}

function mobius_up()	{
	let temp = grid.shift(); 
	grid.push(temp.reverse())
}

function mobius_down()	{
	let temp = grid.pop(); 
	grid.unshift(temp.reverse());
}

function mobius_left()	{
	let row = []; 
	for (let i = 0; i < num_rows; i++) {
		row.unshift(grid[i].shift());
	}
	for (let j = 0; j < num_rows; j++){
		grid[j].push(row[j]);
	}
}

function mobius_right()	{
	let row = []; 
	for (let i = 0; i < num_rows; i++) {
		row.unshift(grid[i].pop());
	}
	for (let j = 0; j < num_rows; j++) { 
		grid[j].unshift(row[j]);
	}
}

function transpose() { 
	if (num_rows == num_cols)
		grid =  grid[0].map((col, i) => grid.map(row => row[i]));
}

var file_list = {
	"about" : 
	[
	 	"Welcome to the ●Mobius Terminal●",
	 	"This is a small Unix-like command line",
	 	"with some special properties...",
	 	"Some are readily available, some are hidden,",
	 	"and some will emerge from experimentation.",	
	 	":phase: toggles a special 'phase mode'",
		":cat: the phase_mode file to learn more.",
		"You might find something interesting."
	],

	"phase_mode" : 
	[
		"The phase command toggles PHASE MODE.",
		"In phase mode, you can SLIDE the grid.",
		"[IJKL] keys translate it along a Torus",
		"[WASD] keys translate it along a",
		"Real Projective Plane, which",
		"acts like a ●Mobius● strip in all directions.",
		"If you find things get too scrambled up,", 
		"the unscramble command may be helpful.",
		"---------------------------------------------",
		"Here you can also control the color gradient:",
		"defined by three waves: red, green, and blue.",
		"Each wave has four alterable properties:",
		"magnitude, frequency, center, and phase.",
		"By default, all color centers are set to 0,",
		"this is why the screen is black",
		"pressing [.] toggles this information.",
		"To adjust a particular property level:,",
		"hold keys for the FIRST LETTERS",
		"of the property you want to change,",
		"and adjust with the [] or [] arrow keys.",
		"ex: holding [b] with [c] and pressing []",
		"raises the blue center: see it grow blue.",
		"holding [b] with [m] and pressing []",
		"raises the blue magnitude: blue fluctuates.",
		"Altering all the waves produces gradients.",
		"Some can be quite beautiful, and they become",
		"even more interesting when scrambled",
		"Experiment with it and see.",
		"[=] resets colors, [h] toggles text."
	]
};
./parser.py
'''
Here is a program that takes a list of directories as input,
and outputs a formatted html page to display pages and code 
in an aesthetic way.

It parses python and javascript and html and css code so far,
putting in tags for syntax hilighting.

To use this script you need your target page to be in a
sub-directory, the front page to be named main.html (for linking)
and optionally a picture in the directory called screenshot
'''

import sys, os.path, re

import time

special_symbols = list('[](){}:;!')

js_reserved = [
    "await",    "break",    "case",
    "catch",    "class",    "const",
    "continue", "debugger", "default",
    "delete",   "do",       "else",
    "enum",     "export",   "extends",
    "false",    "finally",  "for",
    "function", "if",       "implements",
    "import",   "in",       "instanceof",
    "interface","let",      "new",
    "null",     "package",  "private",
    "protected","public",   "return",
    "super",    "switch",   "static",
    "this",     "throw",    "try",
    "True",     "typeof",   "var",
    "void",     "while",    "with",
    "yield",
]

py_reserved = [
    'False',    'None',     'True', 
    'and',      'as',       'assert', 
    'async',    'await',    'break', 
    'class',    'continue', 'def', 
    'del',      'elif',     'else', 
    'except',   'finally',  'for', 
    'from',     'global',   'if', 
    'import',   'in',       'is', 
    'lambda',   'nonlocal', 'not', 
    'or',       'pass',     'raise', 
    'return',   'try',      'while', 
    'with',     'yield'
]

c_reserved = [
    'auto',     'break',    'case',
    'char',     'const',    'continue',
    'default',  'do',       'double',
    'else',     'enum',     'extern',   
    'float',    'for',      'goto',
    'if',       'inline',   'int',
    'long',     'register', 'restrict',
    'return',   'short',    'signed',
    'sizeof',   'static',   'struct',
    'switch',   'typedef',  'union',
    'unsigned', 'void',     'volatile',
    'while',    '_Alingas', '_Alignof',
    '_Atomic',  '_Bool',    '_Complex',
    '_Decimal128',          '_Decimal32',
    '_Decimal64',           '_Generic',
    '_Imaginary',           '_Noreturn',
    '_Static_assert',       '_Thread_local',
]

image_extensions = ['.jpg', '.png', '.JPG', '.PNG']

def findWholeWord(w):
    return re.compile(r'\b({0})\b'.format(w)).search

if __name__ == "__main__":
    for directory in sys.argv[1:]:
        for (root, subs, files) in os.walk(directory):

            depth = root.count('/')
            
            js_readers = []
            html_readers = []
            css_readers = []
            txt_readers = []
            py_readers = []
            pictures = []

            content = [js_readers, html_readers, css_readers,
                        py_readers, txt_readers, pictures]
            
            if os.path.exists("./" + root + "/" + "formatted.html"):
                os.remove("./" + root + "/" + "formatted.html")
            print(root, subs, files)
            
            if '/_' in root or 'xterm' in root:
                continue
            for name in files:
                print(name)
                if 'screenshot' in name:
                    pictures.append(name)
                if name.endswith('.js'):
                    with open(root+'/'+name) as temp:
                        js_readers.append([root+'/'+name] + temp.readlines())
                if name.endswith('.html') and 'formatted' not in name:
                    with open(root+'/'+name) as temp:                
                        html_readers.append([root+'/'+name] + temp.readlines())
                if name.endswith('.css'):
                    with open(root+'/'+name) as temp:                
                        css_readers.append([root+'/'+name] + temp.readlines())
                if name.endswith('.txt'):
                    with open(root+'/'+name) as temp:                
                        txt_readers.append([root+'/'+name] + temp.readlines())
                if name.endswith('.py'):
                    with open(root+'/'+name) as temp:                
                        py_readers.append([root+'/'+name] + temp.readlines())

            if any(content): 
                _of     = open("./" + root + "/" + "formatted.html", "w+")
            else:
                continue

            _of.write('<!doctype html>')
            _of.write('<html lang="en">')
            _of.write('<head>')
            _of.write('  <meta charset="utf-8">')
            _of.write('  <title>Ѭ</title>')
            _of.write('<link href="' +  '../'*depth + 'style.css" rel="stylesheet"/>')
            _of.write('</head>')
            _of.write('<body>')

            if pictures:
                if os.path.exists("./" + root + "/" + "main.html"):
                    _of.write('\n\n<div class="image">\n\t <a href="main.html"> \n\t\t' + 
                    '<img class="screenshot" src="' + pictures[0] + '"></img>\n\t </a> \n</div>\n\n')
                else:
                    _of.write('\n\n<div class="image">' +  
                    '<img class="screenshot" src="' + pictures[0] + '"></img>\n</div>\n\n')

            for txt in txt_readers:
                _of.write('<div class="bookmark">' + txt[0]  +  '</div>')
                _of.write("<pre class=notes>\n")
                for line in txt[1:]:
                    line = line.replace("<", "<")
                    line = line.replace(">", ">")
                    line = line.replace("101010", "<")  # until i think of
                    line = line.replace("111000", ">")  # something better...
                    _of.write(line)
                _of.write("</pre>\n")

            for html in html_readers:
                _of.write('<div class="bookmark">' + html[0]  +  '</div>')
                _of.write("<pre class=html>\n")
                for line in html[1:]:  
                    r = line.replace("<", "<kw*^+<")  #  *^+ is just a token for 
                    r = r.replace(">","></kw>")       
                    r = r.replace("*^+", ">")           #  this line to match. Weird, i know...
                    if "<kw><!--" in r:
                        r = r.replace("<kw><!--", '<comment class="html"><!--')
                        r = r.replace("--></kw>", '--></comment>')
                    _of.write(r)
                _of.write("</pre>\n")

            for css in css_readers:
                _of.write('<div class="bookmark">' + css[0]  +  '</div>')
                _of.write("<pre class=css>\n")
                for line in css[1:]:
                    r = line
                    if "{" in line:
                        r = "<sc>"
                        r = r + line.replace("{", "{</sc>")
                    elif "}" in line:
                        r = line.replace("}", "<sc>}</sc>")
                    else:
                        r = "<css>" + r[:-1] + "</css>\n"

                    if "/*" in r:
                        r = r.replace('/*', '<comment class="css">/*')
                        r = r.replace('*/', '*/</comment>')

                    _of.write(r)
                _of.write("</pre>\n")

            for js in js_readers:
                dbl_quote = 0
                sgl_quote = 0
                block_comment = 0
                _of.write('<div class="bookmark">' + js[0]  +  '</div>')
                _of.write("<pre class=js>\n")
                for line_num, line in enumerate(js[1:]):
                    line = line.replace("<", "<")
                    line = line.replace(">", ">")

                    if '/*' in line: 
                        line = line.replace('/*', '<comment Class="js">/*')
                        block_comment = 1
                    if '*/' in line:
                        line = line.replace('*/', '*/</comment>')
                        block_comment = 0
                    if block_comment == 1:
                        _of.write(line)
                        continue

                    for word in js_reserved:
                        start = 0
                        while x := findWholeWord(word)(line, start):
                            line = line[:x.start()] + '<kw>' + word + '</kw>' + line[x.end():]
                            start = x.end() + len('<kw></kw>')
                    for sym in special_symbols:
                        if sym in line:
                            line = line.replace(sym, "<sc>" + sym + "</sc>")

                    j = 0
                    in_tag = 0 # don't put tags inside other tags!
                    temp = list(line)
                    
                    while j < len(temp): #go through chars of the line
                        if temp[j] == '<':
                            in_tag = 1
                        if temp[j] == '>':
                            in_tag = 0
                        # it double marks these /* quotes
                        if temp[j] == '/' and temp[j+1] == '/' and dbl_quote == 0 and sgl_quote ==0:
                            temp[j] = '<comment Class="js">/'
                            temp.append('</comment>')
                            break
                        if temp[j] == '"' and sgl_quote == 0 and in_tag == 0:
                            if dbl_quote == 0:
                                temp.insert(j, '<dbl_quote>')
                                dbl_quote = 1
                            else:
                                temp.insert(j + 1, '</dbl_quote>')
                                dbl_quote = 0
                            j += 2
                            continue
                        elif temp[j] == "'":
                            if sgl_quote == 0 and dbl_quote == 0 and in_tag == 0:
                                temp.insert(j, '<sgl_quote>')
                                sgl_quote = 1
                            else:
                                temp.insert(j + 1, '</sgl_quote>')
                                sgl_quote = 0
                            j += 2
                            continue
                        j += 1
                    line = ''.join(temp)

                    _of.write(line)
                _of.write("</pre>\n")


            #TODO: block quotes in python
            for py in py_readers:
                dbl_quote = 0
                sgl_quote = 0
                _of.write('<div class="bookmark">' + py[0] + '</div>')
                _of.write("<pre class=py>\n")

                for line in py[1:]:
                    line = line.replace("<", "<")
                    line = line.replace(">", ">")
                    for word in py_reserved:
                        start = 0
                        while x := findWholeWord(word)(line, start):
                            line = line[:x.start()] + '<kw>' + word + '</kw>' + line[x.end():]
                            start = x.end() + len('<kw></kw>')
                    for sym in special_symbols:
                        if sym in line:
                            line = line.replace(sym, "<sc>" + sym + "</sc>")
                    j = 0
                    in_tag = 0 # don't put tags inside other tags!
                    temp = list(line)
                    while j < len(temp):
                        if temp[j] == '#' and dbl_quote == 0 and sgl_quote ==0:
                            temp[j] = '<comment Class="py">#'
                            temp.append('</comment>')
                            break
                        if temp[j] == '"' and sgl_quote == 0:
                            if dbl_quote == 0:
                                temp.insert(j, '<dbl_quote>')
                                dbl_quote = 1
                            else:
                                temp.insert(j + 1, '</dbl_quote>')
                                dbl_quote = 0
                            j += 2
                            continue
                        elif temp[j] == "'":
                            if sgl_quote == 0 and dbl_quote == 0:
                                temp.insert(j, '<sgl_quote>')
                                sgl_quote = 1
                            else:
                                temp.insert(j + 1, '</sgl_quote>')
                                sgl_quote = 0
                            j += 2
                            continue
                        j += 1
                    line = ''.join(temp)
                    _of.write(line)
                _of.write("</pre>\n")

            _of.write("</body>")
            _of.write("</html>")