<html>
	<head>
		<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
	</head>
	<body class="flex column">
		<main id="main-area" class="flex f1">
			<div id="poly-wrapper" class="f1">
				<div id="poly-canvas">
					<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg" height="1000" width="1000">
						<defs>
							<pattern id="backgrid" x="0" y="0" width="0.001" height="0.001">
								<circle class="fill-circle" cx="0" cy="0" r="0.1"></circle>
								<circle class="fill-circle" cx="1" cy="0" r="0.1"></circle>
								<circle class="fill-circle" cx="0" cy="1" r="0.1"></circle>
								<circle class="fill-circle" cx="1" cy="1" r="0.1"></circle>
							</pattern>
						</defs>
						<rect fill="url(#backgrid)" width="1000" height="1000"></rect>
						<g id="svg-content"></g>
						<g id="svg-controls">
							<path id="preview-shape"></path>
							<circle id="cursor-circle" cx="0" cy="0" r="0.3"></circle>
							<rect id="view-box"></rect>
						</g>
					</svg>
				</div>
			</div>
			<aside id="side-controls" class="flex column no-sel">
				<div class="sidebar-box">
					<div id="color-box" class="no-sel">
						<div class="col-slider" id="hue-box">
							<div class="slider-nub"></div>
						</div>
						<div class="col-slider" id="luminance-box">
							<div class="slider-nub"></div>
						</div>
						<div class="col-slider" id="saturation-box"></div>
					</div>
				</div>
				<div class="sidebar-box f1 no-border">
					<div id="layer-box" class="flex column">
						<div id="layer-controls"></div>
						<div id="layers" class="f1"></div>
					</div>
				</div>
			</aside>
		</main>
		<div id="controls" class="f-fixed">
			<div class="ctrl-btn" onclick="setEditMode('draw')">
				<svg viewBox="0 0 26 26">
					<path d="M6 16.5l-1 4l4 -1l-2 -1"></path>
					<path d="M7 15.5l1 2l2 1l10 -9l-2 -1l-1 -2"></path>
					<path d="M18 5.5l1 2l2 1l-1 -2"></path>
				</svg>
			</div>
			<div class="ctrl-btn" onclick="setEditMode('move')">
				<svg viewBox="0 0 15 15">
					<path d="M6 10l-2 -2l-1 0l0 1l3 3l4 0l2 -7l-1 -1l-2 4l0 -4l-1 -1l-1 5l-1 -4l-1 -1"></path>
				</svg>
			</div>
			<div class="ctrl-btn" onclick="setEditMode('drag')">
				<svg viewBox="0 0 26 26">
					<path d="M13 5l-4 3l3 0l0 10l-3 0l4 3l4 -3l-3 0l0 -10l3 0"></path>
					<path d="M8 12l10 0l0 -3l3 4l-3 4l0 -3l-10 0l0 3l-3 -4l3 -4"></path>
				</svg>
			</div>
			<div class="ctrl-btn" onclick="setEditMode('select')">
				<svg viewBox="0 0 25 25">
					<path d="M8.5 6l1 -1l1 1l0 6l1 -1l2 0l1 1l1 -1l2 0l1 1l0 6l-2 2l-8 0l-2 -2l0 -6l2 -1l0 -3"></path>
				</svg>
			</div>
			<div class="ctrl-btn" onclick="setEditMode('delete')">
				<svg viewBox="0 0 27 27">
					<path d="M5 17l3 4l4 0l10 -8l0 -2l-4 -5l-2 0l-11 9"></path>
				</svg>
			</div>
			<div class="spacer"></div>
			<div class="ctrl-btn hist-btn" onclick="stepHistory(-1)">
				<svg viewBox="0 0 19 19">
					<path d="M15 7.5l-6 0l0 -3l-5 5l5 5l0 -3l6 0"></path>
				</svg>
			</div>
			<div class="ctrl-btn hist-btn" onclick="stepHistory(1)">
				<svg viewBox="0 0 19 19">
					<path d="M15 9.5l-5 -5l0 3l-6 0l0 4l6 0l0 3"></path>
				</svg>
			</div>
			<div class="spacer"></div>
			<div class="ctrl-btn zoom-btn" onclick="zoom(-1)">
				<svg viewBox="0 0 31 31">
					<path d="M8 14l0 3l15 0l0 -3"></path>
				</svg>
			</div>
			<div class="ctrl-btn zoom-btn" onclick="zoom(1)">
				<svg viewBox="0 0 31 31">
					<path d="M14 8l0 6l-6 0l0 3l6 0l0 6l3 0l0 -6l6 0l0 -3l-6 0l0 -6"></path>
				</svg>
			</div>
			<div class="control-box">
				<span class="control-lbl">Padding</span>
				<input class="number">
			</div>
			<div class="control-box">
				<span class="control-lbl">Aspect ratio</span>
				<input class="number aspect-item" disabled>
				<span class="colon">:</span>
				<input class="number aspect-item" disabled>
				<div class="checkbox" id="aspect-toggle">
					<svg viewBox="0 0 12 12">
						<path d="M8 3.5l1 1l-3 3l-1 1l-2 -2l1 -1l1 1" fill="#ffffff"></path>
					</svg>
				</div>
			</div>
			<div id="right-box">
				<div class="control-box">
					<span class="control-lbl">Dark mode</span>
					<div class="checkbox" id="dm-toggle">
						<svg viewBox="0 0 12 12">
							<path d="M8 3.5l1 1l-3 3l-1 1l-2 -2l1 -1l1 1" fill="#ffffff"></path>
						</svg>
					</div>
				</div>
			</div>
		</div>
	</body>
