<html>
	<head>
		<style>
			:root {
				--accent-color: #f35f5f;
				--text-dark: #333333;
				--text-light: #808080;
			}
			body {
				accent-color: var(--accent-color);
				background: #f9fafa;
				border: 0px;
				font-family: Montserrat, sans-serif;
				margin: 0px;
				padding: 0px;
				text-align: center;
			}
			input[type="file"] {
				display: none;
			}
			.accent {
				color: var(--accent-color);
				text-decoration: underline;
			}
			.center-left {
				display: inline-block;
				text-align: left;
			}
			.clickable:hover {
				cursor: pointer;
			}
			.content-container {
				border-bottom-left-radius: 32px;
				border-bottom-right-radius: 32px;
				color: var(--text-light);
				font-size: 16px;
				opacity: 1;
				overflow-y: hidden;
				padding: 24px 15%;
				transition: opacity 0.25s;
			}
			.content-container-button {
				background: var(--accent-color);
				border-radius: 16px;
				color: #ffffff;
				display: inline-block;
				padding: 16px 64px;
				transition: background 0.25s;
			}
			.content-container-button:hover {
				cursor: pointer;
			}
			.content-container-button.disabled {
				background: var(--text-light) !important;
			}
			.content-container-button.disabled:hover {
				cursor: not-allowed;
			}
			.content-container-title {
				color: var(--text-dark);
				font-size: 24px;
				font-weight: bold;
			}
			.icon-right-arrow {
				border: solid #ffffff;
				border-width: 0 2px 2px 0;
				display: inline-block;
				margin-left: 4px;
				padding: 4px;
				position: relative;
				transform: rotate(315deg);
			}
			.main-container {
				background: #ffffff;
				border-radius: 32px;
				margin-bottom: 64px;
				margin-left: 50%;
				margin-top: 64px;
				padding: 24px;
				transform: translateX(-50%);
				width: 50%;
			}
			.main-container img {
				height: 24px;
			}
			.title-container {
				color: var(--text-dark);
				font-size: 48px;
				font-weight: bold;
				padding: 24px;
			}
			.two-column-left {
				display: inline-block;
				text-align: right;
				width: 50%;
			}
			.two-column-right {
				display: inline-block;
				text-align: left;
				width: 50%;
			}
			.spacer {
				padding-top: 8px;
			}
			.spacer-dash {
				border-top: 8px solid var(--text-dark);
				margin: auto;
				width: 32px;
			}
			.spinner {
				display: inline-block;
				position: relative;
				width: 32px;
				height: 32px;
			}
			.spinner div {
				box-sizing: border-box;
				display: block;
				position: absolute;
				width: 32px;
				height: 32px;
				border: 4px solid var(--accent-color);
				border-radius: 50%;
				animation: spinner 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
				border-color: var(--accent-color) transparent transparent transparent;
			}
			.spinner div:nth-child(1) {
				animation-delay: -0.45s;
			}
			.spinner div:nth-child(2) {
				animation-delay: -0.3s;
			}
			.spinner div:nth-child(3) {
				animation-delay: -0.15s;
			}
			@keyframes spinner {
				0% {
					transform: rotate(0deg);
				}
				100% {
					transform: rotate(360deg);
				}
			}
		</style>
		<title>Versium File Hasher</title>
		<link rel="stylesheet" href="wstyle.css" />
	</head>
	<body>
		<div class="main-container">
			<div class="title-container">
				<img src="data:image/svg+xml;base64, PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTEuNzQiIGhlaWdodD0iMTI5LjUxIiB2aWV3Qm94PSIwIDAgNjkxLjc0IDEyOS41MSI+PGRlZnM+PHN0eWxlPi5he2ZpbGw6I2YzNWY1Zjt9LmJ7ZmlsbDojZmY3ZDdkO30uY3tmaWxsOiNmZmI2OGU7fS5ke2ZpbGw6IzMzMzt9PC9zdHlsZT48L2RlZnM+PHBhdGggY2xhc3M9ImEiIGQ9Ik05NywxMTEuMTNhMTIuNDIsMTIuNDIsMCwwLDEtMS45My0uMSwyMS42OSwyMS42OSwwLDAsMS0zMC4zNC00LjU2LDIwLjMxLDIwLjMxLDAsMCwxLTEuMzItMkwyNi4yNyw0MC4xOWExNi44MiwxNi44MiwwLDAsMSwwLTE3LjI3LDE2LjYzLDE2LjYzLDAsMCwxLDEzLjMyLTguNWwuMjktLjU4Yy4zOS0uNjcuODctMS4zNSwxLjM1LTJBMzAuODMsMzAuODMsMCwwLDEsNTEsMi41NUg0MS4yM0EyOS4wNiwyOS4wNiwwLDAsMCwxNiwxN2EyOC42NiwyOC42NiwwLDAsMCwwLDI5LjE1TDUzLjIsMTEwLjQ1YTMzLjUyLDMzLjUyLDAsMCwwLDU4LjEsMGwxLjkzLTMuNDdhMzMuNTgsMzMuNTgsMCwwLDEtMTMuMTIsNEM5OC45MywxMTEsOTgsMTExLjEzLDk3LDExMS4xM1oiLz48cGF0aCBjbGFzcz0iYiIgZD0iTTExMy45MSw4Ni43MWgtLjM5Yy0uNzcsMC0xLjU0LS4xLTIuMzEtLjFBMjgsMjgsMCwwLDEsMTA5LjQ3LDg5YTUuNTEsNS41MSwwLDAsMS0uNjguNzdjLS4zOC4zOS0uODcuNzctMS4yNSwxLjE2YTExLjY0LDExLjY0LDAsMCwxLTEuMjYsMSwxMS44MSwxMS44MSwwLDAsMS0yLjUzLDEuMzUsMTMuNjYsMTMuNjYsMCwwLDEtMS4zNS41OCwxMC44MiwxMC44MiwwLDAsMS0zLjE4LjY4LDE4LjMzLDE4LjMzLDAsMCwxLTIuMjIuMSwxNiwxNiwwLDAsMS02LjM3LTEuMjYsMTYuMjYsMTYuMjYsMCwwLDEtNy45Mi03TDU0LjI0LDM3LjJhNC4xLDQuMSwwLDAsMS0uNDgtLjg3LDEzLjkxLDEzLjkxLDAsMCwxLTEuMDYtMi44LDEzLjIzLDEzLjIzLDAsMCwxLS40OC0zLjg2QTEzLjM4LDEzLjM4LDAsMCwxLDUzLDI0Ljg1YTEwLDEwLDAsMCwxLDEuMjUtMi43MSwxNC44NiwxNC44NiwwLDAsMSw4LjIxLTYuNzUsMTUuMTcsMTUuMTcsMCwwLDEsMi44OS0uNjgsMy41MSwzLjUxLDAsMCwxLDEtLjA5Yy4zOCwwLC42Ny0uMSwxLS4xaDIuOUE3Ljg4LDcuODgsMCwwLDEsNzEsMTNhNS41MSw1LjUxLDAsMCwxLC4zOC0uNjdsLjEtLjE5LjEtLjEuMDktLjE5QTMwLjY0LDMwLjY0LDAsMCwxLDgxLDIuNDVINjcuMmEzMSwzMSwwLDAsMC02LjY2Ljc4Yy0uNTguMDktMS4wNy4yOS0xLjY0LjM4YTE2Ljc3LDE2Ljc3LDAsMCwwLTMsMS4xNmMtLjQ4LjE5LTEsLjQ4LTEuNDQuNjhBMzAuMTQsMzAuMTQsMCwwLDAsNTEsNy42N2wtMS4xNi44NmEzMC4yLDMwLjIsMCwwLDAtNSw1LjUxbC0xLjE1LDEuNzNhMjUuMDgsMjUuMDgsMCwwLDAtMi43MSw2LjY2LDIyLjc1LDIyLjc1LDAsMCwwLS42NywzLjQ4LDI3Ljg3LDI3Ljg3LDAsMCwwLDAsN2MuMDkuNTguMTksMS4xNS4zLDEuNzMuMTgsMS4xNi41NywyLjMyLjg3LDMuNDhhMzIuMjgsMzIuMjgsMCwwLDAsMS4zNSwzLjI4Yy4yOS41OC41OCwxLjA2Ljg3LDEuNjRMNzIuMTMsOTIuMjFBMjguNiwyOC42LDAsMCwwLDg3LjU3LDEwNWwxLjc0LjU4YTIyLjg5LDIyLjg5LDAsMCwwLDMuNjYuNjhjMS4yNi4wOSwyLjUxLjE5LDMuNzcuMTkuODcsMCwxLjgzLS4xLDIuNy0uMWEyOC4wNywyOC4wNywwLDAsMCwyMS45MS0xNC4wOWw1LTguNzhBMjYuNjIsMjYuNjIsMCwwLDEsMTEzLjkxLDg2LjcxWiIvPjxwYXRoIGNsYXNzPSJiIiBkPSJNNDUsMTQuMjNjLS4zOS41OC0uNzgsMS4xNi0xLjE1LDEuNzRDNDQuMjYsMTUuNDEsNDQuNjQsMTQuODMsNDUsMTQuMjNaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik01Mi40MywzMS40MWExMy44NywxMy44NywwLDAsMS0uMS0xLjkzQTYuNiw2LjYsMCwwLDAsNTIuNDMsMzEuNDFaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik0xMDguNTgsODkuOGMtLjM5LjM5LS44Ny43Ny0xLjI1LDEuMTZBNy42NSw3LjY1LDAsMCwwLDEwOC41OCw4OS44WiIvPjxwYXRoIGNsYXNzPSJiIiBkPSJNNTMuNzgsMzYuMTRhMTMuOTEsMTMuOTEsMCwwLDEtMS4wNi0yLjhBMjMuNjYsMjMuNjYsMCwwLDAsNTMuNzgsMzYuMTRaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik01Mi44MSwzMy40NGExNi4wNSwxNi4wNSwwLDAsMS0uMzgtMS45M0E3LjU5LDcuNTksMCwwLDAsNTIuODEsMzMuNDRaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik0xMDYuMTksOTEuODNhMTMuNSwxMy41LDAsMCwxLTIuNTEsMS4zNUE5LjIyLDkuMjIsMCwwLDAsMTA2LjE5LDkxLjgzWiIvPjxwYXRoIGNsYXNzPSJiIiBkPSJNODIuNzMsODYuMzMsNTQuMjYsMzcuMSw4Mi43Myw4Ni4zM2ExNi4zNCwxNi4zNCwwLDAsMCw3LjkyLDYuOTRBMTYuNjIsMTYuNjIsMCwwLDEsODIuNzMsODYuMzNaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik02Ny4zOSwxNC40MmEyLjg5LDIuODksMCwwLDAtMSwuMUEyLjg5LDIuODksMCwwLDEsNjcuMzksMTQuNDJaIi8+PHBhdGggY2xhc3M9ImIiIGQ9Ik02Mi40NywxNS4xOWExNiwxNiwwLDAsMSwyLjg5LS42N0ExNiwxNiwwLDAsMCw2Mi40NywxNS4xOVoiLz48cGF0aCBjbGFzcz0iYiIgZD0iTTUxLjE3LDcuNzZhMTkuNzUsMTkuNzUsMCwwLDEsMy40OC0yLjIyQzUzLjQ5LDYuMjIsNTIuMzMsNyw1MS4xNyw3Ljc2WiIvPjxwYXRoIGNsYXNzPSJiIiBkPSJNNTAuMTEsOC42M2EzMC40OCwzMC40OCwwLDAsMC01LDUuNWgwQTI3LDI3LDAsMCwxLDUwLjExLDguNjNaIi8+PHBhdGggY2xhc3M9ImMiIGQ9Ik0xNTMuMDksMjguOWExLjI4LDEuMjgsMCwwLDAtLjExLS41OCw2LjgyLDYuODIsMCwwLDEtLjEtMS4zNS4zNS4zNSwwLDAsMC0uMS0uMjksMjYuMzYsMjYuMzYsMCwwLDAtMy4zNy05Ljk0LjExLjExLDAsMCwwLS4xLS4xaDBhMjQuMzYsMjQuMzYsMCwwLDAtMy4xOS00LjQ0bC0uMTktLjE5Yy0uMS0uMTktLjI5LS4yOS0uMzgtLjQ4YTI1LjQxLDI1LjQxLDAsMCwwLTcuNDQtNS43OSwzMC44MSwzMC44MSwwLDAsMC0xMC4yOS0zLjE5LDM2LjMsMzYuMywwLDAsMC00LjU0LS4yOUg5N2ExMy43OSwxMy43OSwwLDAsMC0xLjkzLjEsMy43LDMuNywwLDAsMC0uNDguMDloLS4yYTI1LjEsMjUuMSwwLDAsMC0xOSwxMS43OC4zLjMsMCwwLDAtLjEuMTljLS4wOS4yLS4yOS40OC0uMzguNjgtLjI5LjU4LS41OCwxLjA2LS44NywxLjU0YTIxLjkyLDIxLjkyLDAsMCwwLTEsMi4yMmwtLjI5Ljg3YTExLjQ0LDExLjQ0LDAsMCwwLS40OCwxLjY0LDE0LDE0LDAsMCwwLS4zOCwxLjkzLDI1LjY2LDI1LjY2LDAsMCwwLC4zOCwxMS4yLDExLjgzLDExLjgzLDAsMCwwLC40OCwxLjY0LDI5LjUxLDI5LjUxLDAsMCwwLDIuMTMsNC43M0w5MS41LDY5LjYzYTI0Ljc5LDI0Ljc5LDAsMCwwLDE3LjA4LDEyLjE2Yy43Ny4xOSwxLjY0LjE5LDIuNDEuMzlhMTgsMTgsMCwwLDAsMi4zMi4wOWguMzlhMjQuNDcsMjQuNDcsMCwwLDAsMTguNzItOC44OC4wOS4wOSwwLDAsMSwuMS0uMDlsLjA5LS4xYy4xLS4xLjEtLjEuMS0uMTlhMjIuNDgsMjIuNDgsMCwwLDAsMi40MS0zLjQ4bDEzLjQyLTIzLjM1YTMxLjQ2LDMxLjQ2LDAsMCwwLDQuMjQtMTMuOXYtLjJhNi45Myw2LjkzLDAsMCwwLC4xLTEuNDR2LS41OEEyLjExLDIuMTEsMCwwLDAsMTUzLjA5LDI4LjlaTTEzOC4zMyw0MC4xOSwxMjQuOTEsNjMuNTVhMTMuMjIsMTMuMjIsMCwwLDEtMTEuMzksNi41NiwxMywxMywwLDAsMS0xMS4zOS02LjU2TDg1LjUzLDM0Ljc5YTEzLjcxLDEzLjcxLDAsMCwxLDAtMTMuNjEsMTMuNDQsMTMuNDQsMCwwLDEsMTAuODEtNi43NmMuMjksMCwuNTgtLjA5LDEtLjA5aDI2LjE1YTE2LjkzLDE2LjkzLDAsMCwxLDE0Ljg3LDguNTljMy4wNyw1LjQsMi44LDEzLjIyLDAsMTcuMjdaIi8+PHBhdGggY2xhc3M9ImQiIGQ9Ik0xOTYuMjQsMTA0LjE4LDE3OC40OSw1Ny41NmE4Ljg3LDguODcsMCwwLDEsNS4xLTExLjQ3LDguNzgsOC43OCwwLDAsMSwzLjE3LS41OWguNzdBOC45Miw4LjkyLDAsMCwxLDE5Niw1MS42OGwxNC4wOSw0My40MywxNC00My40M2E4LjkyLDguOTIsMCwwLDEsOC40OS02LjE4aDBhOC44Myw4LjgzLDAsMCwxLDguMywxMi4wNmwtMTcuNjYsNDYuNzJhMTQuNDcsMTQuNDcsMCwwLDEtMTMuNTEsOS4zNmgwQTE0Ljc4LDE0Ljc4LDAsMCwxLDE5Ni4yNCwxMDQuMThaIi8+PHBhdGggY2xhc3M9ImQiIGQ9Ik0zMDMuNjYsODUuMzZoLTM4QzI2OCw5NCwyNzQuNTIsOTkuMDcsMjgzLjIsOTkuMDdhMjMuOTEsMjMuOTEsMCwwLDAsMTIuNzQtMy42Nyw3LjMzLDcuMzMsMCwwLDEsOS4xNywxLjE2aDBBNy41Miw3LjUyLDAsMCwxLDMwNCwxMDhhNDAuMzUsNDAuMzUsMCwwLDEtMjIuMjksNi4xOGMtMjEuODEsMC0zNS42MS0xMy45LTM1LjYxLTM0LjU1LDAtMjEsMTQuMjgtMzQuOTQsMzUtMzQuOTQsMTkuNzksMCwzMC44OCwxMC44MSwzMy4xLDI4LjY2QTEwLjYsMTAuNiwwLDAsMSwzMDUsODUuMjcsMTAuMiwxMC4yLDAsMCwxLDMwMy42Niw4NS4zNlptLTcuNDMtMTEuNDljLS4yOS05LTYuMDgtMTQuNzYtMTUuMDUtMTQuNzYtOC41OSwwLTE0LjQ4LDUuNzktMTUuOTMsMTQuNzZaIi8+PHBhdGggY2xhc3M9ImQiIGQ9Ik0zNDYuOSw1Ny42NmEyNC44MiwyNC44MiwwLDAsMSwxMS40OS0xMC44MWM1LjIxLTIuMzIsMTEsMS43NCwxMSw3LjQzdjFhOC4xLDguMSwwLDAsMS03LjE0LDcuOTFjLTkuNjUsMS40NS0xNS40NCw4LjIxLTE1LjQ0LDE3LjU3djIzLjA2YTkuNzIsOS43MiwwLDAsMS05LjY5LDkuNzVoLS4wNmE5LjcyLDkuNzIsMCwwLDEtOS43NS05LjY5VjU1LjE1QTkuNzIsOS43MiwwLDAsMSwzMzcsNDUuNGguMDZhOS43Myw5LjczLDAsMCwxLDkuNzUsOS42OXYuMDZaIi8+PHBhdGggY2xhc3M9ImQiIGQ9Ik00MjYuMzMsNTguNjNsLS4xOS4yOWE3LjQ0LDcuNDQsMCwwLDEtOS40NiwzLjQ3LDM4LDM4LDAsMCwwLTE0LjU3LTMuNTdjLTQuNTQsMC03LjgyLDEuNTQtNy44Miw1LDAsMTAuMjMsMzYsNSwzNS44MSwyOS4xNCwwLDEzLjktMTIuMjYsMjEuMjQtMjcuNywyMS4yNGE1MCw1MCwwLDAsMS0yMy40NS01LjYsNy40MSw3LjQxLDAsMCwxLTMuMTktOS43NWgwYTcuMzUsNy4zNSwwLDAsMSw5LjgtMy40NWwuMTQuMDdBMzkuMTMsMzkuMTMsMCwwLDAsNDAzLDk5LjkzYzQuOTIsMCw4LjU5LTEuNjQsOC41OS01LjMsMC0xMC45MS0zNS4zMy01LTM1LjMzLTI5LDAtMTQuMDksMTIuMDctMjEuMjMsMjYuODMtMjEuMjNhNTIuMTMsNTIuMTMsMCwwLDEsMTkuNiw0LDcuNDYsNy40NiwwLDAsMSwzLjg2LDkuODFDNDI2LjQ3LDU4LjM1LDQyNi40LDU4LjQ5LDQyNi4zMyw1OC42M1oiLz48ZWxsaXBzZSBjbGFzcz0iYiIgY3g9IjQ1Mi45NyIgY3k9IjI2IiByeD0iMTAuNTIiIHJ5PSIxMC45MSIvPjxwYXRoIGNsYXNzPSJkIiBkPSJNNDQzLjIyLDEwMy43OVY1NS4xNWE5LjczLDkuNzMsMCwwLDEsOS42OS05Ljc1SDQ1M2E5LjcyLDkuNzIsMCwwLDEsOS43NCw5Ljd2NDguNjlhOS43MSw5LjcxLDAsMCwxLTkuNjksOS43NWgwQTkuNzksOS43OSwwLDAsMSw0NDMuMjIsMTAzLjc5WiIvPjxwYXRoIGNsYXNzPSJkIiBkPSJNNTM2LjQ2LDExMy41NGgwYTkuNjUsOS42NSwwLDAsMS05LjY1LTkuNjV2LTIuMzJjLTQuNzQsOC41OS0xMi41NSwxMi43NC0yMi43OCwxMi43NC0xNS4wNiwwLTI0LjUyLTEwLTI0LjUyLTI2VjU1LjE1YTkuNjUsOS42NSwwLDAsMSw5LjY1LTkuNjVoLjA5YTkuNjUsOS42NSwwLDAsMSw5LjY1LDkuNjVWODIuNjZjMCw4LjU5LDQuOTMsMTMuOCwxMi43NCwxMy44QzUyMSw5Ni4zNiw1MjYuNyw4OSw1MjYuNyw3OS4wOXYtMjRhOS42NSw5LjY1LDAsMCwxLDkuNjUtOS42NWgwQTkuNjUsOS42NSwwLDAsMSw1NDYsNTUuMDV2NDguNzRhOS41NSw5LjU1LDAsMCwxLTkuMzQsOS43NVoiLz48cGF0aCBjbGFzcz0iZCIgZD0iTTY3OS42OCw3MC42OXYzMy4xYTkuNjYsOS42NiwwLDAsMS05LjY1LDkuNjZoMGE5LjY2LDkuNjYsMCwwLDEtOS42NS05LjY2Vjc2LjM4YzAtOC43OC01LTEzLjg5LTEzLjEzLTEzLjg5LTkuMzYuMDktMTUsNy40My0xNSwxNy4zN3YyNGE5LjY1LDkuNjUsMCwwLDEtOS42NSw5LjY1aDBhOS42NSw5LjY1LDAsMCwxLTkuNjUtOS42NVY3Ni40OGMwLTguNzgtNS0xMy45LTEzLTEzLjktOS40Ni4xLTE1LjE2LDcuNDMtMTUuMTYsMTcuMzh2MjRhOS42NSw5LjY1LDAsMCwxLTkuNjUsOS42NUg1NzVhOS42Niw5LjY2LDAsMCwxLTkuNjYtOS42NVY1NS4zNEE5LjcyLDkuNzIsMCwwLDEsNTc1LDQ1LjZoLjA2YTkuNzIsOS43MiwwLDAsMSw5Ljc0LDkuN3YyLjA3YzQuODMtOC40LDEyLjc0LTEyLjQ1LDIzLjA4LTEyLjU1LDExLjQ4LDAsMTkuNzgsNS43OSwyMy4wNiwxNS43NEM2MzUuMzgsNTAsNjQ0LDQ1LDY1NS40Niw0NC44Miw2NzAuMTMsNDQuNjMsNjc5LjY4LDU0Ljc3LDY3OS42OCw3MC42OVoiLz48L3N2Zz4=" alt="Versium Logo" />
				<div class="spacer"></div>
				File Hasher
			</div>
			<div class="spacer-dash"></div>
			<div class="content-container" id="content-container">
			</div>
		</div>
		<input type="file" id="file-input">
		<script src="hash.js"></script>
		<script src="simple-tagger.js"></script>
		<script src="logic.js"></script>
		<script>
			step0(true);
		</script>
	</body>
	<input type="file" id="file-input">
	<script>
		const hashingFunctions = {	
			md5: function(str, textEncoder = null) {
				const T = new Uint32Array([
					0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
					0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
					0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
					0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
					0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
					0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
					0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
					0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
					0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
					0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
					0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
					0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
					0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
					0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
					0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
					0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
				]);
				const shift = new Uint8Array([
					7, 12, 17, 22,
					7, 12, 17, 22,
					7, 12, 17, 22,
					7, 12, 17, 22,
					5,	9, 14, 20,
					5,	9, 14, 20,
					5,	9, 14, 20,	
					5,	9, 14, 20,
					4, 11, 16, 23,
					4, 11, 16, 23,
					4, 11, 16, 23,
					4, 11, 16, 23,
					6, 10, 15, 21,
					6, 10, 15, 21,
					6, 10, 15, 21,
					6, 10, 15, 21
				]);
				const blocks = new Uint32Array(Math.ceil(str.length / 64) * 16);
				let i;
				for (i = 0; i < str.length; i++) {
					blocks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
				}
				blocks[i >> 2] |= 0x80 << ((i % 4) * 8);
				blocks[blocks.length - 2] = str.length * 8;
				let a = 0x67452301;
				let b = 0xefcdab89;
				let c = 0x98badcfe;
				let d = 0x10325476;
				for (i = 0; i < blocks.length; i += 16) {
					const aa = a;
					const bb = b;
					const cc = c;
					const dd = d;
					for (let j = 0; j < 64; j++) {
						const idx = j < 16 ? j : (j % 16);
						const f = j < 16 ? ((b & c) | (~b & d)) :
							j < 32 ? ((d & b) | (~d & c)) :
							j < 48 ? (b ^ c ^ d) :
							(c ^ (b | ~d));
						const g = j < 16 ? j :
							j < 32 ? (5 * idx + 1) % 16 :
							j < 48 ? (3 * idx + 5) % 16 :
							(7 * idx) % 16;
						const tmp = d;
						d = c;
						c = b;
						b = b + rotateLeft((a + f + T[j] + blocks[i + g]), shift[j]);
						a = tmp;
					}
					a += aa;
					b += bb;
					c += cc;
					d += dd;
				}
				const result = new Uint32Array([a, b, c, d]);
				const hex = Array.from(new Uint8Array(result.buffer))
					.map(b => b.toString(16).padStart(2, '0'))
					.join('');
				return hex;
				function rotateLeft(n, s) {
					return (n << s) | (n >>> (32 - s));
				}
			},
			sha1: async function(str, textEncoder) {
				var hashBuffer = await window.crypto.subtle.digest("SHA-1", textEncoder.encode(str));
				let hashArray = Array.from(new Uint8Array(hashBuffer));
				return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
			},
			sha256: async function(str, textEncoder) {
				var hashBuffer = await window.crypto.subtle.digest("SHA-256", textEncoder.encode(str));
				let hashArray = Array.from(new Uint8Array(hashBuffer));
				return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
			}
		};
	</script>
	<script>
		function generateHashedSimpleTags(first, last, address, zip, tagFormat, tagLength, bits, encoding) {
			var numTags = 0;
			if (address && zip) {
				numTags = 1;
				if (last) {
					numTags = 2;
					if (first) numTags = 3;
				}
			} else return [];
			var addressTag = generateStAddrHelper(address, tagFormat, tagLength);
			zip = generateStZipHelper(zip);
			var toReturn = [
				hashInt64(zip + ':::' + addressTag, bits, encoding)
			];
			if (numTags > 1) {
				last = generateStNameHelper(last);
				toReturn[1] = hashInt64(zip + ':' + last + '::' + addressTag, bits, encoding);
				if (numTags > 2) {
					first = generateStNameHelper(first);
					toReturn[2] = hashInt64(zip + ':' + last + ':' + first + ':' + addressTag, bits, encoding);
				}
			}
			return toReturn;
			function generateStAddrHelper(address, tagFormat, tagLength) {
				var addrChunkZeroWeights = [
					'POB', 'PMB', 'BOX', 'ST', 'STE', 'AVE', 'AV', 'DR', 'RD', 'APT', 
					'LN', 'N', 'E', 'S', 'W', 'NW', 'NE', 'SE', 'SW', 'BLVD', 'CIR', 'WA', 'WAY', 
					'CT', 'PKWY', 'RR', 'UNIT', 'TER'
				];
				var numDecode = {
					zero: '0',
					one: '1',
					two: '2',
					three: '3',
					four: '4',
					five: '5',
					six: '6',
					seven: '7',
					eight: '8',
					nine: '9',
					ten: '10',
					eleven: '11',
					twelve: '12',
					thirteen: '13',
					fourteen: '14',
					fifteen: '15',
					sixteen: '16',
					seventeen: '17',
					eighteen: '18',
					nineteen: '19',
					twenty: '20',
					thirty: '30',
					forty: '40',
					fifty: '50',
					sixty: '60',
					seventy: '70',
					eighty: '80',
					ninety: '90',
					first: '1st',
					second: '2nd',
					third: '3rd',
					fourth: '4th',
					fifth: '5th',
					sixth: '6th',
					seventh: '7th',
					eighth: '8th',
					nineth: '9th',
					tenth: '10th',
					eleventh: '11th',
					twelfth: '12th',
					thirteenth: '13th',
					fourteenth: '14th',
					fifteenth: '15th',
					sixteenth: '16th',
					seventeenth: '17th',
					eighteenth: '18th',
					nineteenth: '19th',
					twentieth: '20th',
				};	
				address = address.toUpperCase().replace(/[^0-9A-Za-z ]/g, '').trim();
				var tokWeight = [];
				var toks = address.split(' ');
				var cc = toks.length;
				for (var i = 0; i < cc; i++) {
					toks[i] = toks[i].trim();
					if (tagFormat == 1) {
						if (numDecode[toks[i].toLowerCase()]) toks[i] = numDecode[toks[i].toLowerCase()];
					}
					tokWeight[i] = toks[i].length;
					if (/^\d$/.test(toks[i].charAt(0))) tokWeight[i] = ((tokWeight[i] + 3) * 3) / 2;
					if (addrChunkZeroWeights.includes(toks[i])) tokWeight[i] = 0;
				}
				for (var i = 0; i < cc; i++) {
					for (var j = i + 1; j < cc; j++) {
						if (tokWeight[j] > tokWeight[i]) {
							var k = tokWeight[i];
							var p = toks[i];
							tokWeight[i] = tokWeight[j];
							toks[i] = toks[j];
							tokWeight[j] = k;
							toks[j] = p;
						}
					}
				}
				return toks.join(' ').toUpperCase().replace(/[^0-9A-Z]/g, '').substring(0, tagLength);	
			}
			function generateStNameHelper(s) {
				return s.toUpperCase().replace(/[^A-Z]/g, '');
			}
			function generateStZipHelper(s) {
				s = s.replace(/[^0-9]/g, '').padStart(5, '0');
				if (s.length > 5) s = s.substring(0, 5);
				return s;
			}
			function hashInt64(x, bits = 64, encoding = 0) {
				var md5 = hashingFunctions.md5(x.trim().toLowerCase());
				var x1 = md5.substring(0, 8);
				var x2 = md5.substring(8, 16);
				var x3 = md5.substring(16, 24);
				var x4 = md5.substring(24, 32);
				var i1 = BigInt(parseInt(x1, 16));
				var i2 = BigInt(parseInt(x2, 16));
				var i3 = BigInt(parseInt(x3, 16));
				var i4 = BigInt(parseInt(x4, 16));
				var overflow = 2n ** 63n;
				i1 = leftShiftOverflow(i1, 48n, overflow);
				i2 = leftShiftOverflow(i2, 32n, overflow);
				i3 = leftShiftOverflow(i3, 16n, overflow);
				var i = i1 ^ i2 ^ i3 ^ i4;
				if (i < 0) i *= -1n;
				if (bits == 32) i = i % BigInt(parseInt('7FFFFFED', 16));
				else if (bits == 16) i = i % BigInt(parseInt('FFF1', 16));
				else if (bits == 8) i = i % BigInt(parseInt('FB', 16));
				else i = i % BigInt(parseInt('7FFFFFFFFFFFE96B', 16));
				if (encoding == 1) return i.toString(16);
				else if (encoding == 2) return btoa(i.toString());
				else return i.toString();
			}
			function leftShiftOverflow(n, shift, overflow) {
				n = n * (2n ** shift);
				var sign = n / overflow;
				if (sign % 2n) return (n % overflow) - overflow;
				else return n % overflow;
			}
		}
	</script>
	<script>
		var cryptoAvailable = false;
		if (window.crypto && window.crypto.subtle) cryptoAvailable =  true;
		function emptyGlobalVariables() {
			inputInfo = {
				delimiter: '',
				first: '',
				last: '',
				address: '',
				zip: ''
			};
			outputInfo = {
				capitalization: 1,
				trim: 1,
				hashSalt: '',
				tagFormat: 1,
				tagLength: 8,
				hashLength: 64,
				hashEncoding: 0
			}
			header = {};
			output = '';
			errors = 0;
			fileInputId = 'file-input';
			fileInput = document.getElementById(fileInputId);
			contentContainerId = 'content-container';
			contentContainer = document.getElementById(contentContainerId);
			chooseOptions = 1;
			hashingAlgorithm = 'md5';
		}
		async function step0(pageLoad = false) {
			if (!pageLoad) await clearContentContainer();
			emptyGlobalVariables();
			fileInput.value = null;
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'File Selection';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			var fileInputLabel = document.createElement('label');
			fileInputLabel.className = 'accent clickable';
			fileInputLabel.htmlFor = 'file-input';
			fileInputLabel.innerHTML = 'Click here';
			content.appendChild(fileInputLabel);
			content.innerHTML += ' to select a file to hash.<br>Note that you must select a file that has headers.';
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var nextButton = createNextButton();
			nextButton.classList.add('disabled');
			nextButton.onclick = function() { 
				if (this.classList.contains('disabled')) return;
				step1();
			};
			contentContainer.appendChild(nextButton);
			fileInput.addEventListener ('change', () => {
				if (fileInput.value) {
					header = getFileHeader();
					nextButton.classList.remove('disabled');
				} else nextButton.classList.add('disabled');
			});
		}
		async function step1() {
			await clearContentContainer();
			var hashingAlgorithmInputId = 'input-hashalgorithm';
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'Hashing Algorithm';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			content.innerHTML = 'What hashing algorithm would you like to use?';
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			var hashingAlgorithmButtonsDiv = document.createElement('div');
			hashingAlgorithmButtonsDiv.className = 'center-left';
			var radioButtonMd5 = document.createElement('input');
			radioButtonMd5.id = hashingAlgorithmInputId + '-md5';
			radioButtonMd5.name = hashingAlgorithmInputId;
			radioButtonMd5.type = 'radio';
			radioButtonMd5.value = 'md5';
			radioButtonMd5.checked = true;
			var radioButtonMd5Label = document.createElement('label');
			radioButtonMd5Label.htmlFor = hashingAlgorithmInputId + '-md5';
			radioButtonMd5Label.innerHTML = 'MD5';
			hashingAlgorithmButtonsDiv.appendChild(radioButtonMd5);
			hashingAlgorithmButtonsDiv.appendChild(radioButtonMd5Label);
			hashingAlgorithmButtonsDiv.appendChild(document.createElement('br'));
			var radioButtonSha1 = document.createElement('input');
			radioButtonSha1.id = hashingAlgorithmInputId + '-sha1';
			radioButtonSha1.name = hashingAlgorithmInputId;
			radioButtonSha1.onclick = () => { alert('SHA-1 is a complex hashing algorithm.  Large files may take several minutes to complete hashing.'); };
			radioButtonSha1.type = 'radio';
			radioButtonSha1.value = 'sha1';
			radioButtonSha1.checked = false;
			radioButtonSha1.disabled = !cryptoAvailable;
			var radioButtonSha1Label = document.createElement('label');
			radioButtonSha1Label.htmlFor = hashingAlgorithmInputId + '-sha1';
			radioButtonSha1Label.innerHTML = 'SHA-1';
			hashingAlgorithmButtonsDiv.appendChild(radioButtonSha1);
			hashingAlgorithmButtonsDiv.appendChild(radioButtonSha1Label);
			hashingAlgorithmButtonsDiv.appendChild(document.createElement('br'));
			var radioButtonSha256 = document.createElement('input');
			radioButtonSha256.id = hashingAlgorithmInputId + '-sha256';
			radioButtonSha256.name = hashingAlgorithmInputId;
			radioButtonSha256.onclick = () => { alert('SHA-256 is a complex hashing algorithm.  Large files may take several minutes to complete hashing.'); };
			radioButtonSha256.type = 'radio';
			radioButtonSha256.value = 'sha256';
			radioButtonSha256.checked = false;
			radioButtonSha256.disabled = !cryptoAvailable;
			var radioButtonSha256Label = document.createElement('label');
			radioButtonSha256Label.htmlFor = hashingAlgorithmInputId + '-sha256';
			radioButtonSha256Label.innerHTML = 'SHA-256';
			hashingAlgorithmButtonsDiv.appendChild(radioButtonSha256);
			hashingAlgorithmButtonsDiv.appendChild(radioButtonSha256Label);
			contentContainer.appendChild(hashingAlgorithmButtonsDiv);
			contentContainer.appendChild(createSpacer());
			if (!cryptoAvailable) {
				var noCryptoButton = document.createElement('div');
				noCryptoButton.className = 'accent clickable';
				noCryptoButton.innerHTML = 'Why can\'t I select SHA-1 or SHA-256?';
				noCryptoButton.onclick = () => {
					alert('SHA-1 and SHA-256 hashing is only available on web browsers that support the web crypto library.  Try a browser like Chrome, Firefox or Edge to use these hashing algorithms.');
				};
				contentContainer.appendChild(noCryptoButton);
			}
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var nextButton = createNextButton();
			nextButton.onclick = function() { 
				hashingAlgorithm = getRadioButtonValue(hashingAlgorithmInputId);
				step2();
			};
			contentContainer.appendChild(nextButton);
		}
		async function step2() {
			await clearContentContainer();
			var chooseOptionsInputId = 'input-chooseoptions';
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'Hashing Customization';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			content.innerHTML = 'Would you like to customize how your file is hashed?';
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			var chooseOptionsButtonsDiv = document.createElement('div');
			chooseOptionsButtonsDiv.className = 'center-left';
			var radioButtonDefaultOptions = document.createElement('input');
			radioButtonDefaultOptions.id = chooseOptionsInputId + '-no';
			radioButtonDefaultOptions.name = chooseOptionsInputId;
			radioButtonDefaultOptions.type = 'radio';
			radioButtonDefaultOptions.value = '0';
			radioButtonDefaultOptions.checked = true;
			var radioButtonDefaultOptionsLabel = document.createElement('label');
			radioButtonDefaultOptionsLabel.htmlFor = chooseOptionsInputId + '-no';
			radioButtonDefaultOptionsLabel.innerHTML = 'Use default hashing options';
			chooseOptionsButtonsDiv.appendChild(radioButtonDefaultOptions);
			chooseOptionsButtonsDiv.appendChild(radioButtonDefaultOptionsLabel);
			chooseOptionsButtonsDiv.appendChild(document.createElement('br'));
			var radioButtonChooseOptions = document.createElement('input');
			radioButtonChooseOptions.id = chooseOptionsInputId + '-yes';
			radioButtonChooseOptions.name = chooseOptionsInputId;
			radioButtonChooseOptions.type = 'radio';
			radioButtonChooseOptions.value = '1';
			var radioButtonChooseOptionsLabel = document.createElement('label');
			radioButtonChooseOptionsLabel.htmlFor = chooseOptionsInputId + '-yes';
			radioButtonChooseOptionsLabel.innerHTML = 'Choose my hashing options';
			chooseOptionsButtonsDiv.appendChild(radioButtonChooseOptions);
			chooseOptionsButtonsDiv.appendChild(radioButtonChooseOptionsLabel);
			contentContainer.appendChild(chooseOptionsButtonsDiv);	
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var nextButton = createNextButton();
			nextButton.onclick = function() { 
				chooseOptions = parseInt(getRadioButtonValue(chooseOptionsInputId));
				step3();
			};
			contentContainer.appendChild(nextButton);
		}
		async function step3() {
			var numCsvFields = 0;
			var numTsvFields = 0;
			var splitFileName = fileInput.files[0].name.split('.');
			if (splitFileName[splitFileName.length - 1].toLowerCase() == 'csv') {
				numCsvFields = 1;
				numTsvFields = 0;
			} else if (splitFileName[splitFileName.length - 1].toLowerCase() == 'tsv') {
				numCsvFields = 0;
				numTsvFields = 1;
			} else {
				numCsvFields = parseLine(header, ',', false).length;
				numTsvFields = parseLine(header, '\t', false).length;
			}
			if (!chooseOptions && numCsvFields != numTsvFields && numCsvFields != 0) {
				if (numCsvFields > numTsvFields) inputInfo.delimiter = ',';
				else inputInfo.delimiter = '\t';
				header = parseLine(header, inputInfo.delimiter, false);
				step4();
			} else {
				await clearContentContainer();
				var delimiterInputId = 'input-delimiter';
				var title = document.createElement('div');
				title.className = 'content-container-title';
				title.innerHTML = 'File Type';
				contentContainer.appendChild(title);
				contentContainer.appendChild(createSpacer());
				var content = document.createElement('div');
				content.innerHTML += 'What type of file is this?';
				contentContainer.appendChild(content);
				contentContainer.appendChild(createSpacer());
				var radioButtonsDiv = document.createElement('div');
				radioButtonsDiv.className = 'center-left';
				var radioButtonCSV = document.createElement('input');
				radioButtonCSV.id = delimiterInputId + '-csv';
				radioButtonCSV.name = delimiterInputId;
				radioButtonCSV.type = 'radio';
				radioButtonCSV.value = ',';
				radioButtonCSV.checked = (numCsvFields > numTsvFields);
				var radioButtonCSVLabel = document.createElement('label');
				radioButtonCSVLabel.htmlFor = delimiterInputId + '-csv';
				radioButtonCSVLabel.innerHTML = 'CSV';
				radioButtonsDiv.appendChild(radioButtonCSV);
				radioButtonsDiv.appendChild(radioButtonCSVLabel);
				radioButtonsDiv.appendChild(document.createElement('br'));
				var radioButtonTSV = document.createElement('input');
				radioButtonTSV.id = delimiterInputId + '-tsv';
				radioButtonTSV.name = delimiterInputId;
				radioButtonTSV.type = 'radio';
				radioButtonTSV.value = '\t';
				radioButtonTSV.checked = (numTsvFields > numCsvFields);
				var radioButtonTSVLabel = document.createElement('label');
				radioButtonTSVLabel.htmlFor = delimiterInputId + '-tsv';
				radioButtonTSVLabel.innerHTML = 'TSV';
				radioButtonsDiv.appendChild(radioButtonTSV);
				radioButtonsDiv.appendChild(radioButtonTSVLabel);
				contentContainer.appendChild(radioButtonsDiv);
				contentContainer.appendChild(createSpacer());
				contentContainer.appendChild(createSpacer());
				contentContainer.appendChild(createSpacer());
				var nextButton = createNextButton();
				if (!((numCsvFields > numTsvFields) || (numTsvFields > numCsvFields))) nextButton.classList.add('disabled');
				nextButton.onclick = function() { 
				if (this.classList.contains('disabled')) return;
					inputInfo.delimiter = getRadioButtonValue(delimiterInputId);
					header = parseLine(header, inputInfo.delimiter, false);
					step4();
				};
				contentContainer.appendChild(nextButton);
				document.getElementsByName(delimiterInputId).forEach(e => {
					e.addEventListener('change', function () {
						nextButton.classList.remove('disabled');
					});
				});
			}
		}
		async function step4() {
			var fieldGuesses = {
				first: { index: undefined, guesses: 0 },
				last: { index: undefined, guesses: 0 },
				address: { index: undefined, guesses: 0 },
				zip: { index: undefined, guesses: 0 },
			}
			for (var i = 0; i < header.length; i++) {
				var guesses = 0;
				var guess = '';
				if (header[i].match(/.*first.*/i)) {
					guesses++;
					guess = 'first';
				}
				if (header[i].match(/.*last.*/i)) {
					guesses++;
					guess = 'last';
				}
				if (header[i].match(/^(postal|mailing|mail|street|corp|owner)?\s?addr(ess)?$/i)) {
					guesses++;
					guess = 'address';
				}
				if (header[i].match(/.*(zip|postal\scode).*/i)) {
					guesses++;
					guess = 'zip';
				}
				if (guesses == 1) {
					fieldGuesses[guess].index = i;
					fieldGuesses[guess].guesses++;
				}
			}
			Object.keys(fieldGuesses).forEach(k => {
				if (fieldGuesses[k].guesses != 1) fieldGuesses[k] = false;
			});
			if (!chooseOptions) {
				Object.keys(fieldGuesses).forEach(k => {
					if (fieldGuesses[k]) inputInfo[k] = fieldGuesses[k].index;
					else inputInfo[k] = '-1';
				});
				step6();
			} else {
				await clearContentContainer();
				var relevantFields = {
					first: 'First name',
					last: 'Last name',
					address: 'Address',
					zip: 'Zip'
				};
				var headerInputId = 'input-header';
				var title = document.createElement('div');
				title.className = 'content-container-title';
				title.innerHTML = 'File Header';
				contentContainer.appendChild(title);
				contentContainer.appendChild(createSpacer());
				var content = document.createElement('div');
				content.innerHTML += 'What columns in your file correspond to these field types?  Identifying these allows us to add Versium SimpleTags to your output file.';
				contentContainer.appendChild(content);
				contentContainer.appendChild(createSpacer());
				var twoColumnContainer = document.createElement('div');
				Object.keys(relevantFields).forEach(k => {
					var twoColumnLeft = document.createElement('div');
					twoColumnLeft.className = 'two-column-left';
					twoColumnLeft.innerHTML = relevantFields[k] + ':&nbsp;&nbsp;';
					twoColumnContainer.appendChild(twoColumnLeft);
					var twoColumnRight = document.createElement('div');
					twoColumnRight.className = 'two-column-right';
					var twoColumnRightInput = document.createElement('select');
					twoColumnRightInput.id = headerInputId + '-' + k;
					twoColumnRightInput.name = headerInputId;
					var defaultOption = document.createElement('option');
					defaultOption.innerHTML = 'None';
					defaultOption.selected = !fieldGuesses[k];
					defaultOption.value = -1;
					twoColumnRightInput.appendChild(defaultOption);
					for (var i = 0; i < header.length; i++) {
						var optionElement = document.createElement('option');
						optionElement.innerHTML = header[i];
						optionElement.value = i;
						if (fieldGuesses[k] && fieldGuesses[k].index == i) optionElement.selected = true;
						twoColumnRightInput.appendChild(optionElement);
					}
					twoColumnRight.appendChild(twoColumnRightInput);
					twoColumnContainer.appendChild(twoColumnRight);
					twoColumnContainer.appendChild(document.createElement('br'));
				});
				contentContainer.appendChild(twoColumnContainer);
				contentContainer.appendChild(createSpacer());
				contentContainer.appendChild(createSpacer());
				contentContainer.appendChild(createSpacer());
				var nextButton = createNextButton();
				nextButton.onclick = function() { 
					if (this.classList.contains('disabled')) return;
					Object.keys(relevantFields).forEach(k => {
						inputInfo[k] = document.getElementById(headerInputId + '-' + k).value;
					});
					step5();
				};
				contentContainer.appendChild(nextButton);
			}
		}
		async function step5() {
			await clearContentContainer();
			var capitalizationInputId = 'input-capitalization';
			var trimInputId = 'input-trim';
			var hashSaltInputId = 'input-hashsalt';
			var tagFormatInputId = 'input-tagformat';
			var tagLengthInputId = 'input-taglength';
			var hashLengthInputId = 'input-hashlength';
			var hashEncodingInputId = 'input-hashencoding';
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'Options';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			content.innerHTML += 'Are there any options you\'d like to select for your output file?';
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var optionsDiv = document.createElement('div');
			optionsDiv.className = 'center-left';
				var capitalizationDiv = document.createElement('div');
				capitalizationDiv.innerHTML = 'Edit capitalization:';
				capitalizationDiv.appendChild(document.createElement('br'));
					var radioLowerCase = document.createElement('input');
					radioLowerCase.checked = true;
					radioLowerCase.id = capitalizationInputId + '-lower';
					radioLowerCase.name = capitalizationInputId;
					radioLowerCase.type = 'radio';
					radioLowerCase.value = 1;
					var radioLowerCaseLabel = document.createElement('label');
					radioLowerCaseLabel.htmlFor = capitalizationInputId + '-lower';
					radioLowerCaseLabel.innerHTML = 'Lowercase';
					capitalizationDiv.appendChild(radioLowerCase);
					capitalizationDiv.appendChild(radioLowerCaseLabel);
					capitalizationDiv.appendChild(document.createElement('br'));
					var radioUpperCase = document.createElement('input');
					radioUpperCase.id = capitalizationInputId + '-upper';
					radioUpperCase.name = capitalizationInputId;
					radioUpperCase.type = 'radio';
					radioUpperCase.value = 2;
					var radioUpperCaseLabel = document.createElement('label');
					radioUpperCaseLabel.htmlFor = capitalizationInputId + '-upper';
					radioUpperCaseLabel.innerHTML = 'Uppercase';
					capitalizationDiv.appendChild(radioUpperCase);
					capitalizationDiv.appendChild(radioUpperCaseLabel);
					capitalizationDiv.appendChild(document.createElement('br'));
					var radioNoCapitalization = document.createElement('input');
					radioNoCapitalization.id = capitalizationInputId + '-none';
					radioNoCapitalization.name = capitalizationInputId;
					radioNoCapitalization.type = 'radio';
					radioNoCapitalization.value = 0;
					var radioNoCapitalizationLabel = document.createElement('label');
					radioNoCapitalizationLabel.htmlFor = capitalizationInputId + '-none';
					radioNoCapitalizationLabel.innerHTML = 'Maintain capitalization';
					capitalizationDiv.appendChild(radioNoCapitalization);
					capitalizationDiv.appendChild(radioNoCapitalizationLabel);
				optionsDiv.appendChild(capitalizationDiv);
				optionsDiv.appendChild(createSpacer());
				optionsDiv.appendChild(createSpacer());
				var trimDiv = document.createElement('div');
				trimDiv.innerHTML = 'Trim fields:';
				trimDiv.appendChild(document.createElement('br'));
					var radioTrim = document.createElement('input');
					radioTrim.checked = true;
					radioTrim.id = trimInputId + '-trim';
					radioTrim.name = trimInputId;
					radioTrim.type = 'radio';
					radioTrim.value = 1;
					var radioTrimLabel = document.createElement('label');
					radioTrimLabel.htmlFor = trimInputId + '-trim';
					radioTrimLabel.innerHTML = 'Trim leading/trailing whitespace';
					trimDiv.appendChild(radioTrim);
					trimDiv.appendChild(radioTrimLabel);
					trimDiv.appendChild(document.createElement('br'));
					var radioNoTrim = document.createElement('input');
					radioNoTrim.id = trimInputId + '-notrim';
					radioNoTrim.name = trimInputId;
					radioNoTrim.type = 'radio';
					radioNoTrim.value = 0;
					var radioNoTrimLabel = document.createElement('label');
					radioNoTrimLabel.htmlFor = trimInputId + '-notrim';
					radioNoTrimLabel.innerHTML = 'Don\'t trim leading/trailing whitespace';
					trimDiv.appendChild(radioNoTrim);
					trimDiv.appendChild(radioNoTrimLabel);
				optionsDiv.appendChild(trimDiv);
			contentContainer.appendChild(optionsDiv);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var advancedOptionsButton = document.createElement('div');
			advancedOptionsButton.className = 'accent clickable';
			advancedOptionsButton.innerHTML = 'Show advanced options';
			advancedOptionsButton.onclick = () => {
				var advancedOptionsDiv = document.getElementById('advanced-options');
				if (advancedOptionsDiv.style.display == 'none') advancedOptionsDiv.style.display = 'inline-block';
				else advancedOptionsDiv.style.display = 'none';
			};
			contentContainer.appendChild(advancedOptionsButton);
			var advancedOptionsDiv = document.createElement('div');
			advancedOptionsDiv.className = 'center-left';
			advancedOptionsDiv.id = 'advanced-options';
			advancedOptionsDiv.style.display = 'none';
				advancedOptionsDiv.appendChild(createSpacer());
				advancedOptionsDiv.appendChild(createSpacer());
				var hashSaltDiv = document.createElement('div');
				hashSaltDiv.innerHTML = 'Hash salt (this doesn\'t apply to Versium SimpleTags)';
				hashSaltDiv.appendChild(document.createElement('br'));
					var inputHashSalt = document.createElement('input');
					inputHashSalt.id = hashSaltInputId;
					inputHashSalt.name = hashSaltInputId;
					inputHashSalt.type = 'text';
					inputHashSalt.value = '';
					inputHashSalt.placeholder = 'Salt to be prepended';
					hashSaltDiv.appendChild(inputHashSalt);
				advancedOptionsDiv.appendChild(hashSaltDiv);
				advancedOptionsDiv.appendChild(createSpacer());
				advancedOptionsDiv.appendChild(createSpacer());
				var tagFormatDiv = document.createElement('div');
				tagFormatDiv.innerHTML = 'Versium SimpleTag format:';
				tagFormatDiv.appendChild(document.createElement('br'));
					var radioDecode = document.createElement('input');
					radioDecode.checked = true;
					radioDecode.id = tagFormatInputId + '-decode';
					radioDecode.name = tagFormatInputId;
					radioDecode.type = 'radio';
					radioDecode.value = 1;
					var radioDecodeLabel = document.createElement('label');
					radioDecodeLabel.htmlFor = tagFormatInputId + '-decode';
					radioDecodeLabel.innerHTML = 'Decode numbers (default)';
					tagFormatDiv.appendChild(radioDecode);
					tagFormatDiv.appendChild(radioDecodeLabel);
					tagFormatDiv.appendChild(document.createElement('br'));
					var radioNoDecode = document.createElement('input');
					radioNoDecode.id = tagFormatInputId + '-nodecode';
					radioNoDecode.name = tagFormatInputId;
					radioNoDecode.type = 'radio';
					radioNoDecode.value = 0;
					var radioNoDecodeLabel = document.createElement('label');
					radioNoDecodeLabel.htmlFor = tagFormatInputId + '-nodecode';
					radioNoDecodeLabel.innerHTML = 'Do not decode numbers';
					tagFormatDiv.appendChild(radioNoDecode);
					tagFormatDiv.appendChild(radioNoDecodeLabel);
				advancedOptionsDiv.appendChild(tagFormatDiv);
				advancedOptionsDiv.appendChild(createSpacer());
				advancedOptionsDiv.appendChild(createSpacer());
				var tagLengthDiv = document.createElement('div');
				tagLengthDiv.innerHTML = 'Versium SimpleTag address length:';
				tagLengthDiv.appendChild(document.createElement('br'));
					var radioEight = document.createElement('input');
					radioEight.checked = true;
					radioEight.id = tagLengthInputId + '-eight';
					radioEight.name = tagLengthInputId;
					radioEight.type = 'radio';
					radioEight.value = 8;
					var radioEightLabel = document.createElement('label');
					radioEightLabel.htmlFor = tagLengthInputId + '-eight';
					radioEightLabel.innerHTML = 'Eight (default)';
					tagLengthDiv.appendChild(radioEight);
					tagLengthDiv.appendChild(radioEightLabel);
					tagLengthDiv.appendChild(document.createElement('br'));
					var radioTen = document.createElement('input');
					radioTen.id = tagLengthInputId + '-ten';
					radioTen.name = tagLengthInputId;
					radioTen.type = 'radio';
					radioTen.value = 10;
					var radioTenLabel = document.createElement('label');
					radioTenLabel.htmlFor = tagLengthInputId + '-ten';
					radioTenLabel.innerHTML = 'Ten';
					tagLengthDiv.appendChild(radioTen);
					tagLengthDiv.appendChild(radioTenLabel);
					tagLengthDiv.appendChild(document.createElement('br'));
					var radioTwelve = document.createElement('input');
					radioTwelve.id = tagLengthInputId + '-twelve';
					radioTwelve.name = tagLengthInputId;
					radioTwelve.type = 'radio';
					radioTwelve.value = 12;
					var radioTwelveLabel = document.createElement('label');
					radioTwelveLabel.htmlFor = tagLengthInputId + '-twelve';
					radioTwelveLabel.innerHTML = 'Twelve';
					tagLengthDiv.appendChild(radioTwelve);
					tagLengthDiv.appendChild(radioTwelveLabel);
				advancedOptionsDiv.appendChild(tagLengthDiv);
				advancedOptionsDiv.appendChild(createSpacer());
				advancedOptionsDiv.appendChild(createSpacer());
				var hashLengthDiv = document.createElement('div');
				hashLengthDiv.innerHTML = 'Versium SimpleTag hash length:';
				hashLengthDiv.appendChild(document.createElement('br'));
					var radio64Bit = document.createElement('input');
					radio64Bit.checked = true;
					radio64Bit.id = hashLengthInputId + '-64bit';
					radio64Bit.name = hashLengthInputId;
					radio64Bit.type = 'radio';
					radio64Bit.value = 64;
					var radio64BitLabel = document.createElement('label');
					radio64BitLabel.htmlFor = hashLengthInputId + '-64bit';
					radio64BitLabel.innerHTML = '64 Bit (default)';
					hashLengthDiv.appendChild(radio64Bit);
					hashLengthDiv.appendChild(radio64BitLabel);
					hashLengthDiv.appendChild(document.createElement('br'));
					var radio32Bit = document.createElement('input');
					radio32Bit.id = hashLengthInputId + '-32bit';
					radio32Bit.name = hashLengthInputId;
					radio32Bit.type = 'radio';
					radio32Bit.value = 32;
					var radio32BitLabel = document.createElement('label');
					radio32BitLabel.htmlFor = hashLengthInputId + '-32bit';
					radio32BitLabel.innerHTML = '32 Bit';
					hashLengthDiv.appendChild(radio32Bit);
					hashLengthDiv.appendChild(radio32BitLabel);
					hashLengthDiv.appendChild(document.createElement('br'));
					var radio16Bit = document.createElement('input');
					radio16Bit.id = hashLengthInputId + '-16bit';
					radio16Bit.name = hashLengthInputId;
					radio16Bit.type = 'radio';
					radio16Bit.value = 16;
					var radio16BitLabel = document.createElement('label');
					radio16BitLabel.htmlFor = hashLengthInputId + '-16bit';
					radio16BitLabel.innerHTML = '16 Bit';
					hashLengthDiv.appendChild(radio16Bit);
					hashLengthDiv.appendChild(radio16BitLabel);
					hashLengthDiv.appendChild(document.createElement('br'));
					var radio8Bit = document.createElement('input');
					radio8Bit.id = hashLengthInputId + '-8bit';
					radio8Bit.name = hashLengthInputId;
					radio8Bit.type = 'radio';
					radio8Bit.value = 8;
					var radio8BitLabel = document.createElement('label');
					radio8BitLabel.htmlFor = hashLengthInputId + '-8bit';
					radio8BitLabel.innerHTML = '8 Bit';
					hashLengthDiv.appendChild(radio8Bit);
					hashLengthDiv.appendChild(radio8BitLabel);
				advancedOptionsDiv.appendChild(hashLengthDiv);
				advancedOptionsDiv.appendChild(createSpacer());
				advancedOptionsDiv.appendChild(createSpacer());
				var hashEncodingDiv = document.createElement('div');
				hashEncodingDiv.innerHTML = 'Versium SimpleTag hash encoding:';
				hashEncodingDiv.appendChild(document.createElement('br'));
					var radioDecimal = document.createElement('input');
					radioDecimal.checked = true;
					radioDecimal.id = hashEncodingInputId + '-decimal';
					radioDecimal.name = hashEncodingInputId;
					radioDecimal.type = 'radio';
					radioDecimal.value = 0;
					var radioDecimalLabel = document.createElement('label');
					radioDecimalLabel.htmlFor = hashEncodingInputId + '-decimal';
					radioDecimalLabel.innerHTML = 'Decimal (default)';
					hashEncodingDiv.appendChild(radioDecimal);
					hashEncodingDiv.appendChild(radioDecimalLabel);
					hashEncodingDiv.appendChild(document.createElement('br'));
					var radioHex = document.createElement('input');
					radioHex.id = hashEncodingInputId + '-hex';
					radioHex.name = hashEncodingInputId;
					radioHex.type = 'radio';
					radioHex.value = 1;
					var radioHexLabel = document.createElement('label');
					radioHexLabel.htmlFor = hashEncodingInputId + '-hex';
					radioHexLabel.innerHTML = 'Hex';
					hashEncodingDiv.appendChild(radioHex);
					hashEncodingDiv.appendChild(radioHexLabel);
					hashEncodingDiv.appendChild(document.createElement('br'));
					var radioBase64 = document.createElement('input');
					radioBase64.id = hashEncodingInputId + '-base64';
					radioBase64.name = hashEncodingInputId;
					radioBase64.type = 'radio';
					radioBase64.value = 2;
					var radioBase64Label = document.createElement('label');
					radioBase64Label.htmlFor = hashEncodingInputId + '-base64';
					radioBase64Label.innerHTML = 'Base64';
					hashEncodingDiv.appendChild(radioBase64);
					hashEncodingDiv.appendChild(radioBase64Label);
				advancedOptionsDiv.appendChild(hashEncodingDiv);
			contentContainer.appendChild(advancedOptionsDiv);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var nextButton = createNextButton('Finish');
			nextButton.onclick = async function() {
				outputInfo.capitalization = Number(getRadioButtonValue(capitalizationInputId));
				outputInfo.trim = Number(getRadioButtonValue(trimInputId));
				outputInfo.hashSalt = document.getElementById(hashSaltInputId).value;
				outputInfo.tagFormat = Number(getRadioButtonValue(tagFormatInputId));
				outputInfo.tagLength = Number(getRadioButtonValue(tagLengthInputId));
				outputInfo.hashLength = Number(getRadioButtonValue(hashLengthInputId));
				outputInfo.hashEncoding = Number(getRadioButtonValue(hashEncodingInputId));
				step6();
			};
			contentContainer.appendChild(nextButton);
		}
		async function step6() {
			await clearContentContainer();
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'Hashing...';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			content.innerHTML += 'We\'re processing your file.  It will download as soon as we\'re done with it!  Depending on the size of your file and your selected hashing algorithm, this may take several minutes.';
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var loadingSpinnerDiv = document.createElement('div');
			loadingSpinnerDiv.className = 'spinner';
			var emptyDiv = document.createElement('div');
			for (var i = 0; i < 4; i++) loadingSpinnerDiv.appendChild(emptyDiv.cloneNode(true));
			contentContainer.appendChild(loadingSpinnerDiv);
			var reader = new FileReader();
			reader.fileName = fileInput.files[0].name;
			reader.onload = async function (progressEvent) {
				var delimiter = inputInfo.delimiter;
				var additionalHeaderFields = [];
				if (inputInfo.address >= 0 && inputInfo.zip >= 0) {
					additionalHeaderFields = ['VERSIUM STADDR HASHED'];
					if (inputInfo.last >= 0) {
						additionalHeaderFields.push('VERSIUM STHHLD HASHED');
						if (inputInfo.first >= 0) additionalHeaderFields.push('VERSIUM ST10 HASHED');
					}
				}
				var outputHeader = header.concat(additionalHeaderFields);
				output = outputHeader.join(delimiter) + '\n';
				var textEncoder = new TextEncoder('utf-8');
				var inputLines = this.result.split('\n');
				for (var i = 1; i < inputLines.length; i++) {
					if (inputLines[i].trim() == '') continue;
					if (outputInfo.capitalization == 1) inputLines[i] = inputLines[i].toLowerCase();
					else if (outputInfo.capitalization == 2) inputLines[i] = inputLines[i].toUpperCase();
					var parsedLine = parseLine(inputLines[i], delimiter);
					if (parsedLine.length != header.length) {
						output += 'Malformatted line\n';
						errors++;
						continue;
					}
					var outputLine = parsedLine.slice();
					for (var j = 0; j < outputLine.length; j++) {
						if (outputInfo.trim) outputLine[j] = outputLine[j].trim();
						outputLine[j] = await hashingFunctions[hashingAlgorithm](outputInfo.hashSalt + outputLine[j], textEncoder);
					}
					var appendedData = await generateHashedSimpleTags(parsedLine[inputInfo.first], parsedLine[inputInfo.last], parsedLine[inputInfo.address], parsedLine[inputInfo.zip], outputInfo.tagFormat, outputInfo.tagLength, outputInfo.hashLength, outputInfo.hashEncoding);
					outputLine = outputLine.concat(appendedData);
					output += outputLine.join(delimiter) + '\n';
				}
				download(appendToFilename(this.fileName, '_versium'), output);
				step7();
			};
			reader.readAsText(fileInput.files[0]);
		}
		async function step7() {
			await clearContentContainer();
			var title = document.createElement('div');
			title.className = 'content-container-title';
			title.innerHTML = 'All done';
			contentContainer.appendChild(title);
			contentContainer.appendChild(createSpacer());
			var content = document.createElement('div');
			content.innerHTML += 'Your file has been processed and is downloading now!';
			if (errors) {
				content.appendChild(createSpacer());
				content.innerHTML += 'There were ' + errors + ' errors or malformatted lines in your input file.  These lines are noted in the output file as "Malformatted line".';
			}
			contentContainer.appendChild(content);
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			contentContainer.appendChild(createSpacer());
			var nextButton = createNextButton('Start Over');
			nextButton.onclick = function() { 
				if (this.classList.contains('disabled')) return;
				step0();
			};
			contentContainer.appendChild(nextButton);
		}
		function appendToFilename(filename, string){
			var dotIndex = filename.lastIndexOf(".");
			if (dotIndex == -1) return filename + string;
			else return filename.substring(0, dotIndex) + string + filename.substring(dotIndex);
		} 
		async function clearContentContainer() {
			contentContainer.style.opacity = 0;
			await new Promise(r => setTimeout(r, 500));
			contentContainer.innerHTML = '';
			contentContainer.style.opacity = 1;
		}
		function createNextButton(innerText = 'Next') {
			var nextButton = document.createElement('div');
			nextButton.className = 'content-container-button';
			nextButton.innerHTML = innerText;
			var icon = document.createElement('i');
			icon.className = 'icon-right-arrow';
			nextButton.appendChild(icon);
			return nextButton;
		}
		function createSpacer() {
			var spacer = document.createElement('div');
			spacer.className = 'spacer';
			return spacer;
		}
		function download(filename, contents) {
			var e = document.createElement('a');
			e.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(contents));
			e.setAttribute('download', filename);
			e.style.display = 'none';
			document.body.appendChild(e);
			e.click();
			document.body.removeChild(e);
		}
		function getFileHeader() {
			var reader = new FileReader();
			reader.onload = function (progressEvent) {
				header = reader.result.split('\n').shift();
			}
			reader.readAsText(fileInput.files[0]);
		}
		function getRadioButtonValue(name) {
			return document.querySelector('input[name="' + name + '"]:checked').value;
		}
		function parseLine(line, delimiter = ',') {
			var insideQuote = false,
				entries = [],
				entry = [];
			line = line.trimEnd();
			line.split('').forEach (c => {
				if (c === '"') {
					insideQuote = !insideQuote;
				} else {
					if (c == delimiter && !insideQuote) {
						entries.push(entry.join(''));
						entry = [];
					} else {
						entry.push(c);
					}
				}
			});
			entries.push(entry.join(''));
			return entries;
		}
	</script>
	<script>
		step0(true);
	</script>
</body>