Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <main>
	<div id="output"></div>
	<div class="controls">
		<section class="svg__opts">
			<h2 class="h --m">
				SVG
			</h2>
			<button class="js-generate svg__generate new" aria-label="generate new SVG">Generate</button>
			<div class="svg__current">
				<button class="js-dl svg__current-btn" aria-label="download current SVG"><svg width="100" height="100" id="icon-download" class="icon" viewBox="0 0 24 24" aria-hidden="true">
  <title>download</title>
  <path  class="download-bottom" d="M21 14c-0.6 0-1 0.4-1 1v4c0 0.6-0.4 1-1 1h-14c-0.6 0-1-0.4-1-1v-4c0-0.6-0.4-1-1-1s-1 0.4-1 1v4c0 1.7 1.3 3 3 3h14c1.7 0 3-1.3 3-3v-4c0-0.6-0.4-1-1-1z"></path>
  <path class="download-top" d="M11.3 15.7c0.1 0.1 0.2 0.2 0.3 0.2 0.1 0.1 0.3 0.1 0.4 0.1s0.3 0 0.4-0.1c0.1-0.1 0.2-0.1 0.3-0.2l5-5c0.4-0.4 0.4-1 0-1.4s-1-0.4-1.4 0l-3.3 3.3v-9.6c0-0.6-0.4-1-1-1s-1 0.4-1 1v9.6l-3.3-3.3c-0.4-0.4-1-0.4-1.4 0s-0.4 1 0 1.4l5 5z"></path>
</svg><span class="btn__text">download</span></button>
				<button class="js-copy svg__current-btn" data-clipboard-target="#svg-code" aria-label="copy markup for current SVG"><svg width="100" height="100" id="icon-code" class="icon" viewBox="0 0 30 28" aria-hidden="true">
  <title>code</title>
  <path d="M9.641 21.859l-0.781 0.781c-0.203 0.203-0.516 0.203-0.719 0l-7.281-7.281c-0.203-0.203-0.203-0.516 0-0.719l7.281-7.281c0.203-0.203 0.516-0.203 0.719 0l0.781 0.781c0.203 0.203 0.203 0.516 0 0.719l-6.141 6.141 6.141 6.141c0.203 0.203 0.203 0.516 0 0.719zM18.875 5.187l-5.828 20.172c-0.078 0.266-0.359 0.422-0.609 0.344l-0.969-0.266c-0.266-0.078-0.422-0.359-0.344-0.625l5.828-20.172c0.078-0.266 0.359-0.422 0.609-0.344l0.969 0.266c0.266 0.078 0.422 0.359 0.344 0.625zM29.141 15.359l-7.281 7.281c-0.203 0.203-0.516 0.203-0.719 0l-0.781-0.781c-0.203-0.203-0.203-0.516 0-0.719l6.141-6.141-6.141-6.141c-0.203-0.203-0.203-0.516 0-0.719l0.781-0.781c0.203-0.203 0.516-0.203 0.719 0l7.281 7.281c0.203 0.203 0.203 0.516 0 0.719z"></path>
</svg><span class="js-code btn__text">copy code</span></button>

			</div>
		</section>

		<section class="history">
			<header>
				<h2 class="h --m">
					History
				</h2>
				<p class="js-history history__state"><strong>1</strong> of <strong>1</strong></p>
			</header>
			<button class="js-prev hist__btn hist__btn-prev u-hidden" aria-label="previous svg in history">
      <span class="btn__text">
        prev
      </span>
    </button>

			<button class="js-next hist__btn hist__btn-next u-hidden" aria-label="next svg in history"><span class="btn__text">
    next
    </span></button>
		</section>
	</div>
</main>

<!-- hidden input to grab value of svg -->
<input type="text" id="svg-code" class="sr-only" tabindex="-1" aria-hidden="true">

<!-- updates changes for screenreader users updates  -->
<p class="js-status sr-only" aria-live="assertive"></p>
              
            
!

CSS

              
                @mixin pse($top: 0, $left: 0, $height: 100%, $width: 100%) {
	content: "";
	position: absolute;
	top: $top;
	left: $left;
	height: $height;
	width: $width;
}