</html>
body {
	display: flex;
	font-size: 15px;
	margin: 0;
	overflow: hidden;
	height: 100vh;
	font-family: "Open Sans", sans-serif;
}

body.moving * {
	cursor: move;
	cursor: -moz-grabbing !important;
	cursor: -webkit-grabbing !important;
}

.no-sel {
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.flex, .flex-centered, .center-align, .center-justify, .flex-fill {
	display: -webkit-box;
	display: -ms-flexbox;
	display: -moz-box;
	display: -webkit-flex;
	display: flex;
}

.flex-centered, .center-align {
	align-items: center;
}

.flex-centered, .center-justify {
	justify-content: center;
}

.flex-fill, .flex-fill-i {
	flex-grow: 1;
	flex-shrink: 0;
	flex-basis: 0;
}

.column {
	flex-direction: column;
}

.row {
	flex-direction: row;
}

.f1 {
	flex-grow: 1;
	flex-shrink: 1;
	flex-basis: 0;
}

.f-fixed {
	flex-grow: 0;
	flex-shrink: 0;
}

/*Real stuff*/

#main-area {
	position: relative;
}

#poly-wrapper {
	position: relative;
}

#poly-canvas {
	position: absolute;
	width: 1000em;
	height: 1000em;
	margin-left: -500em;
	margin-top: -500em;
	top: 50%;
	left: 50%;
	cursor: default;
	cursor: -moz-grab;
	cursor: -webkit-grab;
}

.thumbnail {
	position: absolute;
	width: 1000em;
	height: 1000em;
	margin-left: -500em;
	margin-top: -500em;
	top: 50%;
	left: 50%;
}

body.dark-mode #poly-canvas {
	background: #17181b;
}

body.show-draw #poly-canvas {
	cursor: pointer;
}

body.select #poly-canvas,
body.delete #poly-canvas,
body.drag-path #poly-canvas {
	cursor: default;
}

svg {
	width: 100%;
	height: 100%;
}

#svg-controls {
	pointer-events: none;
}

body.hide-overlays #svg-controls {
	display: none;
}

#fill-rect {
	fill: white;
	stroke: #eee;
}

.fill-circle {
	fill: #e3e3e3;
}

body.dark-mode .fill-circle {
	fill: #2f354c;
}

#cursor-circle {
	display: none;
	fill: #ddd;
}

body.show-draw #cursor-circle {
	display: block;
}

body.dark-mode #cursor-circle {
	fill: #3f81ce;
}

#preview-shape {
	fill: rgba(165, 165, 165, 0.2);
	stroke: #848484;
	stroke-width: 0.07;
	stroke-dasharray: 0.2;
}

#view-box {
	fill: none;
	stroke: rgba(63, 129, 206, 0.4);
	stroke-width: 0.1;
	stroke-dasharray: 0.2;
}

body.dark-mode #view-box {
	stroke: rgba(140, 193, 255, 0.7);
}

body.select #svg-content path:hover,
body.delete #svg-content path:hover,
body.drag-path #svg-content path:hover {
	opacity: 0.6;
	cursor: pointer;
}

#side-controls {
	width: 300px;
	background: #292d3f;
	z-index: 10;
}

.sidebar-box {
	border-bottom: 1px solid #595f7b;
	margin: 8px;
}

.sidebar-box.no-border {
	border: none;
}

#color-box {
	margin: 4px;
	margin-bottom: 12px;
	padding: 9px;
	background: #4b4f62;
}

.col-slider {
	position: relative;
	width: 256px;
	height: 25px;
	background: #404352;
}

.col-slider + .col-slider {
	margin-top: 9px;
}

.slider-nub {
	position: absolute;
	top: 0;
	left: 0;
	width: 2px;
	height: 100%;
	background: #7e7e7e;
}

#hue-box {
	background: linear-gradient(to right, red 0%, #ff0 16%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red 100%);
}

#luminance-box {
	background: linear-gradient(to right, black 0%, red 50%, white 100%);
}

.col-square {
	float: left;
	width: 16px;
	height: 25px;
	cursor: pointer;
}

.col-square.active {
	border-top: 3px solid black;
	border-bottom: 3px solid white;
	box-sizing: border-box;
}

#layer-box {
	height: 100%;
	border: 1px solid #595f7b;
	border-radius: 3px;
	margin: 0 4px;
}

#layer-controls {
	height: 30px;
	border-bottom: 1px solid #595f7b;
	margin: 0 4px;
}

