add blog, initial post

This commit is contained in:
2025-12-22 00:52:35 +02:00
parent a82235f882
commit 2063002971
32 changed files with 3507 additions and 135 deletions

13
src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

21
src/app.html Normal file
View File

@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Work+Sans:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,26 @@
<script lang="ts">
import { page } from '$app/state';
const links = [
{ href: '/', label: 'Blog' },
{ href: '/about', label: 'About' },
{ href: '/music', label: 'Music' }
];
</script>
<nav class="border-b border-electric-violet-800 bg-white">
<div class="mx-auto max-w-4xl px-4">
<div class="flex gap-8 py-4">
{#each links as link}
<a
href={link.href}
class=" text-gray-700 transition hover:text-electric-violet-800"
class:font-semibold={page.url.pathname === link.href}
class:text-electric-violet-800={page.url.pathname === link.href}
>
{link.label}
</a>
{/each}
</div>
</div>
</nav>

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import { onMount } from 'svelte';
import { tsParticles } from '@tsparticles/engine';
import { loadSlim } from '@tsparticles/slim';
import particlesConfig from '$lib/particles-config.json';
let container: HTMLDivElement;
onMount(async () => {
await loadSlim(tsParticles);
await tsParticles.load({
id: 'tsparticles',
options: {
fullScreen: {
enable: false,
zIndex: -1
},
...particlesConfig
}
});
});
</script>
<div id="tsparticles" bind:this={container} class="fixed inset-0 -z-10"></div>

1
src/lib/index.ts Normal file
View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -0,0 +1,83 @@
{
"particles": {
"number": {
"value": 80,
"density": {
"enable": true,
"area": 800
}
},
"color": {
"value": "#9000f5"
},
"shape": {
"type": "circle"
},
"opacity": {
"value": 0.5,
"random": false
},
"size": {
"value": 3,
"random": true
},
"links": {
"enable": true,
"distance": 150,
"color": "#9000f5",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 1,
"direction": "none",
"random": false,
"straight": false,
"outModes": {
"default": "out"
}
}
},
"interactivity": {
"detectsOn": "canvas",
"events": {
"onHover": {
"enable": false,
"mode": "repulse"
},
"onClick": {
"enable": true,
"mode": "repulse"
},
"resize": {
"enable": true
}
},
"modes": {
"grab": {
"distance": 400,
"links": {
"opacity": 1
}
},
"bubble": {
"distance": 400,
"size": 40,
"duration": 2,
"opacity": 0.8
},
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"quantity": 4
},
"remove": {
"quantity": 2
}
}
},
"detectRetina": true
}

14
src/routes/+layout.svelte Normal file
View File

@@ -0,0 +1,14 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
import Nav from '$lib/components/Nav.svelte';
import Particles from '$lib/components/Particles.svelte';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<Particles />
<div class="fixed inset-0 -z-5 backdrop-blur-xs"></div>
<Nav />
{@render children()}

27
src/routes/+page.svelte Normal file
View File

@@ -0,0 +1,27 @@
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title>Blog</title>
</svelte:head>
<div class="mx-auto max-w-4xl px-4 py-12">
<h1 class="mb-8 text-4xl font-bold">Blog Posts</h1>
<div class="space-y-6">
{#each data.posts as post}
<article class="border-b pb-6">
<a href="/posts/{post.slug}" class="group">
<h2 class="mb-2 text-2xl font-semibold transition group-hover:text-electric-violet-800">
{post.title}
</h2>
<p class="mb-2 text-gray-600">{post.date}</p>
<p class="text-gray-700">{post.excerpt}</p>
</a>
</article>
{/each}
</div>
</div>

23
src/routes/+page.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
const postFiles = import.meta.glob<{ metadata: { title: string; date: string; excerpt: string } }>(
'./posts/*.md',
{ eager: true }
);
const posts = Object.entries(postFiles).map(([path, post]) => {
const slug = path.replace('./posts/', '').replace('.md', '');
return {
slug,
title: post.metadata.title,
date: post.metadata.date,
excerpt: post.metadata.excerpt
};
});
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return { posts };
};

15
src/routes/about/+page.md Normal file
View File

@@ -0,0 +1,15 @@
---
title: About
---
<svelte:head>
<title>About</title>
</svelte:head>
<div class="prose prose-lg mx-auto max-w-4xl px-4 py-12">
# About Me
This is the about page.
</div>

26
src/routes/layout.css Normal file
View File

