Menu
Avatar
The menu of my blog
Quick Stats
Quests
30 Quests
Messages
2 Messages
Playback
5 Playback
Items
6 Items
Skills
2 Skills
Trace
1 Trace
Message

The Sword Art Online Utilities Project

Welcome, traveler. This is a personal blog built in the style of the legendary SAO game interface. Navigate through the menu to explore the journal, skills, and item logs.

© 2020-2026 Nagi-ovo | RSS | Breezing
← Back to Quest Log
Building a Blog from Scratch: Do I Still Have the Passion?
Building a Blog from Scratch: Do I Still Have the Passion?

Fulfilling a decade-old dream: building an SAO-themed blog and documenting the MDsveX writing syntax

Jan 1, 2026 12 min read
designmdsvexsveltekit

Human-Crafted

Written directly by the author with no AI-generated sections.

Preface

The first line of code I ever wrote (in any meaningful sense) was a single-file HTML personal blog. The title was literally “Personal Blog”. After that I went through:

  • 2023: Hexo + GitHub Pages
  • 2024–2025: xLog

Since early 2025, xLog had become more and more unstable, so I exported everything in advance and kept it ready to migrate. By the time I finally decided to “build my own”, there were only two days left before 2026. For a three-minute-interest ADHD brain, this used to be an impossible mission. Luckily, 2025 turned into the “Year of Agents” (hopefully), and code agents became dramatically stronger than what we had at the end of last year. I’ve successfully moved from “trust conversational programming only” to being okay with letting agents take the wheel.

So… Antigravity + Cursor + Codex, let’s go. With Gemini 3.0 Pro/Flash, Claude 4.5 Opus/Sonnet, and GPT-5.2-Extra-High backing them up, these tools have reached a level that would’ve been unimaginable to me just a few months ago.

Design Direction

When I opened Antigravity, this image suddenly popped into my head:

kirito-2025

So I thought: why not build a Sword Art Online–themed blog?

From there my brain started rendering a whole UI in real time, and I tried to use AI to prototype it.

Tech Stack

At the core it’s SvelteKit + MDsveX — a combo that lets me write Svelte components directly inside Markdown, which is extremely flexible.

For code highlighting I use rehype-pretty-code + Shiki, supporting line numbers, line highlights, and code folding. GitHub-style Markdown extensions (tables, task lists, etc.) are enabled via remark-gfm.

Styling-wise it’s pure SCSS — no Tailwind or other frameworks. Well it’s partly because I forgot to tell the LLM in the planning stage, but also because Tailwind can genuinely become a maintenance nightmare. So let’s just go back to the “learn frontend by typing CSS line by line in front of a live server” era.


Writing Guide (Notes to Future Me)

Everything below is a cheat sheet for writing posts in this blog (SvelteKit + MDsveX): file conventions, frontmatter, supported Markdown extensions, math, enhanced code blocks, media embeds, Svelte-in-Markdown, and a few gotchas.

0. Quick Start (Checklist)

  • New post: src/lib/content/blog/<YYYY>/<slug>.md
  • English version: src/lib/content/blog/<YYYY>/<slug>.en.md (same slug; locale suffix)
  • Static assets: static/assets/... maps to /assets/...
  • URL: /<slug> (slug = filename; must be globally unique)
  • Local preview: bun run dev → http://localhost:5173/<slug>

1. Frontmatter (Metadata)

Fields used by the list page and the post page:

FieldRequiredTypeNotes
title✅stringPost title
excerpt✅stringCard text + SEO description (keep it short, ~≤ 180 chars)
date✅stringSort key (use ISO YYYY-MM-DD or full ISO datetime)
tags✅string[]Tag filters
readTime✅numberMinutes (manual)
cover⭕stringCover image path (/assets/...)
creationMethod✅independent / ai-assistedShows the creation badge
lastUpdated⭕stringShown as “Last updated”
translated⭕stringFor translated posts: triggers a “translated by …” popup

Template:

---
title: "Post Title"
excerpt: "Short summary (aim for ≤ 180 characters)"
date: "2026-01-01"
tags:
  - tag-one
  - tag-two
readTime: 10
cover: "/assets/images/posts/2026/my-post/cover.png"
creationMethod: independent
lastUpdated: "2026-01-01"
# translated: "GPT-5.2" # Only for translated versions
---

2. Markdown + GFM Extensions

This project enables remark-gfm, so you get GitHub-style tables, task lists, strikethrough, and autolinks on top of basic Markdown.

Headings (Auto Anchors)

Headings automatically get ids, and the entire heading is clickable (via rehype-slug + rehype-autolink-headings). To link to a section, just copy the URL with #....

Lists & Task Lists

  • Regular ordered/unordered lists work as usual
  • Task lists (GFM):
- [ ] TODO
- [x] Done