#layers {
	overflow: auto;
}

.layer {
	position: relative;
	width: 100%;
	float: left;
}

.layer.active {
	background: #3e4461;
}

.layer-inner {
	margin: 6px 12px 6px 8px;
}

.layer-view {
	position: relative;
	width: 40px;
	height: 30px;
	border: 1px solid #595f7b;
	background: white;
	overflow: hidden;
}

body.dark-mode .layer-view {
	background: #17181b;
}

.layer-name {
	display: block;
	position: relative;
	color: #c4c6cd;
	margin: 0 6px;
	background: transparent;
	border: none;
	font-family: inherit;
	padding: 3px 0;
	text-indent: 0.5em;
	text-overflow: ellipsis;
}

.layer-name[disabled] {
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.layer-name:focus {
	outline: none;
	background: rgba(255,255,255,0.1);
}

.layer .layer-vis {
	width: 20px;
	height: 20px;
	margin: 0;
	background: rgba(0,0,0,0.2);
}

#controls {
	width: 100%;
	height: 50px;
	background: #252834;
	left: 0;
	bottom: 0;
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	z-index: 10;
}

.ctrl-btn {
	float: left;
	width: 34px;
	height: 34px;
	margin: 8px;
	margin-right: 0;
	border-radius: 3px;
	background: #404352;
	cursor: pointer;
}

.ctrl-btn svg {
	fill: #797a81;
}

.ctrl-btn.active {
	background: #3f81ce;
}

.ctrl-btn.active svg {
	fill: white;
}

.spacer {
	float: left;
	height: 34px;
	width: 1px;
	margin: 8px;
	margin-right: 0;
	background: #404352;
}

.control-box {
	float: left;
	margin: 11px 0;
	margin-left: 25px;
}

#right-box {
	float: right;
	margin-right: 8px;
}

.control-lbl, .colon {
	float: left;
	line-height: 28px;
	text-transform: uppercase;
	font-size: 70%;
	color: #eee;
	margin-right: 5px;
}

.colon {
	margin-right: 0;
	margin-left: 5px;
}

#controls input {
	display: block;
	float: left;
	height: 28px;
	border: none;
	font-family: inherit;
	background: #404352;
	padding: 0 7px;
	outline: none;
	color: #eee;
	width: 50px;
	border-radius: 3px;
	margin-left: 5px;
}

#controls input[disabled] {
	background: #2f313d;
	color: #6f759a;
}

.checkbox {
	float: left;
	width: 28px;
	height: 28px;
	background: #404352;
	border-radius: 3px;
	margin-left: 5px;
	cursor: pointer;
}

.checkbox.checked {
	background: #3f81ce;
}

.checkbox svg {
	display: none;
}

.checkbox.checked svg {
	display: block;
}

body.hide-overlays .poly-item {
	opacity: 0.6;
}

.poly-item.dragging {
	opacity: 1 !important;
}
var cellSize=15,
	dragging=false,
	hist=[],
	histPointer=-1,
	evtMap={
		draw: {
			down: addPoint,
			move: moveDot
		},
		drag: {
			down: selectPath,
			drag: dragPath,
			up: dropPath
		},
		move: {
			down: startCDrag,
			drag: cDrag,
			up: endCDrag
		},
		select: {
			down: accentPath
		},
		delete: {
			down: evtDeletePath
		},
		hue: {
			drag: hueSet,
			up: resetEMode,
			inert: true
		},
		luminance: {
			drag: lumSet,
			up: resetEMode,
			inert: true
		}
	},
	canvasCoords={
		x: -500,
		y: -500
	},
	viewBox={
		x: 0,
		y: 0,
		w: 0,
		h: 0,
		padding: 1,
		aspectW: 1,
		aspectH: 1,
		showAspect: false
	},
	state={
		layer: null	
	},
	color={
		hue: 0,
		luminance: 0,
		saturation: 0
	},
	anonLayerCount=0,
	evtMode=null,
	oldEMode=null,
	moveData={},
	fill=[255,0,0,255],
	shapes=[],
	layers=[],
	editMode=null,
	activeLayer=null;

//Elements
var sCont=document.getElementById("svg-content"),
	canv=document.getElementById("poly-canvas"),
	cursor=document.getElementById("cursor-circle"),
	pShape=document.getElementById("preview-shape"),
	vBox=document.getElementById("view-box"),
	sControls=document.getElementById("color-box"),
	cSliders=document.getElementsByClassName("col-slider");
	
canv.addEventListener("mousedown",down);
canv.addEventListener("mousemove",move);
canv.addEventListener("mouseup",up);
sControls.addEventListener("mousemove",move);
sControls.addEventListener("mouseup",up);
document.body.addEventListener("keydown",keydown);

function down(evt){
	dragging=true;
	if (evtMode.down)
		evtMode.down(evt);
}

function move(evt){
	if (evtMode.move)
		evtMode.move(evt);
	if (dragging)
		drag(evt);
}

function drag(evt){
	if (evtMode.drag)
		evtMode.drag(evt);
}