@@ -0,0 +1,26 @@
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@theme{
--color-electric-violet-50: #fbf3ff;
--color-electric-violet-100: #f4e3ff;
--color-electric-violet-200: #eccdff;
--color-electric-violet-300: #dda5ff;
--color-electric-violet-400: #c86cff;
--color-electric-violet-500: #b435ff;
--color-electric-violet-600: #a20fff;
--color-electric-violet-700: #9000f5;
--color-electric-violet-800: #7806c3;
--color-electric-violet-900: #63079c;
--color-electric-violet-950: #430076;
}
body {
font-family: "Work Sans", sans-serif;
font-optical-sizing: auto;
font-style: normal;
}
h1, h2, h3, h4, h5{
font-family: 'Space Mono', monospace;
}

15
src/routes/music/+page.md Normal file
View File

@@ -0,0 +1,15 @@
---
title: Music
---
<svelte:head>
<title>Music</title>
</svelte:head>
<div class="prose prose-lg mx-auto max-w-4xl px-4 py-12">
# Music
This is the music page.
</div>

View File

@@ -0,0 +1,22 @@
<script lang="ts">
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
</script>
<svelte:head>
<title>{data.metadata.title}</title>
</svelte:head>
<article class="mx-auto prose prose-lg max-w-4xl px-4 py-12">
<header class="mb-8">
<h1 class="mb-2 text-4xl font-bold">{data.metadata.title}</h1>
<p class="text-gray-600">{data.metadata.date}</p>
</header>
<div
class="prose-headings:font-semibold prose-a:text-electric-violet-800 prose-a:no-underline hover:prose-a:underline"
>
<data.content />
</div>
</article>

View File

@@ -0,0 +1,15 @@
import type { PageLoad } from './$types';
import { error } from '@sveltejs/kit';
export const load: PageLoad = async ({ params }) => {
try {
const post = await import(`../${params.slug}.md`);
return {
content: post.default,
metadata: post.metadata
};
} catch (e) {
throw error(404, `Post not found: ${params.slug}. Error: ${e}`);
}
};

View File

@@ -0,0 +1,52 @@
---
title: "How This Site Was Made (And Why You Probably Don't Care)"
date: "2025-12-22"
excerpt: "A cynical walkthrough of building a blog with SvelteKit, particles.js that doesn't work, and enough utility classes to make you question your life choices."
---
Everybody's dying. Some people are just doing it faster than others by choosing the wrong JavaScript framework.
## The Tech Stack
Here's what powers this monument to narcissism you're currently reading:
- **SvelteKit** - Because apparently React wasn't complicated enough, and Vue was too mainstream. At least Svelte 5 has runes now, which sound mystical but are really just state management with a rebrand.
- **MDsveX** - For simple markdown and then maybe a component when you don't want to keep things simple.
- **Tailwind CSS v4** - Utility classes everywhere. My HTML looks like someone had a seizure on the keyboard, but hey, at least I don't have to name things.
- **TypeScript** - Strict mode enabled, naturally. Because nothing says "I hate myself" quite like adding type annotations to everything.
## The Font Situation
I spent more time picking fonts than actually building this blog. By a lot.
## The Particles Background (A Tragedy in Three Acts)
**Act 1:** "My last blog used particles.js. It's been 11 years since its last update but how bad could it be?"
**Act 2:** `Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode`
**Act 3:** Install tsparticles. Realize the Svelte wrapper expects Svelte 4. Remove wrapper. Import engine directly. Finally works. Question choices.
The particles are approximately what I felt to be my favorite color on that day (`#9000f5` to be exact). There's a subtle blur overlay so they don't distract from the profound thoughts I'm sharing with the world.
## The Routing Architecture
Blog posts live at `/posts/[slug]` because that's what civilized people do. The homepage is the blog listing because if you came here expecting anything else, you're lost. At some point these posts could become too many (right...) so I might have to add pagination.
There's also an About page and a Music page. Both are markdown files. Both are probably empty. I'll get to it ...eventually.
## Key Learnings
1. **Old libraries don't work with modern tooling** - Shocking, I know. It's almost like maintaining software requires effort.
2. **The most important file is CLAUDE.md** - Because six months from now, Future Me will have no idea how any of this works. Documentation is self-care.
## The Build Process
It's using `@sveltejs/adapter-static` because this gets deployed to static hosting. No server-side rendering, no edge functions, no complexity. Just HTML, CSS, and JavaScript being served like it's 2005.
## Conclusion
This site was built in a couple hours with SvelteKit, some questionable design choices, and the crushing realization that web development in 2025 is somehow both easier and more complicated than it was 5 years ago.
Will I maintain it? Probably not. Will I write actual content? Maybe. Will I spend more time tweaking the particle configuration than writing posts? Absolutely.