@function h-btn-t($deg) {
	$transform: rotate($deg) skew($deg);

	@return $transform;
}

$accent: #1f3ee0;
$accent2: #f49fc4;
$accent2-lt: desaturate(lighten($accent2, 15%), 40%);
$prim: #39cbb3;
$tert: #f2694b;
$cement: #f3f4f5;

$ul-h: 0.15em;
$bar-w: 3em;
$mb: 1.5rem;

$bk: 45em;

// TRANSFORMS
$btn-a: translateY(2px);
$prev-d: 15deg;
$next-d: -15deg;

// TRANSITIONS
$t-t: transform 200ms ease-in-out;
$a-t: all 200ms ease-in-out;
// GENERAL

::selection {
	background-color: $accent;
	color: $cement;
}

::-moz-selection {
	background-color: $accent;
	color: $cement;
}

* {
	box-sizing: border-box;

	&:focus {
		outline: none;
	}
}

:root {
	font-size: calc(1rem + 0.2vw);
}

html {
	height: 100%;
}

body {
	position: relative;
	min-height: 100%;
	margin: 0;
	background-color: $cement;
	font-family: "Roboto Mono", monospace;

	@media screen and (min-width: $bk) {
		margin-left: $bar-w * 0.5;
	}

	&::before {
		@include pse($left: auto, $height: $bar-w);
		right: 0;

		background-color: $accent2-lt;
		z-index: -1;

		@media screen and (min-width: $bk) {
			height: 100%;
			width: $bar-w;
		}
	}
}

// HEADINGS

.h {
	margin-top: 0;
	margin-bottom: 1.5 + $ul-h;
	color: $accent;
	position: relative;
	max-width: max-content;
	text-align: left;
	font-family: "Poppins", sans-serif;

	&::before {
		@include pse($width: 50%, $height: $ul-h, $top: auto);
		bottom: -$ul-h;
		background-color: $prim;
	}

	&.--m {
		font-size: 1.4em;
	}
}

// BUTTONS

button {
	font-family: inherit;
	padding: 1em 2em;
	box-shadow: none;
	outline: none;
	border: none;
	background-color: transparent;
	-webkit-appearance: none;
	appearance: none;
	cursor: pointer;

	&:active {
		transform: $btn-a;
	}

	&:focus {
		box-shadow: inset 0 0 0 3px $accent2;
	}
}

.btn__text {
	margin-left: 0.5em;
	vertical-align: middle;
	display: inline-block;
}

.hist__btn {
	border: 2px solid $prim;
	font-size: 1rem;
	position: relative;
	margin: 0 0.5em;
	padding: 0.75em 1.5em;
	color: $accent;
	transition: background-color 400ms 300ms ease-in-out;

	@media screen and (min-width: $bk) {
		margin-bottom: 0;
	}

	&:focus,
	&:hover {
		box-shadow: none;

		&::before {
			transform: scaleX(1);
		}
	}

	&::before {
		@include pse();
		background-color: $accent2-lt;
		transform: scaleX(0);
		transition: $t-t;
	}

	&-prev {
		margin-bottom: $mb + 0.25;
		transform-origin: left center;
		transform: h-btn-t($prev-d);

		&::before {
			transform-origin: bottom right;
		}

		.btn__text {
			transform: h-btn-t($prev-d * -1);
		}

		&:active {
			transform: h-btn-t($prev-d) $btn-a;
		}

		@media screen and (min-width: $bk) {
			margin-bottom: 0;
		}
	}

	&-next {
		transform-origin: right center;
		transform: h-btn-t($next-d);

		&:active {
			transform: h-btn-t($next-d) $btn-a;
		}

		&::before {
			transform-origin: bottom left;
		}

		.btn__text {
			transform: h-btn-t($next-d * -1);
		}
	}

	.btn__text {
		margin: 0;
	}
}