function up(evt){
	dragging=false;
	if (evtMode.up)
		evtMode.up(evt);
}

function keydown(evt){
	var key=evt.getKey();
	if (evt.altKey){
		switch (key){
			case "n":
				addLayer(new Layer());
				evt.preventDefault();
				return;
		}
	}
	
	switch (key){
		case "b":
			setEditMode("draw");
			break;
		case "h":
			setEditMode("move");
			break;
		case "v":
			setEditMode("drag");
			break;
		case "s":
			setEditMode("select");
			break;
		case "d":
			setEditMode("delete");
			break;
		case "delete":
			if (moveData.points&&editMode=="draw"){
				moveData.points.pop();
				plotPShape();
			}
			break;
		case "enter":
			if (editMode=="draw"&&moveData.points&&moveData.coords)
				addPoint(null,moveData.coords,true);
			break;
	}
}

function setEditMode(mode){
	var body=document.body,
		bcl=body.classList,
		cb=document.getElementsByClassName("ctrl-btn"),
		modes=["draw","move","drag","select","delete"];
	
	for (var i=0;i<modes.length;i++){
		var cl=cb[i].classList;
		if (modes[i]==mode)
			cl.add("active");
		else
			cl.remove("active");
	}
	
	//Clear settings
	clRem(body,"show-draw","select","drag-path","delete");
	pShape.setAttribute("d","");
	
	switch (mode){
		case "draw":
			bcl.add("show-draw");
			moveData={
				points: []
			};
			cursor.setAttribute("cx",-1000);
			cursor.setAttribute("cy",-1000);
			break;
		case "move":
			moveData={};
			break;
		case "drag":
			bcl.add("drag-path");
			moveData={};
			break;
		case "select":
			bcl.add("select");
			break;
		case "delete":
			bcl.add("delete");
			break;
	}
	editMode=mode;
	evtMode=evtMap[mode];
}

function clRem(elem){
	for (var i=1;i<arguments.length;i++)
		elem.classList.remove(arguments[i]);
}

function clearSVG(svg){
	var ch=svg.children;
	while (ch[0])
		svg.removeChild(ch[0]);
}

function genPath(arr,close){
	var d="",
		prev=[0,0];
	for (var i=0;i<arr.length;i++){
		var m=[
			arr[i][0]-prev[0],
			arr[i][1]-prev[1]
		];
		d+=(i?"l":"M")+m[0]+" "+m[1];
		prev=arr[i];
	}
	if (close)
		d+=" z";
	return d;
}

function genSVG(){
	var ps=[].concat.apply([],layers.filter(function(e){return e.visible;}).map(function(e){return e.paths;})),
    	svg='<svg viewBox="0 0 $0 $1">\n$2\n</svg>',
        paths=ps.map(function(e){
            var path=genPath(e.points.map(function(p){
                    return [p[0]-viewBox.x,p[1]-viewBox.y];
                }));
            return '\t<path d="$0" fill="$1"></path>'.format(path,arrToCol(e.fill));
        });
    return svg.format(viewBox.w,viewBox.h,paths.join("\n"));
}

//Draw shapes
function addPoint(evt,c,finish){
	c=c || getCoords(evt);
	var p=moveData.points[0],
		lp=moveData.points[moveData.points.length-1],
		sameAsLast=lp&&(c.x==lp[0]&&c.y==lp[1]),
		min=finish&&!sameAsLast?1:2;
	
	if (moveData.points.length>min&&(c.x==p[0]&&c.y==p[1]||sameAsLast||finish)){
		moveData.fill=fill.slice();
		if (finish&&!sameAsLast)
			moveData.points.push([c.x,c.y]);
		addShape(moveData,activeLayer);
		setEditMode("draw");
		pushHistory();
	}else if (!sameAsLast&&!finish){
		moveData.points.push([c.x,c.y]);
		moveDot(null,c);
	}
}

function moveDot(evt,c){
	c=c || getCoords(evt);
	moveData.coords=c;
	cursor.setAttribute("cx",c.x);
	cursor.setAttribute("cy",c.y);
	plotPShape();
}

function plotPShape(){
	if (moveData.points.length&&moveData.coords){
		var p=moveData.points.slice();
		p.push([moveData.coords.x,moveData.coords.y]);
		pShape.setAttribute("d",genPath(p));
	}else{
		pShape.setAttribute("d","");
	}
}
	
function addShape(shape,layer,noVis){
	var p=node(ns("path"),"poly-item",null,{
			d: genPath(shape.points,true),
			fill: arrToCol(shape.fill)
		}).app(layer.svgLayer),
		clone=p.cloneNode();
	layer.thumb.appendChild(clone);
	shape.layer=layer;
	p.ref=shape;
	shape.path=p;
	shape.thumbClone=clone;
	layer.paths.push(shape);
	if (!noVis)
		calculateLayerViewBox(layer);
}

