void: Finish porting the main source
diff --git a/apps/void/src/app.d.ts b/apps/void/src/app.d.ts
new file mode 100644
index 0000000..f59b884
--- /dev/null
+++ b/apps/void/src/app.d.ts
@@ -0,0 +1,12 @@
+// See https://kit.svelte.dev/docs/types#app
+// for information about these interfaces
+declare global {
+	namespace App {
+		// interface Error {}
+		// interface Locals {}
+		// interface PageData {}
+		// interface Platform {}
+	}
+}
+
+export {};
diff --git a/apps/void/src/app.html b/apps/void/src/app.html
new file mode 100644
index 0000000..effe0d0
--- /dev/null
+++ b/apps/void/src/app.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
+		<meta name="viewport" content="width=device-width" />
+		%sveltekit.head%
+	</head>
+	<body data-sveltekit-preload-data="hover">
+		<div style="display: contents">%sveltekit.body%</div>
+	</body>
+</html>
diff --git a/apps/void/src/app.postcss b/apps/void/src/app.postcss
new file mode 100644
index 0000000..76589de
--- /dev/null
+++ b/apps/void/src/app.postcss
@@ -0,0 +1,49 @@
+/* Write your global styles here, in PostCSS syntax */
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+	:root {
+		--background: 224 71% 4%;
+		--foreground: 213 31% 91%;
+
+		--muted: 223 47% 11%;
+		--muted-foreground: 215.4 16.3% 56.9%;
+
+		--popover: 224 71% 4%;
+		--popover-foreground: 215 20.2% 65.1%;
+
+		--card: 224 71% 4%;
+		--card-foreground: 213 31% 91%;
+
+		--border: 216 34% 17%;
+		--input: 216 34% 17%;
+
+		--primary: 210 40% 98%;
+		--primary-foreground: 222.2 47.4% 1.2%;
+
+		--secondary: 222.2 47.4% 11.2%;
+		--secondary-foreground: 210 40% 98%;
+
+		--accent: 216 34% 17%;
+		--accent-foreground: 210 40% 98%;
+
+		--destructive: 359 51% 48%;
+		--destructive-foreground: 210 40% 98%;
+
+		--ring: 216 34% 17%;
+
+		--radius: 0.5rem;
+	}
+}
+
+@layer base {
+	* {
+		@apply border-border;
+	}
+	body {
+		@apply bg-background text-foreground;
+		font-feature-settings: 'rlig' 1, 'calt' 1;
+	}
+}
diff --git a/apps/void/src/index.test.ts b/apps/void/src/index.test.ts
new file mode 100644
index 0000000..e07cbbd
--- /dev/null
+++ b/apps/void/src/index.test.ts
@@ -0,0 +1,7 @@
+import { describe, it, expect } from 'vitest';
+
+describe('sum test', () => {
+	it('adds 1 + 2 to equal 3', () => {
+		expect(1 + 2).toBe(3);
+	});
+});
diff --git a/apps/void/src/lib/utils.ts b/apps/void/src/lib/utils.ts
new file mode 100644
index 0000000..256f86f
--- /dev/null
+++ b/apps/void/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+	return twMerge(clsx(inputs));
+}
diff --git a/apps/void/src/routes/+layout.svelte b/apps/void/src/routes/+layout.svelte
new file mode 100644
index 0000000..9b019ec
--- /dev/null
+++ b/apps/void/src/routes/+layout.svelte
@@ -0,0 +1,9 @@
+<script>
+	import { BackgroundStars } from '$components/decoration/BackgroundStars';
+	import '../app.postcss';
+	import '../app.postcss';
+</script>
+
+<slot />
+
+<BackgroundStars starCount={20} />
diff --git a/apps/void/src/routes/+page.server.ts b/apps/void/src/routes/+page.server.ts
new file mode 100644
index 0000000..19b40a3
--- /dev/null
+++ b/apps/void/src/routes/+page.server.ts
@@ -0,0 +1,14 @@
+export const actions = {
+	default: async ({ request }: { request: any }) => {
+		const data = await request.formData();
+		const message = data.get("message");
+		const pastMessages = data.get("pastMessages");
+		const reset = data.get("reset");
+
+		if (!reset) {
+			return { message, pastMessages, reset: false };
+		} else {
+			return { message: "", pastMessages: null, reset: true };
+		}
+	}
+}
diff --git a/apps/void/src/routes/+page.svelte b/apps/void/src/routes/+page.svelte
new file mode 100644
index 0000000..894b5c2
--- /dev/null
+++ b/apps/void/src/routes/+page.svelte
@@ -0,0 +1,100 @@
+<script lang="ts">
+	import Button from '$components/ui/button/Button.svelte';
+	import Input from '$components/ui/input/Input.svelte';
+	import { onMount } from 'svelte';
+	import { flip } from 'svelte/animate';
+	import { fade } from 'svelte/transition';
+
+	export let form;
+
+	let messageInput: HTMLInputElement;
+	function focusMessageInput() {
+		messageInput.focus();
+	}
+
+	let systemMessage =
+		'Welcome to the void. Everything you put here is completely private. Messages will fade as they go up the page and disappear after a few minutes, or you can refresh the page to clear them too. Let out your frustrations and watch them slip away into the void...';
+	const resetMessage =
+		"Poof: it's gone; hopefully you feel a little better after letting your frustrations out. If there's something you still need to vent about feel free to continue. I'm still here to listen...";
+	let messages: string[] = [];
+	let currentlyTyped: string = '';
+
+	if (form?.pastMessages) {
+		messages = form?.pastMessages.toString().split('\n');
+	}
+
+	if (form?.message) {
+		messages.push(form?.message.toString());
+	}
+
+	if (form?.reset) {
+		systemMessage = resetMessage;
+	}
+
+	onMount(focusMessageInput);
+</script>
+
+<form
+	on:submit|preventDefault={() => {
+		messages.push(currentlyTyped);
+		messages = messages;
+		currentlyTyped = '';
+		focusMessageInput();
+	}}
+	method="post"
+	class="p-2 flex flex-col h-screen gap-2 justify-end pt-0"
+>
+	<div class="flex p-2 pt-0 flex-col justify-end overflow-hidden pastMessages">
+		{#each (messages.length === 0 ? [systemMessage] : ['']).concat(messages) as message, index (`${index} ${message}`)}
+			<p class:message="{index !== 0}" transition:fade={{ duration: 100 }} animate:flip={{ duration: 100 }}>
+				{message}
+			</p>
+		{/each}
+		<input type="hidden" value={messages.join('\n')} name="pastMessages" />
+	</div>
+
+	<div class="flex w-full gap-2">
+		<Input
+			bind:element={messageInput}
+			bind:value={currentlyTyped}
+			name="message"
+			placeholder="Write down your worries, and let them slip into the void"
+		/>
+		<Button type="submit">Send</Button>
+		<Button
+			name="reset"
+			value="reset"
+			on:click={() => {
+				currentlyTyped = '';
+				systemMessage = resetMessage;
+				messages = [systemMessage];
+			}}>Clean</Button
+		>
+	</div>
+</form>
+
+<style lang="postcss">
+	div.pastMessages::after {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: 0;
+		z-index: 1;
+		width: 100%;
+		height: 100%;
+		background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
+	}
+
+	@keyframes message-fade-out {
+		0% {
+			opacity: 1;
+		}
+		100% {
+			opacity: 0;
+		}
+	}
+
+	p.message {
+		animation: message-fade-out 150s ease-in-out forwards;
+	}
+</style>