.svg__generate {
	margin-bottom: $mb;
	max-width: 15em;
	color: #fff;
	letter-spacing: 2px;
	text-transform: uppercase;
	background-color: $accent;
	position: relative;
	overflow: hidden;

	&:hover {
		&::before {
			transform: rotate(25deg) translate(-10%, -20%) scaleX(1);
		}
	}

	@media screen and (min-width: $bk) {
		width: 100%;
		font-size: 1.1em;
	}

	&::before {
		@include pse($width: $bar-w * 0.75, $height: 150%);
		background-color: $accent2;
		transform: rotate(25deg) translate(-10%, -20%) scaleX(0);
		transition: $t-t;
		transform-origin: top left;
	}
}

.svg__current-btn {
	background-color: transparent;
	padding: 0.45em 0.75em;
	transition: $a-t;
	position: relative;

	&:hover {
		&::before {
			transform: scaleX(1);
		}
	}

	&::before {
		@include pse($height: $ul-h, $top: auto);
		bottom: $ul-h / 2;
		background-color: $prim;
		transform: scaleX(0);
		transform-origin: left;
		transition: $t-t;
	}

	.icon {
		color: $tert;
		font-size: 1.25em;
		transition: $a-t;
		will-change: transform;
	}

	&:hover,
	&:focus {
		.download-top {
			animation: bounce 1000ms alternate-reverse infinite linear;
		}
	}
}

// SECTIONS

main {
	min-height: 100vh;
	max-width: 1200px;
	margin: auto;
	padding: 1.5rem;

	> * {
		display: flex;
		justify-content: center;
	}

	@media screen and (min-width: $bk) {
		display: flex;
		flex-direction: row-reverse;
		align-items: center;
		justify-content: center;

		> * {
			margin-bottom: 0;
			flex: 1 1 0;
			flex-direction: column;
			align-items: center;
		}
	}
}

#output {
	margin-bottom: 3rem;
	@media screen and (min-width: $bk) {
		margin-bottom: 0;
	}
}

.controls {
	> * {
		width: 100%;
		text-align: center;
		margin: 0 0.25em;
	}

	@media screen and (min-width: $bk) {
		margin: 0 0 $mb;
	}
}

.svg__current {
	@media screen and (min-width: $bk) {
		display: flex;
		justify-content: center;
		margin-bottom: $mb * 1.5;
	}
}

.u-hidden {
	opacity: 0;
	visibility: hidden;
}

.history {
	> * {
		transition: $a-t;
	}

	header {
		display: flex;
		justify-content: center;
		flex-direction: column;

		@media screen and (min-width: $bk) {
			flex-direction: row;
			align-items: start;
		}
	}
}

.history__state {
	margin: 0 auto $mb / 2;
	max-width: 10em;
	padding: 0.25em 1em;
	background-color: $accent2-lt;

	@media screen and (min-width: $bk) {
		margin: 0 0 0 auto;
	}
}

// OUTPUT SVG

svg {
	width: 12em;
	height: auto;
}

// ICON BASE

.icon {
	display: inline-block;
	width: 1em;
	height: 1em;
	stroke-width: 0;
	stroke: currentColor;
	fill: currentColor;
	vertical-align: middle;
}

.download-bottom {
	color: $accent;
}

// ANIMATIONS

@keyframes bounce {
	0%,
	50% {
		transform: translateY(-5%);
	}

	25%,
	75% {
		transform: translateY(5%);
	}
}

// hide input
#svg-code {
	position: absolute;
	opacity: 0;
}

[hidden] {
	display: none;
}

// accessible way of hiding content
.sr-only {
	border: 0 !important;
	clip: rect(1px, 1px, 1px, 1px) !important;
	clip-path: inset(50%) !important;
	height: 1px !important;
	overflow: hidden !important;
	padding: 0 !important;
	position: absolute !important;
	width: 1px !important;
	white-space: nowrap !important;
	top: 0;
}

              
            
!

JS

              
                // based on this Dribbble shot https://dribbble.com/shots/4806195-SXSW-Featured-Events-Exploration

// globals
let size = 50;
let sw = 3;
let columns = 3;
let rows = 5;
const w = columns * size;
const h = rows * size;
const r = size / 2;
const geos = ["circle", "line"];
const colors = ["#f49fc4", "#39cbb3", "#f2694b", "#000400", "#1f3ee0"];
const cMax = 2; // max circles to allow on each SVG