function calculateLayerViewBox(layer){
	if (layer.paths.length){
		var points=[].concat.apply([],layer.paths.map(function(e){return e.points})),
			xs=points.map(function(e){return e[0]}),
			ys=points.map(function(e){return e[1]}),
			//Could have used Math.min/max.apply, but maybe it's best to
			//not pass thousands of arguments to a function
			xMin=xs.reduce(function(v,c){return Math.min(v,c)}),
			yMin=ys.reduce(function(v,c){return Math.min(v,c)}),
			xMax=xs.reduce(function(v,c){return Math.max(v,c)}),
			yMax=ys.reduce(function(v,c){return Math.max(v,c)});

		layer.viewBox={
			x: xMin,
			y: yMin,
			w: xMax-xMin,
			h: yMax-yMin
		};
	}
	drawVisualGuides();
}

function drawVisualGuides(){
	var vbs=layers.filter(function(e){return e.visible&&e.paths.length;}).map(function(e){
			var v=e.viewBox
			return {
				xMin: v.x,
				yMin: v.y,
				xMax: v.x+v.w,
				yMax: v.y+v.h
			};
		});
	
	if (vbs.length){
		var c=vbs.reduce(function(v,c){
				return {
					xMin: Math.min(v.xMin,c.xMin),
					yMin: Math.min(v.yMin,c.yMin),
					xMax: Math.max(v.xMax,c.xMax),
					yMax: Math.max(v.yMax,c.yMax)
				}
			}),
			x=c.xMin-viewBox.padding,
			y=c.yMin-viewBox.padding,
			w=c.xMax-c.xMin+2*viewBox.padding,
			h=c.yMax-c.yMin+2*viewBox.padding;
		
		if (viewBox.showAspect){
			var ar=w/h,
				vbar=viewBox.aspectW/viewBox.aspectH,
				w2=ar>vbar?w:h*vbar,
				h2=ar>vbar?w/vbar:h;
			x+=(w-w2)/2;
			y+=(h-h2)/2;
			h=h2;
			w=w2;
		}
		
		viewBox.x=x;
		viewBox.y=y;
		viewBox.w=w;
		viewBox.h=h;
		
		vBox.setAttribute("x",viewBox.x);
		vBox.setAttribute("y",viewBox.y);
		vBox.setAttribute("width",viewBox.w);
		vBox.setAttribute("height",viewBox.h);
		vBox.style.display="";
	}else
		vBox.style.display="none";
}

//Drag canvas
function startCDrag(evt){
	moveData={
		coords: getAbsCoords(evt)
	};
	document.body.classList.add("moving");
}

function cDrag(evt){
	var c=getAbsCoords(evt);
	canvasCoords.x+=(c.x-moveData.coords.x);
	canvasCoords.y+=(c.y-moveData.coords.y);
	canv.style.marginLeft=canvasCoords.x+"em";
	canv.style.marginTop=canvasCoords.y+"em";
	moveData.coords=c;
}

function endCDrag(){
	document.body.classList.remove("moving");
	updateThumbPos();
}

//Drag paths
function selectPath(evt){
	var ref=evt.target.ref;
	if (ref){
		moveData={
			coords: getCoords(evt),
			draggingPath: ref
		};
		document.body.classList.add("hide-overlays");
		ref.path.classList.add("dragging");
		selectLayer(ref.layer);
	}
}

function dragPath(evt){
	var path=moveData.draggingPath;
	if (path){
		var c=getCoords(evt),
			delta={
				x: c.x-moveData.coords.x,
				y: c.y-moveData.coords.y
			};
		
		if (delta.x||delta.y){
			path.points=path.points.map(function(p){
				return [p[0]+delta.x,p[1]+delta.y];
			});
			var d=genPath(path.points,true);
			path.path.setAttribute("d",d);
			path.thumbClone.setAttribute("d",d);
			moveData.coords=c;
		}
	}
}

function dropPath(){
	var path=moveData.draggingPath;
	if (path){
		document.body.classList.remove("hide-overlays");
		path.path.classList.remove("dragging");
		calculateLayerViewBox(path.layer);
		moveData={};
	}
}

//Select path
function accentPath(evt){
	var path=evt.target.ref;
	if (path){
		selectLayer(path.layer);
	}
}

//Delete
function evtDeletePath(evt){
	deletePath(evt.target);
}

function deletePath(path){
	var ref=path.ref;
	if (ref){
		var layer=ref.layer,
			canv=layer.svgLayer,
			thumb=layer.thumb;

		for (var i=0,l=layer.paths.length;i<l;i++){
			if (layer.paths[i]==ref){
				layer.paths.splice(i,1);
				break;
			}
		}
		canv.removeChild(path);
		thumb.removeChild(ref.thumbClone);
		pushHistory();
		calculateLayerViewBox(layer);
	}
}

//Save and load
function relayPushHistory(){
	var data=cloneObj(shapes,["path"]),
		hObj=hist[hist.length-1];
	if (checkEq(data,hObj))
		pushHistory(data);
}

function pushHistory(obj){
	obj=obj || cloneObj(shapes,["path"]);
	hist.splice(++histPointer,100000,obj);
	setHistBtns();
}

