diff --git a/content/index.html b/content/index.html
index 88ffb1942326e0355ec9a65c460ed28dff81d339..54231a8773c4d2b2d69a3296f6c3adb7b5d86b21 100644
--- a/content/index.html
+++ b/content/index.html
@@ -61,7 +61,7 @@
 		</header>
 
 		<main>
-			<div id="content">
+			<div id="step1">
 				<p id="intro-text">
 					Již za dva měsíce nás čekají prezidentské volby a Piráti znovu nabízí možnost, jak si vyřídit voličský průkaz - tedy potvrzení, že můžeš volit i na jiném místě, než v trvalém bydlišti.<br>
 					Vyplň náš jednoduchý formulář, stáhni si ho v PDF a pošli ho datovkou nebo dones osobně na úřad. Neboj se - vážíme si tvého soukromí, nesbíráme žádná osobní data a vše zpracovává tvůj prohlížeč.
@@ -73,22 +73,25 @@
 							type="text"
 							id="name"
 							placeholder="Jméno *"
+							value="Tomáš"
 						>
 						<input
 							type="text"
 							id="surname"
 							placeholder="Příjmení *"
+							value="Černohorský"
 						>
 					</div>
 					
 					<div class="input-single-with-label">
 						<label
-							for="birthday"
+							for="birth-date"
 						>Datum narození *</label>
 						<input
 							type="date"
-							id="birthday"
-							name="birthday"
+							id="birth-date"
+							name="birth-date"
+							value="2004-04-25"
 						>
 					</div>
 					
@@ -97,13 +100,13 @@
 					<div
 						id="address-autocomplete"
 						aria-role="textbox"
-						class="autocomplete-container"
 					></div>
 					
 					<input
 						type="text"
 						id="street"
 						placeholder="Ulice, popisné a orientační č. *"
+						value="Svojsíkova 145"
 					>
 					
 					<div class="input-group input-group-2">
@@ -111,12 +114,14 @@
 							type="text"
 							id="city"
 							placeholder="Obec *"
+							value="Krupka"
 						>
 						
 						<input
 							type="text"
 							id="zip"
 							placeholder="PSČ *"
+							value="41501"
 						>
 					</div>
 					
@@ -126,9 +131,17 @@
 						<option>Vyzvednout osobně</option>
 						<option>Předat osobě, která se prokáže plnou mocí</option>
 						<option>Zaslat na adresu trvalého pobytu</option>
-						<option>Zaslat jinam</option>
+						<option selected>Zaslat jinam</option>
 					</select>
 					
+					<input
+						type="text"
+						id="other-address-conditional"
+						placeholder="Doručovací adresa *"
+						style="display:none"
+						value="Svojsíkova 13, 415 01 Teplice"
+					>
+					
 					<select
 						id="card-type"
 					>
@@ -146,7 +159,7 @@
 				</div>
 			</div>
 			
-			<div id="canvas-wrapper">
+			<div id="step2">
 				<canvas id="page-1-canvas" width="2480" height="3507"></canvas>
 				<canvas id="page-2-canvas" width="2480" height="3507"></canvas>
 			</div>
diff --git a/content/static/css/base.css b/content/static/css/base.css
index 81ca82d58ee94b8928007d79e3022951e64adb94..ee1e20831351714105fd67bbb95571c0ab8c70e1 100644
--- a/content/static/css/base.css
+++ b/content/static/css/base.css
@@ -72,15 +72,20 @@ textarea {
 	align-items:center
 }
 
-#content {
+#step1 {
 	max-width:1000px;
 	margin-left:auto;
 	margin-right:auto;
 	padding-top:40px
 }
 
-#canvas-wrapper {
-	display:none
+#step2 {
+	display:none;
+	object-fit:contain
+}
+
+#step2 canvas {
+	width:100%
 }
 
 #intro-text {
@@ -96,11 +101,13 @@ textarea {
 	gap:14px;
 	max-width:750px;
 	margin-left:auto;
-	margin-right:auto
+	margin-right:auto;
+	padding-bottom:20px
 }
 
 #address-autocomplete {
-	position:relative
+	position:relative;
+	text-align:center
 }
 
 #form-preview {
