add blog, initial post
This commit is contained in:
151
.gitignore
vendored
151
.gitignore
vendored
@@ -1,138 +1,23 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
node_modules
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
# Env
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
9
.prettierignore
Normal file
9
.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
16
.prettierrc
Normal file
16
.prettierrc
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tailwindStylesheet": "./src/routes/layout.css"
|
||||
}
|
||||
2
.tool-versions
Normal file
2
.tool-versions
Normal file
@@ -0,0 +1,2 @@
|
||||
direnv 2.37.1
|
||||
nodejs 24.12.0
|
||||
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
}
|
||||
}
|
||||
39
README.md
39
README.md
@@ -1,3 +1,38 @@
|
||||
# jawhng-blog
|
||||
# sv
|
||||
|
||||
A nerd's blog
|
||||
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```sh
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
|
||||
72
claude.md
Normal file
72
claude.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
Static blog built with SvelteKit, MDsveX, and Tailwind CSS v4. Uses `@sveltejs/adapter-static` for static site generation.
|
||||
|
||||
## Development Commands
|
||||
```bash
|
||||
pnpm install # Install dependencies
|
||||
pnpm dev # Start dev server (default: http://localhost:5173)
|
||||
pnpm build # Build for production
|
||||
pnpm preview # Preview production build
|
||||
pnpm check # Type-check with svelte-check
|
||||
pnpm check:watch # Type-check in watch mode
|
||||
pnpm format # Format code with Prettier
|
||||
pnpm lint # Lint with ESLint and Prettier
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Routing Structure
|
||||
```
|
||||
src/routes/
|
||||
├── +layout.svelte # Root layout with Nav component
|
||||
├── +page.svelte # Blog listing (homepage at /)
|
||||
├── +page.ts # Loads all posts from ./posts/*.md
|
||||
├── posts/
|
||||
│ ├── *.md # Blog post markdown files (with frontmatter)
|
||||
│ └── [slug]/
|
||||
│ ├── +page.svelte # Individual post renderer
|
||||
│ └── +page.ts # Dynamic post loader
|
||||
├── about/
|
||||
│ └── +page.md # About page (markdown)
|
||||
└── music/
|
||||
└── +page.md # Music page (markdown)
|
||||
```
|
||||
|
||||
### Blog Post Structure
|
||||
- Posts are markdown files in `src/routes/posts/*.md`
|
||||
- Required frontmatter: `title`, `date`, `excerpt`
|
||||
- Posts accessed at `/posts/{slug}` where slug is filename without `.md`
|
||||
- Blog listing at root `/` shows all posts sorted by date (newest first)
|
||||
|
||||
### Navigation
|
||||
Navigation component at `src/lib/components/Nav.svelte` with links:
|
||||
- Blog (/) - default landing page
|
||||
- About (/about)
|
||||
- Music (/music)
|
||||
|
||||
### MDsveX Configuration
|
||||
- Configured in `svelte.config.js` to process `.md` and `.svx` files
|
||||
- Extensions: `['.svelte', '.md', '.svx']`
|
||||
- Preprocessors: `vitePreprocess()` and `mdsvex()`
|
||||
|
||||
## Development Guidelines
|
||||
- Use Svelte 5+ with runes (`$props`, `$state`, `$derived`, etc.)
|
||||
- TypeScript with strict mode enabled
|
||||
- Tailwind CSS v4 classes only (no custom CSS unless necessary)
|
||||
- Use pnpm as package manager
|
||||
- Self-explanatory code without comments
|
||||
- Short, concise function and variable names
|
||||
- Careful state management to avoid infinite loops
|
||||
|
||||
## Technology Stack
|
||||
- SvelteKit 2.x with static adapter
|
||||
- Svelte 5.x (runes-based)
|
||||
- MDsveX for markdown processing
|
||||
- Tailwind CSS v4 with @tailwindcss/typography
|
||||
- TypeScript with strict mode
|
||||
- ESLint + Prettier for code quality
|
||||
- Vite for build tooling
|
||||
41
eslint.config.js
Normal file
41
eslint.config.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import js from '@eslint/js';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||
|
||||
rules: {
|
||||
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
'no-undef': 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "jawhng-blog",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"format": "prettier --write .",
|
||||
"lint": "prettier --check . && eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.4.0",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@sveltejs/adapter-auto": "^7.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
"@sveltejs/kit": "^2.49.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@types/node": "^24",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.13.1",
|
||||
"globals": "^16.5.0",
|
||||
"mdsvex": "^0.12.6",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||
"svelte": "^5.45.6",
|
||||
"svelte-check": "^4.3.4",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "^7.2.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tsparticles/engine": "^3.9.1",
|
||||
"@tsparticles/slim": "^3.9.1"
|
||||
}
|
||||
}
|
||||
2824
pnpm-lock.yaml
generated
Normal file
2824
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal 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
21
src/app.html
Normal 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>
|
||||
1
src/lib/assets/favicon.svg
Normal file
1
src/lib/assets/favicon.svg
Normal 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 |
26
src/lib/components/Nav.svelte
Normal file
26
src/lib/components/Nav.svelte
Normal 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>
|
||||
25
src/lib/components/Particles.svelte
Normal file
25
src/lib/components/Particles.svelte
Normal 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
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
83
src/lib/particles-config.json
Normal file
83
src/lib/particles-config.json
Normal 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
14
src/routes/+layout.svelte
Normal 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
27
src/routes/+page.svelte
Normal 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
23
src/routes/+page.ts
Normal 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
15
src/routes/about/+page.md
Normal 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
26
src/routes/layout.css
Normal 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
15
src/routes/music/+page.md
Normal 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>
|
||||
22
src/routes/posts/[slug]/+page.svelte
Normal file
22
src/routes/posts/[slug]/+page.svelte
Normal 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>
|
||||
15
src/routes/posts/[slug]/+page.ts
Normal file
15
src/routes/posts/[slug]/+page.ts
Normal 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}`);
|
||||
}
|
||||
};
|
||||
52
src/routes/posts/how-this-site-was-made.md
Normal file
52
src/routes/posts/how-this-site-was-made.md
Normal 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.
|
||||
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# allow crawling everything by default
|
||||
User-agent: *
|
||||
Disallow:
|
||||
28
svelte.config.js
Normal file
28
svelte.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import { mdsvex } from 'mdsvex';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Add .md and .svx as valid extensions
|
||||
extensions: ['.svelte', '.md', '.svx'],
|
||||
|
||||
preprocess: [
|
||||
vitePreprocess(),
|
||||
mdsvex({
|
||||
extensions: ['.md', '.svx']
|
||||
})
|
||||
],
|
||||
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: undefined,
|
||||
precompress: false,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// To make changes to top-level options such as include and exclude, we recommend extending
|
||||
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
|
||||
}
|
||||
5
vite.config.ts
Normal file
5
vite.config.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({ plugins: [tailwindcss(), sveltekit()] });
|
||||
Reference in New Issue
Block a user