function stepHistory(dir){
	var hp=Math.max(Math.min(histPointer+dir,hist.length-1),0);
	if (hp!=histPointer){
		histPointer=hp;
		load(cloneObj(hist[histPointer]));
	}
}

//Layer control
function Layer(name){
	this.name=name;
	this.visible=true;
	this.opacity=1;
	this.svgLayer=null;
	this.layerBox=null;
	this.paths=[];
	this.viewBox={
		x: 0,
		y: 0,
		w: 0,
		h: 0
	};
	
	this.setVisible=function(state){
		if (this.visible!=state){
			this.visible=state;
			this.svgLayer.style.display=state?"":"none";
		}
	};
}

function addLayer(data){
	data=data || {};
	var lay=document.getElementById("layers"),
		box=node("div","layer").before(activeLayer?activeLayer.layerBox:null,lay),
		inner=node("div","layer-inner flex center-align").app(box),
		lv=node("div","layer-view f-fixed").app(inner),
		ln=node("input","layer-name f1",null,{value: data.name || "Untitled layer $0".format(++anonLayerCount), disabled: true}).app(inner),
		cb=node("div","checkbox layer-vis f-fixed").app(inner),
		svg=node(ns("svg"),"thumbnail",null,{viewBox: "0 0 1000 1000"}).app(lv);
	new Checkbox(cb,function(state){
		data.setVisible(state);
		drawVisualGuides();
	},data.visible);
	data.layerBox=box;
	data.thumb=svg;
	
	cb.onclick=function(evt){
		evt.stopPropagation();
	};
	
	var g=node(ns("g"),"svg-layer").after(activeLayer?activeLayer.svgLayer:null,sCont);
	data.svgLayer=g;
	
	box.addEventListener("click",function(){
		selectLayer(data);
	});
	
	box.addEventListener("dblclick",function(evt){
		if (evt.target==ln){
			ln.disabled=false;
			ln.select();
		}
	});
	
	ln.addEventListener("blur",blur);
	ln.addEventListener("change",blur);
	
	function blur(){
		ln.disabled=true;
		data.name=ln.value || data.name;
		ln.value=data.name;
		ln.selectionStart=0;
		ln.selectionEnd=0;
	}
	
	layers.push(data);
	activeLayer=data;
	selectLayer(data);
	updateThumbSize();
}

function selectLayer(layer){
	var active=document.getElementsByClassName("layer active");
	while (active[0])
		active[0].classList.remove("active");
	layer.layerBox.classList.add("active");
	activeLayer=layer;
}

function updateThumbSize(){
	var pw=document.getElementById("poly-wrapper"),
		t=document.getElementsByClassName("thumbnail"),
		thumbAspect=4/3,
		w=pw.offsetWidth,
		h=pw.offsetHeight,
		fs=((thumbAspect>w/h)?30/h:40/w)*cellSize;
	
	for (var i=0,l=t.length;i<l;i++)
		t[i].style.fontSize=fs+"px";
}

function updateThumbPos(){
	var t=document.getElementsByClassName("thumbnail");
	for (var i=0,l=t.length;i<l;i++){
		t[i].style.marginLeft=canvasCoords.x+"em";
		t[i].style.marginTop=canvasCoords.y+"em";
	}
}

function setHistBtns(){
	var btns=document.getElementsByClassName("hist-btn"),
		cl=btns[0].classList,
		cl2=btns[1].classList;
	
	if (histPointer<1)
		cl.remove("active");
	else
		cl.add("active");
	
	if (histPointer<hist.length-1)
		cl2.add("active");
	else
		cl2.remove("active");
}

/*function load(d){
	clearSVG(sCont);
	shapes=[];
	for (var i=0;i<d.length;i++)
		addShape(d[i],true);
	drawVisualGuides();
	setHistBtns();
}*/
	
function getCoords(evt,round,map,elem){
	round=round || cellSize;
	map=map || 1;
	var bcr=(elem || canv).getBoundingClientRect(),
		e=(evt.touches || [evt])[0];
	return {
		x: Math.round((e.clientX-bcr.left)/round)*map,
		y: Math.floor((e.clientY-bcr.top)/round)*map
	};
}
	
function getAbsCoords(evt){
	var prec=cellSize/20;
	return getCoords(evt,prec,1/20,document.documentElement);
}

function pxCoords(evt,e){
	return getCoords(evt,1,1,e || document.documentElement);
}

function arrToCol(arr,noOpacity){
	if (arr[3]==255||noOpacity){
		var out="#";
		for (var i=0;i<3;i++){
			var str=arr[i].toString(16);
			out+=str.length==1?("0"+str):str;
		}
		return out;
	}
	return "rgba("+arr[0]+","+arr[1]+","+arr[2]+","+(arr[3]/255).toPrecision(2)+")";
}