@@ -133,11 +140,3 @@ textarea {
 .input-single-with-label input {
 	flex:1 1 0px
 }
-
-.geoapify-autocomplete-input {
-	border:0;
-	font-size:medium;
-	height:42px;
-	line-height:42px;
-	width:100%
-}
diff --git a/content/static/css/geo-autocomplete.css b/content/static/css/geo-autocomplete.css
index c8032523ef252c2f32d901000a47ac66a7fbf963..8b88514aa040993354dca9272e3a2796fa823e08 100644
--- a/content/static/css/geo-autocomplete.css
+++ b/content/static/css/geo-autocomplete.css
@@ -1,14 +1,14 @@
 .geoapify-autocomplete-input {
-    padding: 0 31px 0 7px;
-    width: calc(100% - 40px);
+    padding: 0 31px 0 10px;
+    width: calc(100% - 41px);
 
     outline: none;
 
-    line-height: 36px;
-    height: 36px;
-    border: 1px solid rgba(0, 0, 0, 0.3);
+    line-height: 42px;
+    height: 42px;
+    border: 0;
 
-    font-size: 14px;
+    font-size: medium;
 }
 
 .geoapify-autocomplete-items {
diff --git a/content/static/images/check.webp b/content/static/images/check.webp
new file mode 100644
index 0000000000000000000000000000000000000000..500f994425ac08aa512430d2f01690d65af422b1
Binary files /dev/null and b/content/static/images/check.webp differ
diff --git a/content/static/images/page2.webp b/content/static/images/page2.webp
index 6b910c07f94a57ad09fea519e312771f2791b636..0f6ccfcdb9a7bc0751d9b45661337f4701199feb 100644
Binary files a/content/static/images/page2.webp and b/content/static/images/page2.webp differ
diff --git a/content/static/js/main.js b/content/static/js/main.js
index df8ad4be773c16b395f348f3c7245767646a4213..8c906bc89b31022e287facad64952174c4263e87 100644
--- a/content/static/js/main.js
+++ b/content/static/js/main.js
@@ -1,5 +1,308 @@
 const AUTOCOMPLETE_API_KEY = "a17b542575f34d8795f90a23d650349f";
 
+const getImage = async (url) => {
+	const image = new Image();
+	
+	const imageLoadPromise = new Promise(
+		resolve => {
+			image.onload = () => {
+				resolve();
+			}
+			
+			image.src = url;
+		}
+	);
+	
+	await imageLoadPromise;
+	
+	return image;
+}
+
+const setHighestPossibleFontSize = (
+	context,
+	text,
+	font,
+	desiredSize,
+	maxWidth
+) => {
+	let currentSize = desiredSize;
+	
+	context.font = `${currentSize}px ${font}`;
+	
+	while (context.measureText(text).width > maxWidth) {
+		currentSize -= 1;
+		context.font = `${currentSize}px ${font}`;
+	}
+	
+	return desiredSize;
+}
+
+const createCheck = async (
+	context,
+	canvas,
+	x,
+	y
+) => {
+	const checkImage = await getImage("static/images/check.webp");
+	
+	const size = canvas.width * 0.0215;
+	
+	context.drawImage(
+		checkImage,
+		x, y,
+		size, size
+	);
+}
+
+const fillCanvas = async () => {
+	const firstPageCanvas = document.getElementById("page-1-canvas");
+	const firstPageContext = firstPageCanvas.getContext("2d");
+	
+	const secondPageCanvas = document.getElementById("page-2-canvas");
+	const secondPageContext = secondPageCanvas.getContext("2d");
+	
+	const firstPageImage = await getImage("static/images/page1.webp");
+	const secondPageImage = await getImage("static/images/page2.webp");
+	
+	firstPageContext.drawImage(
+		firstPageImage,
+		0, 0,
+		firstPageCanvas.width, firstPageCanvas.height
+	);
+	
+	secondPageContext.drawImage(
+		secondPageImage,
+		0, 0,
+		secondPageImage.width, secondPageImage.height
+	);
+	
+	// BEGIN First page
+	
+	// Local office name
+	const officeName = $("#city").val();
+	
+	setHighestPossibleFontSize(
+		firstPageContext,
+		officeName,
+		"Open Sans",
+		firstPageCanvas.height * 0.015,
+		firstPageCanvas.width * 0.76
+	);
+	
+	firstPageContext.fillStyle = "#000";
+	firstPageContext.fillText(
+		officeName,
+		firstPageCanvas.width * 0.12, firstPageCanvas.height * 0.205
+	);
+	
+	// We're always doing the presidential one, so always make this check
+	const checkboxSharedY = firstPageCanvas.width * 0.12;
+	
+	await createCheck(
+		firstPageContext,
+		firstPageCanvas,
+		checkboxSharedY,
+		firstPageCanvas.height * 0.3665
+	);
+	
+	const roundCheckSharedY = firstPageCanvas.width * 0.185;
+	let electionDate = "";
+	
+	switch ($("#card-type").val()) {
+		case "1. kolo": 
+			createCheck(
+				firstPageContext,
+				firstPageCanvas,
+				roundCheckSharedY,
+				firstPageCanvas.height * 0.3853
+			);
+			
+			electionDate = "13. ledna 2023 - 14. ledna 2023";
+			
+			break;
+		case "2. kolo":
+			createCheck(
+				firstPageContext,
+				firstPageCanvas,
+				roundCheckSharedY,
+				firstPageCanvas.height * 0.404
+			);
+			
+			electionDate = "27. ledna 2023 - 28. ledna 2023";
+			
+			break;
+	}
+	
+	const personalInfoSharedX = firstPageCanvas.width * 0.37;
+	
+	// Election dates
+	
+	setHighestPossibleFontSize(
+		firstPageContext,
+		electionDate,
+		"Open Sans",
+		firstPageCanvas.height * 0.013,
+		firstPageCanvas.width * 0.51
+	);
+	
+	firstPageContext.fillText(
+		electionDate,
+		personalInfoSharedX, firstPageCanvas.height * 0.505
+	);
+	
+	// Name
+	
+	const fullName = (
+		$("#name").val()
+		+ " "
+		+ $("#surname").val()
+	);
+	
+	setHighestPossibleFontSize(
+		firstPageContext,
+		fullName,
+		"Open Sans",
+		firstPageCanvas.height * 0.013,
+		firstPageCanvas.width * 0.51
+	);
+	
+	firstPageContext.fillText(
+		fullName,
+		personalInfoSharedX, firstPageCanvas.height * 0.535
+	);
+	
+	// Birth date
+	
+	const birthDate = new Date($("#birth-date").val());
+	const formattedBirthDate = (
+		birthDate.getDate()
+		+ ". "
+		+ birthDate.getMonth()
+		+ ". "
+		+ birthDate.getFullYear()
+	);
+	
+	setHighestPossibleFontSize(
+		firstPageContext,
+		formattedBirthDate,
+		"Open Sans",
+		firstPageCanvas.height * 0.013,
+		firstPageCanvas.width * 0.51
+	);
+	
+	firstPageContext.fillText(
+		formattedBirthDate,
+		personalInfoSharedX, firstPageCanvas.height * 0.565
+	);
+	
+	// Street + number
+	
+	const address = (
+		$("#street").val()
+		+ ", "
+		+ $("#zip").val()
+		+ " "
+		+ $("#city").val()
+	);
+	
+	setHighestPossibleFontSize(
+		firstPageContext,
+		address,
+		"Open Sans",
+		firstPageCanvas.height * 0.013,
+		firstPageCanvas.width * 0.51
+	);
+	
+	firstPageContext.fillText(
+		address,
+		personalInfoSharedX, firstPageCanvas.height * 0.597
+	);
+	
+	// Pick up options
+	
+	secondPageContext.fillStyle = "#000";
+	
+	switch ($("#receiving-type").val()) {
+		case "Vyzvednout osobně":
+			createCheck(
+				firstPageContext,
+				firstPageCanvas,
+				checkboxSharedY,
+				firstPageCanvas.height * 0.7195
+			);
+			
+			break;
+		case "Předat osobě, která se prokáže plnou mocí":
+			createCheck(
+				firstPageContext,
+				firstPageCanvas,
+				checkboxSharedY,
+				firstPageCanvas.height * 0.751
+			);
+			
+			break;
+		
+		// END First page
+		// BEGIN Second page
+		
+		case "Zaslat na adresu trvalého pobytu":
+			createCheck(
+				secondPageContext,
+				secondPageCanvas,
+				checkboxSharedY,
+				secondPageCanvas.height * 0.08475
+			);
+			
+			break;
+		case "Zaslat jinam":
+			createCheck(
+				secondPageContext,
+				secondPageCanvas,
+				checkboxSharedY,
+				secondPageCanvas.height * 0.1152
+			);
+			
+			const deliveryAddress = $("#other-address-conditional").val();
+			
+			setHighestPossibleFontSize(
+				secondPageContext,
+				deliveryAddress,
+				"Open Sans",
+				secondPageCanvas.height * 0.013,
+				secondPageCanvas.width * 0.51
+			);
+			
+			secondPageContext.fillText(
+				deliveryAddress,
+				secondPageCanvas.width * 0.145, secondPageCanvas.height * 0.155
+			);
+			
+			break;
+	}
+	
+	const currentDate = new Date();
+	const formattedCurrentDate = (
+		currentDate.getDate()
+		+ ". "
+		+ currentDate.getMonth()
+		+ ". "
+		+ currentDate.getFullYear()
+	);
+	
+	setHighestPossibleFontSize(
+		secondPageContext,
+		formattedCurrentDate,
+		"Open Sans",
+		secondPageCanvas.height * 0.013,
+		secondPageCanvas.width * 0.32
+	);
+	
+	secondPageContext.fillText(
+		formattedCurrentDate,
+		secondPageCanvas.width * 0.56, secondPageCanvas.height * 0.232
+	);
+}
+
 window.onload = () => {
 	const autocompleteWidget = new autocomplete.GeocoderAutocomplete(
 		document.getElementById("address-autocomplete"), 
@@ -41,13 +344,37 @@ window.onload = () => {
 		}
 	);
 	
+	$("#receiving-type").on(
+		"change",
+		(event) => {
+			if (event.target.value === "Zaslat jinam") {
+				$("#other-address-conditional").css("display", "block");
+			} else {
+				$("#other-address-conditional").css("display", "none");
+			}
+		}
+	);
+	
 	$("#create-filled-form").on(
 		"click",
-		(event) => {
+		async (event) => {
 			$("#form-wrapper input,#form-wrapper select").attr("disabled", true);
 			// We already know this, don't waste time looking it up.
 			// Repeating ourselves once is fine here.
 			$(event.target).attr("disabled", true);
+			
+			await fillCanvas();
+			
+			await new Promise(
+				resolve => { $("#step1").fadeOut(200, resolve); }
+			);
+			$("#step1").css("display", "none");
+			
+			$("#step2").css("opacity", "0");
+			$("#step2").css("display", "flex");
+			await new Promise(
+				resolve => { $("#step2").fadeIn(200, resolve); }
+			);
 		}
 	);
 }