let circles = 0; // circles generated in currentSVG
let color;
let svg;
let svgNS;

// regex
const shapesRegEx = /<(?:\/)?rect>?|<(?:\/)?line>?|<(?:\/)?circle>?/g;
const groupsRegEx = /<(?:\/)?g>/g;

// set up Clipboard.js
const clipboard = new ClipboardJS(".js-copy");

// creates each "box" in the SVG
const createGroup = (...args) => {
	const [x1, x2, y1, y2, col] = args;
	const g = document.createElementNS(svgNS, "g");
	let rect = createBox(x1, x2, y1, y2, col);
	let shape; // shape inside of box
	const random = Math.floor(Math.random() * 2);

	if (geos[random] === "circle" && circles < cMax && col % 2 === 0) {
		circles++;
		shape = createCircle(x1, y1);
	} else {
		shape = createLine(x1, x2, y1, y2, col);
	}

	svg.appendChild(g);
	g.appendChild(rect);
	g.appendChild(shape);
};

const createBox = (...args) => {
	const [x1, x2, y1, y2, col] = args;
	const rect = document.createElementNS(svgNS, "rect");
	rect.setAttribute("x", x1);
	rect.setAttribute("y", y1);
	rect.setAttribute("width", size);
	rect.setAttribute("height", size);
	rect.setAttribute("fill", "none");
	rect.setAttribute("stroke-width", sw);
	rect.setAttribute("stroke", color);

	return rect;
};

const createLine = (...args) => {
	const [x1, x2, y1, y2, col] = args;
	const line = document.createElementNS(svgNS, "line");
	line.setAttribute("x1", x1);
	line.setAttribute("y1", y1);
	line.setAttribute("x2", size + x1);
	line.setAttribute("y2", size + y1);
	line.setAttribute("stroke", color);
	line.setAttribute("stroke-width", sw);

	if (col % 2 === 0) {
		line.setAttribute("transform", `translate(${x1 + x2}, 0)scale(-1,1)`);
	}

	return line;
};

const createCircle = (x, y) => {
	const cx = r + x;
	const cy = r + y;

	const circle = document.createElementNS(svgNS, "circle");
	circle.setAttribute("cx", cx);
	circle.setAttribute("cy", cy);
	circle.setAttribute("r", r);
	circle.setAttribute("stroke", color);
	circle.setAttribute("stroke-width", sw);
	circle.setAttribute("fill", "none");

	return circle;
};

// reset each time a new SVG is generated
const resetGlobals = () => {
	color = colors[Math.floor(Math.random() * colors.length)];
	svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
	svgNS = svg.namespaceURI;
	circles = 0;
};

// create a new SVG
const generateSVG = () => {
	resetGlobals();
	const vals = Array.from({ length: rows * columns }, item =>
		Math.round(Math.random())
	);

	let row = 0;

	vals.forEach((val, i) => {
		const coords = [];
		const xOffset = i % columns;

		if ((i + 1) % columns === 0) {
			row++;
		}

		if (i % columns === 2) {
			coords.push(
				xOffset * size,
				size * (xOffset + 1),
				size * (row - 1),
				size * (row + 1)
			);
		} else {
			coords.push(
				xOffset * size,
				size * (xOffset + 1),
				size * row,
				size * (row + 1)
			);
		}

		if (val) {
			createGroup(...coords, i);
		}
	});

	svg.setAttribute("viewBox", `-1.5 -1.5 ${w + 10} ${h + 10}`);
	svg.setAttribute("width", w + 10);
	svg.setAttribute("height", h + 10);
	svg.setAttribute("id", `geoboxes${SVGs.list.length || ""}`);

	return svg;
};

// create SVG Obj
const createSVGObj = svg => {
	const serializer = new XMLSerializer();
	const doc = svg;
	let str = serializer.serializeToString(doc);

	str = str
		.replace(shapesRegEx, match => `\n\t\t${match}`)
		.replace(groupsRegEx, match => `\n\t${match}`)
		.replace(/<\/svg>/, match => `\n${match}`);

	return {
		id: `geoboxes-${SVGs.list.length || ""}`,
		str,
		svg
	};
};