function Checkbox(elem,callback,intState){
	this.dom=elem;
	this.callback=callback;
	var internalState=elem.classList.contains("checked");
	intState=intState || internalState;
	
	this.toggle=function(state){
		internalState=(typeof state=="boolean")?state:!internalState;
		var cls=this.dom.classList;
		
		if (internalState)
			cls.add("checked");
		else
			cls.remove("checked");
		
		if (this.callback)
			this.callback(internalState);
	}
	
	elem.addEventListener("click",this.toggle.bind(this));
	Object.defineProperty(this,"checked",{
		set: function(state){
			this.toggle(state);
		},
		get: function(){
			return internalState;
		}
	});
	if (intState!=internalState)
		this.toggle(intState);
}

function cloneObj(data,ignore){
	var ign={};
	ignore=ignore || [];
	for (var i=0;i<ignore.length;i++)
		ign[ignore[i]]=true;
	
	function clone(obj){
		if (typeof obj!="object"||obj instanceof Node||obj===null)
			return obj;
		var out=Array.isArray(obj)?[]:{};
		for (var key in obj){
			if (!ign[key]){
				if (typeof obj[key]=="object")
					out[key]=clone(obj[key]);
				else
					out[key]=obj[key];
			}
		}
		return out;
	}
	
	return clone(data);
}

Array.equals=function(arr,arr2){
	if (arr.length!=arr2.length)
		return false;
	for (var i=0;i<arr.length;i++){
		var a1=arr[i],
			a2=arr2[i];
		if (a1!=a2){
			if (!checkEq(a1,a2))
				return false;
		}
	}
	return true;
};

Object.equals=function(obj,obj2){
	if (obj instanceof Node)
		return true;
	if (Object.keys(obj).length!=Object.keys(obj2).length)
		return false;
	for (var key in obj){
		var o1=obj[key],
			o2=obj2[key];
		if (o1!=o2){
			if (!checkEq(o1,o2))
				return false;
		}
	}
	return true;
}

function checkEq(o1,o2){
	if (typeof o1!=typeof o2)
		return false;
	if (Array.isArray(o1))
		return Array.equals(o1,o2);
	else if (o1!==null&&o2!==null&&typeof o1=="object")
		return Object.equals(o1,o2);
	return o1==o2;
}

function startHueSet(evt){
	saveEMode(evtMap.hue);
	dragging=true;
	hueSet(evt);
}

function hueSet(evt){
	color.hue=Math.max(Math.min(pxCoords(evt,cSliders[0]).x/256,1),0);
	setColorSel();
}

function startLumSet(evt){
	saveEMode(evtMap.luminance);
	dragging=true;
	lumSet(evt);
}

function lumSet(evt){
	color.luminance=Math.max(Math.min(pxCoords(evt,cSliders[1]).x/256,1),0);
	setColorSel();
}

function saveEMode(e){
	if (!evtMode.inert)
		oldEMode=evtMode;
	evtMode=e;
}

function resetEMode(){
	evtMode=oldEMode;
}

function setColorSel(){
	function set(){
		var cols=[
				[1,0,0],
				[1,1,0],
				[0,1,0],
				[0,1,1],
				[0,0,1],
				[1,0,1],
				[1,0,0],
				[1,0,0]
			],
			offs=Math.floor(color.hue*6),
			perc=color.hue*6%1,
			sn=document.getElementsByClassName("slider-nub"),
			sb=document.getElementById("saturation-box"),
			c=cols[offs],
			c2=cols[offs+1],
			h=c.map(function(h,i){return hue(h,c2[i],perc)}),
			l=h.map(function(l){return luminance(l,color.luminance)});

		sn[0].style.left=(color.hue*100)+"%";
		sn[1].style.left=(color.luminance*100)+"%";
		cSliders[1].style.background="linear-gradient(to right, black 0%, "+arrToCol(h,true)+" 50%, white 100%)";
		var out=[0,0,0,0];
		sb.innerHTML="";
		for (var i=0;i<16;i++){
			var s=l.map(function(h){return hue(h/255,1,i/16)}),
				box=node("div","col-square").app(sb);
			s[3]=255;
			if (i==color.saturation){
				box.classList.add("active");
				out=s;
			}
			(function(b,col,idx){
				b.onclick=function(){
					var cs=document.getElementsByClassName("col-square active")[0];
					if (cs)
						cs.classList.remove("active");
					b.classList.add("active");
					color.saturation=idx;
					fill=col;
				}
			})(box,s,i);
			box.style.background=arrToCol(s);
		}
		fill=out;
	}
	
	function hue(start,end,perc){
		return Math.round((start+(end-start)*perc)*255);
	}

	function luminance(col,perc){
		perc*=2;
		if (perc>1)
			return Math.floor(col+(255-col)*(perc-1));
		else
			return Math.floor(col*perc);
	}
	
	set();
}