Links

  • Normal links: [Svelte](https://svelte.dev)
  • Force “open in new tab”: use HTML (and keep rel):
<a target="_blank" rel="noopener noreferrer" href="https://svelte.dev">Svelte</a>

Tables

FeatureUseNotes
TablesCompare datavia GFM
Task listsTrack TODOsvia GFM
MathTechnical writingrendered by KaTeX

Footnotes (Optional)

This is a sentence.[^1]

[^1]: This is the footnote text.

3. Math (KaTeX)

Math is enabled via remark-math + rehype-katex-svelte:

  • Inline: $E = mc^2$ → E=mc2E = mc^2E=mc2
  • Display:
Rt=∑k=0∞γkrt+kR_t = \sum_{k=0}^{\infty} \gamma^k r_{t+k}Rt​=k=0∑∞​γkrt+k​

4. Code Blocks (Shiki + rehype-pretty-code)

Code blocks come with syntax highlighting + a copy button, and support titles, line numbers, highlighted lines, and folding:

example.js
const items = [
  { name: "Item A", price: 100 },
  { name: "Item B", price: 200 },
  { name: "Item C", price: 300 },
];
 
const total = items.reduce((sum, item) => sum + item.price, 0);
console.log(`Total: ${total}`);
  • title="example.js": code block title (filename)
  • showLineNumbers: enable line numbers
  • {2,5}: highlight lines 2 and 5 (ranges supported: {2-6,10})
  • Copy button: appears at the top-right on hover

Folding (By Line)

Fold long/boring ranges:

theme-tokens.css
:root {
  /* Primary palette */
  --sao-orange: #f5a623;
  --sao-blue: #0ea5e9;
  
  --blog-code-bg: #1c1b2f;
  
  /* Folded section */
  --blog-code-border: rgba(245, 166, 35, 0.32);
  --blog-inline-code-bg: rgba(245, 166, 35, 0.18);
  --blog-code-highlight: rgba(245, 166, 35, 0.16);
  --blog-code-lineno: #b5b7cd;
  --blog-code-shell: linear-gradient(135deg, rgba(28, 27, 47, 0.96), rgba(21, 23, 39, 0.9));
  --blog-code-line: rgba(255, 255, 255, 0.08);
  --blog-code-text: #eaeaf6;
}

fold={8-15} hides lines 8–15 by default; readers can toggle it from the bottom-left of the code block.

5. Images (Auto WebP + Lazyload)

Recommended location: static/assets/images/posts/<YYYY>/<slug>/..., and reference them with absolute paths:

![alt](/assets/images/posts/2026/my-post/figure-1.png)

During build, local .png/.jpg/.jpeg images are wrapped in a <picture> with a .webp source (if the sibling .webp exists). To batch-generate WebP files:

bun run convert-webp

Images also default to loading="lazy" and decoding="async" (you can override by writing your own <img loading="eager" ...>).

For alignment/width control, HTML is the easiest:

<p align="center">
  <img src="/assets/images/posts/2026/my-post/demo.png" alt="demo" width="70%" />
</p>

6. Video Embeds (YouTube Example)

Use a responsive wrapper so it doesn’t overflow on mobile:

Replace the video ID after embed/.

7. Music Player (GeoMusicPlayer)

This blog includes a built-in music player component (QQ Music + YouTube; it picks a source based on the viewer’s network):

Initializing Link...

How to use:

  1. Import it at the top of the post: import GeoMusicPlayer from '$lib/components/GeoMusicPlayer.svelte';
  2. Place the component tag and fill in props

Props:

  • qqMusicSongId: QQ Music song ID
  • youtubeVideoId: YouTube video ID
  • songTitle: song title (also used as the global player key)
  • coverExtension: cover extension (expects static/music-covers/<songTitle>.<ext>)
  • autoplay: whether to autoplay (boolean)

8. Svelte in Markdown (MDsveX)

Rules of thumb

  • Keep the <script> block right after frontmatter (the earlier, the safer)
  • You can write components/HTML directly in the body (it’s compiled as Svelte)
  • Avoid writing {count} interpolation in plain Markdown text (it gets escaped as text); wrap it in an element instead, e.g. <span>{count}</span>

Minimal interactive example

This is a live counter demo

Gotcha: don’t use HTML onclick

The build pipeline strips onclick/onClick attributes (mainly to harden the code-block copy button). For interactions, prefer the project’s onmousedown={...} style (or Svelte’s on:click={...}).

9. Horizontal Rules

Use three hyphens:


10. Bilingual Posts (ZH/EN)

Locale variants are resolved by filename suffix:

  • Chinese: <slug>.md (default)
  • English: <slug>.en.md

If the English post is a translation, add translated: "xxx" in frontmatter to show the translation notice.


Closing Thoughts

While building this blog framework, there were countless moments where I wanted to scrap a design and start over, or sneak in a new component at the last minute — and I did… exactly that. For someone who promised to “finish migrating before the new year”, it was pretty painful, but looking back it was also a ton of fun. I thought I probably wouldn’t have a second experience like this in my life, but then I realized: all of this is proof that I still have the passion. I’m sure I’ll end up doing many more interesting things like this.

Just for fun.

Article Info Human-Crafted
Title Building a Blog from Scratch: Do I Still Have the Passion?
Author Nagi-ovo
URL
Last Updated No edits yet
Citation

For commercial reuse, contact the site owner for authorization. For non-commercial use, please credit the source and link to this article.

You may copy, distribute, and adapt this work as long as derivatives share the same license. Licensed under CC BY-NC-SA 4.0.

Session 00:00:00