const historyHandler = () => {
	const { current, list } = SVGs;
	setText(statusEl, `displaying generated SVG ${current + 1} of ${list.length}`);

	historyTxt.innerHTML = `<strong>${current + 1}</strong> of <strong>${
		list.length
	}</strong>`;

	if (current === 0) {
		prev.classList.add("u-hidden");
	} else {
		prev.classList.remove("u-hidden");
	}

	if (list.length < 2 || current === list.length - 1) {
		next.classList.add("u-hidden");
	} else {
		next.classList.remove("u-hidden");
	}
};

// on load/click of New BTN
const SVGHandler = () => {
	const svg = generateSVG();
	const svgObj = createSVGObj(svg);
	SVGs.setCurrent(SVGs.list.length);
	SVGs.list.push(svgObj);
	historyHandler();
	displaySVG(svg);
	input.value = svgObj.str;
	setText(statusEl, "new SVG generated");
};

const SVGs = {
	list: [],
	current: 0,
	setCurrent(i) {
		this.current = i;
		return this.list[i];
	},
	prev() {
		const prev = this.current === 0 ? 0 : this.current - 1;
		this.setCurrent(prev);

		return this.list[prev];
	},
	next() {
		const next =
			this.current === this.list.length - 1 ? this.current : this.current + 1;
		this.setCurrent(next);

		return this.list[next];
	}
};

// download SVG
const download = () => {
	const el = dlTxt;

	try {
		saveSvg();
		setText(el, "downloaded", "download");
		setText(statusEl, "download complete");
	} catch (e) {
		setText(el, "download failed", "download");
		setText(statusEl, "download failed");
	}
};

const saveSvg = () => {
	const svg = SVGs.list[SVGs.current];
	console.log(svg);

	const file = svg.svg;
	file.setAttribute("xmlns", "http://www.w3.org/2000/svg");

	const data = file.outerHTML;
	const preface = '<?xml version="1.0" standalone="no"?>\r\n';
	const blob = new Blob([preface, data], {
		type: "image/svg+xml;charset=utf-8"
	});
	const url = URL.createObjectURL(blob);
	const link = document.createElement("a");

	link.href = url;
	link.download = `${svg.id}geostack.svg`;

	document.body.appendChild(link);
	link.click();
	document.body.removeChild(link);
};
// DOM handling
const t = document.getElementById("output");
const genBtn = document.querySelector(".js-generate");
const prev = document.querySelector(".js-prev");
const next = document.querySelector(".js-next");
const dlBtn = document.querySelector(".js-dl");
const codeBtn = document.querySelector(".js-copy");
const dlTxt = dlBtn.querySelector(".btn__text");
const codeTxt = codeBtn.querySelector(".btn__text");
const input = document.getElementById("svg-code");
const historyTxt = document.querySelector(".js-history");
const statusEl = document.querySelector(".js-status");

const displaySVG = svg => {
	t.innerHTML = "";
	t.appendChild(svg);
};

const setText = (el, msg, end = "") => {
	el.textContent = msg;

	if (end) {
		setTimeout(() => {
			el.textContent = end;
		}, 1000);
	}
};

// handle success and error of code copy
clipboard.on("success", e => {
	setText(codeTxt, "copied", "copy code");
	setText(statusEl, "SVG code copied");
	e.clearSelection();
});

clipboard.on("error", e => {
	setText(codeTxt, "failed to copy", "copy code");
	setText(statusEl, "failed to copy SVG code");
});

genBtn.addEventListener("click", SVGHandler);

prev.addEventListener("click", () => {
	const prev = SVGs.prev();
	historyHandler();
	displaySVG(prev.svg);
});

next.addEventListener("click", () => {
	const next = SVGs.next();
	historyHandler();
	displaySVG(next.svg);
});

dlBtn.addEventListener("click", download);

if (!ClipboardJS.isSupported()) {
	codeBtn.setAttribute("hidden", "true");
}

SVGHandler();

              
            
!
999px

Console