function init(){
	var ai=document.getElementsByClassName("aspect-item"),
		num=document.getElementsByClassName("number"),
		keys=["padding","aspectW","aspectH"],
		min=[0,1,1],
		cb=new Checkbox(document.getElementById("aspect-toggle"),function(state){
			for (var i=0;i<ai.length;i++)
				ai[i].disabled=!state;
			viewBox.showAspect=state;
			drawVisualGuides();
		});
	
	for (var i=0;i<num.length;i++){
		(function(inp,key,m){
			inp.value=viewBox[key];
			inp.oninput=function(evt){
				var val=this.value.replace(/[^0-9.]/g,"");
				if (val!=this.value)
					this.value=val;
				viewBox[key]=Math.max(Number(val) || 0,m);
				drawVisualGuides();
			};
			inp.onchange=function(){
				this.value=viewBox[key];
			};
		})(num[i],keys[i],min[i]);
	}
	
	cSliders[0].addEventListener("mousedown",startHueSet);
	cSliders[1].addEventListener("mousedown",startLumSet);
	
	var dm=new Checkbox(document.getElementById("dm-toggle"),function(state){
		var bcl=document.body.classList;
		if (state)
			bcl.add("dark-mode");
		else
			bcl.remove("dark-mode");
	});
	
	window.onresize=updateThumbSize;
	updateThumbSize();
	addLayer(new Layer());
}

String.prototype.format=function(){
	var out=this;
	for (var i=arguments.length-1;i>=0;i--)
		out=out.replace(new RegExp("\\$"+i,"g"),arguments[i]);
	return out;
}

function node(tag,cls,inner,attrs,evts){
	var node=(tag instanceof Node)?tag:document.createElement(tag);
	if (cls)
		node.setAttribute("class",cls);

	if (inner!==null&&inner!==undefined){
		if (!Array.isArray(inner))
			inner=[inner];
		for (var i=0;i<inner.length;i++){
			var n=inner[i];
			if (!(n instanceof Node))
				n=document.createTextNode(n);
			node.appendChild(n);
		}
	}

	if (attrs){
		for (var key in attrs){
			var a=attrs[key];
			if (typeof a=="object"){
				for (var k in a)
					node[key][k]=a[k];
			}else
				node.setAttribute(key,attrs[key]);
		}
	}

	if (evts){
		for (var evt in evts)
			node.addEventListener(evt,evts[evt]);
	}

	node.app=function(p){
		p.appendChild(this);
		return this;
	};

	node.pre=function(p){
		p.insertBefore(this,p.children[0] || null);
		return this;
	};

	node.before=function(e,par){
		(par || e.parentElement).insertBefore(this,e);
		return this;
	};

	node.after=function(e,par){
		(par || e.parentElement).insertBefore(this,e?e.nextElementSibling:null);
		return this;
	};

	return node;
}

function ns(tag,ns){
	var spaces={
			xhtml: "http://www.w3.org/1999/xhtml",
			svg: "http://www.w3.org/2000/svg",
			xbl: "http://www.mozilla.org/xbl",
			xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
		},
		url=spaces[ns] || spaces.svg;

	return document.createElementNS(url,tag);
}

function extendEvents(){
	var internalKCodeMap={
			8: "backspace",
			13: "enter",
			16: "shift",
			17: "control",
			18: "alt",
			20: "capslock",
			27: "escape",
			32: "space",
			35: "end",
			36: "home",
			37: "arrowleft",
			38: "arrowup",
			39: "arrowright",
			40: "arrowdown",
			46: "delete"
		};

	function isKey(evt,key,ctrl,alt,shift){
		if (ctrl&&!evt.ctrlKey)
			return false;
		if (alt&&!evt.altKey)
			return false;
		if (shift&&!evt.shiftKey)
			return false;

		key=key.toLowerCase();

		//Check most universal option
		if (evt.key)
			return evt.key.toLowerCase()==key;

		return getKey(evt)===key;
	}

	function getKey(evt){
		if (evt.key)
			return evt.key.toLowerCase();

		var kc=evt.which || evt.keyCode || evt.charCode || 0,
			kMap=internalKCodeMap[kc];

		if (kMap)
			return kMap;

		if (kc>=65&&kc<=90)
			return String.fromCharCode(kc+32);
		if (kc>=48&&kc<=57)
			return String.fromCharCode(kc);

		return null;
	}

	function getKeyCode(evt){
		return evt.which || evt.keyCode || evt.charCode || 0;
	}

	function isHotkey(evt,key){
		var ctrlTest=/c(on)?tro?l\+/gi,
			altTest=/alt\+/gi,
			shiftTest=/shift\+/gi,
			ctrl=false,
			alt=false,
			shift=false;

		if (ctrlTest.test(key)){
			ctrl=true;
			key=key.replace(ctrlTest,"");
		}

		if (altTest.test(key)){
			alt=true;
			key=key.replace(altTest,"");
		}

		if (shiftTest.test(key)){
			shift=true;
			key=key.replace(shiftTest,"");
		}

		return isKey(evt,key,ctrl,alt,shift);
	}

	Event.prototype.is=function(a){
		var name=this.constructor.name;

		switch (name){
			case "KeyboardEvent":
				return isHotkey(this,a);
		}
		return false;
	};

	Event.prototype.getKey=function(){
		var name=this.constructor.name;

		switch (name){
			case "KeyboardEvent":
				return getKey(this);
		}

		return null;
	};
}
extendEvents();

init();
setColorSel();
setEditMode("draw");

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.