+ {{ title }} +
+ ++ {{ excerpt }} +
++ {{ excerpt }} +
+
+
+### Code Blocks
+
+Pre-formatted code blocks are used for writing about programming or
+markup source code. Rather than forming normal paragraphs, the lines
+of a code block are interpreted literally. Markdown wraps a code block
+in both `` and `` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab.
+
+This is a normal paragraph:
+
+ This is a code block.
+
+Here is an example of AppleScript:
+
+ tell application "Foo"
+ beep
+ end tell
+
+A code block continues until it reaches a line that is not indented
+(or the end of the article).
+
+Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
+are automatically converted into HTML entities. This makes it very
+easy to include example HTML source code using Markdown -- just paste
+it and indent it, and Markdown will handle the hassle of encoding the
+ampersands and angle brackets. For example, this:
+
+
+
+Regular Markdown syntax is not processed within code blocks. E.g.,
+asterisks are just literal asterisks within a code block. This means
+it's also easy to use Markdown to write about Markdown's own syntax.
+
+```
+tell application "Foo"
+ beep
+end tell
+```
+
+## Span Elements
+
+### Links
+
+Markdown supports two style of links: _inline_ and _reference_.
+
+In both styles, the link text is delimited by [square brackets].
+
+To create an inline link, use a set of regular parentheses immediately
+after the link text's closing square bracket. Inside the parentheses,
+put the URL where you want the link to point, along with an _optional_
+title for the link, surrounded in quotes. For example:
+
+This is [an example](http://example.com/) inline link.
+
+[This link](http://example.net/) has no title attribute.
+
+### Emphasis
+
+Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
+emphasis. Text wrapped with one `*` or `_` will be wrapped with an
+HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML
+`` tag. E.g., this input:
+
+_single asterisks_
+
+_single underscores_
+
+**double asterisks**
+
+**double underscores**
+
+### Code
+
+To indicate a span of code, wrap it with backtick quotes (`` ` ``).
+Unlike a pre-formatted code block, a code span indicates code within a
+normal paragraph. For example:
+
+Use the `printf()` function.
+
+
diff --git a/app/pages/blog/index.vue b/app/pages/blog/index.vue
new file mode 100644
index 00000000..1abace29
--- /dev/null
+++ b/app/pages/blog/index.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+ blog...
+
+
+
+ console.log('Hovered:', i)"
+ />
+
+
+
+
+
+
+ No posts found.
+
+
+
+
diff --git a/app/pages/blog/nuxt.md b/app/pages/blog/nuxt.md
new file mode 100644
index 00000000..e3502b59
--- /dev/null
+++ b/app/pages/blog/nuxt.md
@@ -0,0 +1,14 @@
+---
+author: 'Daniel Roe'
+title: 'Nuxted'
+tags: ['OpenSource', 'Nuxt']
+excerpt: 'Nuxting'
+date: '2026-01-28'
+slug: 'nuxt'
+description: 'Nuxter'
+draft: false
+---
+
+# Nuxt
+
+What a great meta-framework!!
diff --git a/app/pages/blog/open-source.md b/app/pages/blog/open-source.md
new file mode 100644
index 00000000..68f174d8
--- /dev/null
+++ b/app/pages/blog/open-source.md
@@ -0,0 +1,14 @@
+---
+author: 'Daniel Roe'
+title: 'OSS'
+tags: ['OpenSource', 'Nuxt']
+excerpt: 'OSS Things'
+date: '2026-01-28'
+slug: 'open-source'
+description: 'Talking about Open Source Software'
+draft: false
+---
+
+# OSS
+
+This is about Open Source Software.
diff --git a/app/pages/blog/package-registries.md b/app/pages/blog/package-registries.md
new file mode 100644
index 00000000..30951179
--- /dev/null
+++ b/app/pages/blog/package-registries.md
@@ -0,0 +1,14 @@
+---
+author: 'Daniel Roe'
+title: 'Package Registries'
+tags: ['OpenSource', 'Nuxt']
+excerpt: 'Package Registries need fixing'
+date: '2026-01-28'
+slug: 'package-registries'
+description: 'Package Registries Reimagined'
+draft: false
+---
+
+# Package Registries
+
+Shortest explanation: Production grade JavaScript is weird.
diff --git a/app/pages/blog/server-components.md b/app/pages/blog/server-components.md
new file mode 100644
index 00000000..42735217
--- /dev/null
+++ b/app/pages/blog/server-components.md
@@ -0,0 +1,13 @@
+---
+author: 'Daniel Roe'
+title: 'Server Components'
+date: '2026-01-28'
+slug: 'server-components'
+description: 'My first post on the blog'
+excerpt: 'Zero JS'
+draft: false
+---
+
+# Server components
+
+Here is some server component razzle dazzle. Hello there!
diff --git a/app/plugins/blog-wrapper.ts b/app/plugins/blog-wrapper.ts
new file mode 100644
index 00000000..fff0ff57
--- /dev/null
+++ b/app/plugins/blog-wrapper.ts
@@ -0,0 +1,5 @@
+import BlogPostWrapper from '~/components/BlogPostWrapper.vue'
+
+export default defineNuxtPlugin(nuxtApp => {
+ nuxtApp.vueApp.component('BlogPostWrapper', BlogPostWrapper)
+})
diff --git a/docs/content/4. atmosphere-apps/.navigation.yml b/docs/content/4. atmosphere-apps/.navigation.yml
new file mode 100644
index 00000000..474fd58b
--- /dev/null
+++ b/docs/content/4. atmosphere-apps/.navigation.yml
@@ -0,0 +1,2 @@
+title: Atmosphere Apps
+icon: i-lucide-cloudy
diff --git a/docs/content/4. atmosphere-apps/1.atprotocol-architecture.md b/docs/content/4. atmosphere-apps/1.atprotocol-architecture.md
new file mode 100644
index 00000000..41c0d39e
--- /dev/null
+++ b/docs/content/4. atmosphere-apps/1.atprotocol-architecture.md
@@ -0,0 +1,24 @@
+---
+title: ATProtocol Architecture
+description: How npmx.dev exists in the Atmosphere
+navigation:
+ icon: i-lucide-construction
+---
+
+
+
+## Overview
+
+_npmx.dev_ site architecture as it pertains to the ATProtocol ecosystem, the [Atmosphere](https://atproto.com/).
+
+---
+
+## Components
+
+These are the components that allow for _npmx.dev_ to exist as an app on the Atmosphere:
+
+1. **Domain** - _npmx.dev_ site
+2. **OAuth** - Required for interacting with the Atmosphere
+3. **Constellation** - Atmosphere API
+
+---
diff --git a/i18n/locales/en.json b/i18n/locales/en.json
index e0ba7476..b96e69a7 100644
--- a/i18n/locales/en.json
+++ b/i18n/locales/en.json
@@ -10,6 +10,7 @@
"trademark_disclaimer": "npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.",
"footer": {
"about": "about",
+ "blog": "blog",
"docs": "docs",
"source": "source",
"social": "social",
@@ -45,6 +46,10 @@
"settings": "settings",
"back": "back"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Insights and updates from the npmx community"
+ },
"settings": {
"title": "settings",
"tagline": "customize your npmx experience",
diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json
index bdaef15d..8ca53740 100644
--- a/i18n/locales/fr-FR.json
+++ b/i18n/locales/fr-FR.json
@@ -41,10 +41,15 @@
"nav": {
"main_navigation": "Barre de navigation",
"popular_packages": "Paquets populaires",
+ "blog": "blog",
"search": "recherche",
"settings": "paramètres",
"back": "Retour"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Perspectives et actualités de la communauté npmx"
+ },
"settings": {
"title": "paramètres",
"tagline": "personnalisez votre expérience npmx",
diff --git a/i18n/locales/it-IT.json b/i18n/locales/it-IT.json
index 8461105d..51fd8ff1 100644
--- a/i18n/locales/it-IT.json
+++ b/i18n/locales/it-IT.json
@@ -41,10 +41,15 @@
"nav": {
"main_navigation": "Principale",
"popular_packages": "Pacchetti popolari",
+ "blog": "blog",
"search": "cerca",
"settings": "impostazioni",
"back": "Indietro"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Approfondimenti e aggiornamenti dalla comunità npmx"
+ },
"settings": {
"title": "impostazioni",
"tagline": "personalizza la tua esperienza npmx",
diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json
index 8a6cc33a..b96e69a7 100644
--- a/lunaria/files/en-US.json
+++ b/lunaria/files/en-US.json
@@ -10,6 +10,7 @@
"trademark_disclaimer": "npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.",
"footer": {
"about": "about",
+ "blog": "blog",
"docs": "docs",
"source": "source",
"social": "social",
@@ -45,6 +46,10 @@
"settings": "settings",
"back": "back"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Insights and updates from the npmx community"
+ },
"settings": {
"title": "settings",
"tagline": "customize your npmx experience",
@@ -329,7 +334,7 @@
"size": "Size",
"deps": "Deps",
"updated": "Updated",
- "install": "Install",
+ "get_started": "Get started",
"readme": "Readme",
"maintainers": "Maintainers",
"keywords": "Keywords",
diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json
index bdaef15d..8ca53740 100644
--- a/lunaria/files/fr-FR.json
+++ b/lunaria/files/fr-FR.json
@@ -41,10 +41,15 @@
"nav": {
"main_navigation": "Barre de navigation",
"popular_packages": "Paquets populaires",
+ "blog": "blog",
"search": "recherche",
"settings": "paramètres",
"back": "Retour"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Perspectives et actualités de la communauté npmx"
+ },
"settings": {
"title": "paramètres",
"tagline": "personnalisez votre expérience npmx",
diff --git a/lunaria/files/it-IT.json b/lunaria/files/it-IT.json
index 8461105d..51fd8ff1 100644
--- a/lunaria/files/it-IT.json
+++ b/lunaria/files/it-IT.json
@@ -41,10 +41,15 @@
"nav": {
"main_navigation": "Principale",
"popular_packages": "Pacchetti popolari",
+ "blog": "blog",
"search": "cerca",
"settings": "impostazioni",
"back": "Indietro"
},
+ "blog": {
+ "title": "Blog",
+ "description": "Approfondimenti e aggiornamenti dalla comunità npmx"
+ },
"settings": {
"title": "impostazioni",
"tagline": "personalizza la tua esperienza npmx",
diff --git a/modules/standard-site-sync.ts b/modules/standard-site-sync.ts
new file mode 100644
index 00000000..a10c04a7
--- /dev/null
+++ b/modules/standard-site-sync.ts
@@ -0,0 +1,31 @@
+import { defineNuxtModule, useNuxt } from 'nuxt/kit'
+import * as site from '../shared/types/lexicons/site'
+
+const PUBLICATION_SITE = 'https://npmx.dev'
+
+export default defineNuxtModule({
+ meta: {
+ name: 'standard-site-sync',
+ },
+ setup() {
+ const nuxt = useNuxt()
+ if (nuxt.options._prepare) {
+ return
+ }
+ nuxt.hook('content:file:afterParse', ctx => {
+ const { content } = ctx
+
+ const document = site.standard.document.$build({
+ site: PUBLICATION_SITE,
+ path: content.path as string,
+ title: content.title as string,
+ description: (content.excerpt || content.description) as string | undefined,
+ tags: content.tags as string[] | undefined,
+ publishedAt: new Date(content.date as string).toISOString(),
+ })
+
+ // TODO: Mock PDS push
+ console.log('[standard-site-sync] Would push:', JSON.stringify(document, null, 2))
+ })
+ },
+})
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 9e1bda7f..8e75bafa 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,6 +1,8 @@
import { currentLocales } from './config/i18n'
+import Markdown from 'unplugin-vue-markdown/vite'
export default defineNuxtConfig({
+ extensions: ['.md'],
modules: [
function (_, nuxt) {
if (nuxt.options._prepare) {
@@ -88,6 +90,7 @@ export default defineNuxtConfig({
'/about': { prerender: true },
'/settings': { prerender: true },
// proxy for insights
+ '/blog/**': { isr: true, prerender: true },
'/_v/script.js': { proxy: 'https://npmx.dev/_vercel/insights/script.js' },
'/_v/view': { proxy: 'https://npmx.dev/_vercel/insights/view' },
'/_v/event': { proxy: 'https://npmx.dev/_vercel/insights/event' },
@@ -176,6 +179,26 @@ export default defineNuxtConfig({
},
vite: {
+ vue: {
+ include: [/\.vue($|\?)/, /\.(md|markdown)($|\?)/],
+ },
+ plugins: [
+ Markdown({
+ include: [/\.(md|markdown)($|\?)/],
+ wrapperComponent: 'BlogPostWrapper',
+ async markdownItSetup(md) {
+ const shiki = await import('@shikijs/markdown-it')
+ md.use(
+ await shiki.default({
+ themes: {
+ dark: 'github-dark',
+ light: 'github-light',
+ },
+ }),
+ )
+ },
+ }),
+ ],
optimizeDeps: {
include: [
'@vueuse/core',
diff --git a/package.json b/package.json
index 84597047..d73cd1bb 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"generate": "nuxt generate",
"npmx-connector": "pnpm --filter npmx-connector dev",
"preview": "nuxt preview",
- "postinstall": "nuxt prepare && simple-git-hooks && pnpm generate:lexicons",
+ "postinstall": "pnpm generate:lexicons && nuxt prepare && simple-git-hooks",
"generate:lexicons": "lex build --lexicons lexicons --out shared/types/lexicons --clear",
"test": "vite test",
"test:browser": "playwright test",
@@ -40,10 +40,10 @@
"@intlify/shared": "11.2.8",
"@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3",
"@nuxt/a11y": "1.0.0-alpha.1",
- "@nuxt/fonts": "0.13.0",
- "@nuxt/scripts": "0.13.2",
- "@nuxtjs/color-mode": "4.0.0",
- "@nuxtjs/html-validator": "2.1.0",
+ "@nuxt/fonts": "^0.13.0",
+ "@nuxt/scripts": "^0.13.2",
+ "@nuxtjs/color-mode": "^4.0.0",
+ "@nuxtjs/html-validator": "^2.1.0",
"@nuxtjs/i18n": "10.2.1",
"@shikijs/langs": "3.21.0",
"@shikijs/themes": "3.21.0",
@@ -72,11 +72,13 @@
"@npm/types": "2.1.0",
"@nuxt/test-utils": "https://pkg.pr.new/@nuxt/test-utils@1499a48",
"@playwright/test": "1.58.0",
+ "@shikijs/markdown-it": "^3.21.0",
"@types/sanitize-html": "2.16.0",
"@types/semver": "7.7.1",
"@types/validate-npm-package-name": "4.0.2",
"@unocss/nuxt": "66.6.0",
"@unocss/preset-wind4": "66.6.0",
+ "@valibot/to-json-schema": "^1.5.0",
"@vite-pwa/assets-generator": "1.0.2",
"@vite-pwa/nuxt": "1.1.0",
"@vitest/browser-playwright": "4.0.18",
@@ -94,6 +96,7 @@
"std-env": "3.10.0",
"typescript": "5.9.3",
"unocss": "66.6.0",
+ "unplugin-vue-markdown": "^29.2.0",
"unplugin-vue-router": "0.19.2",
"vite-plus": "0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
"vitest": "npm:@voidzero-dev/vite-plus-test@0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ab85b8cb..88d42786 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -45,16 +45,16 @@ importers:
specifier: 1.0.0-alpha.1
version: 1.0.0-alpha.1(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/fonts':
- specifier: 0.13.0
+ specifier: ^0.13.0
version: 0.13.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
'@nuxt/scripts':
- specifier: 0.13.2
+ specifier: ^0.13.2
version: 0.13.2(@unhead/vue@2.1.2(vue@3.5.27(typescript@5.9.3)))(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
'@nuxtjs/color-mode':
- specifier: 4.0.0
+ specifier: ^4.0.0
version: 4.0.0(magicast@0.5.1)
'@nuxtjs/html-validator':
- specifier: 2.1.0
+ specifier: ^2.1.0
version: 2.1.0(@voidzero-dev/vite-plus-test@0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab(@types/node@24.10.9)(esbuild@0.27.2)(happy-dom@20.4.0)(jiti@2.6.1)(terser@5.46.0)(typescript@5.9.3)(yaml@2.8.2))(magicast@0.5.1)
'@nuxtjs/i18n':
specifier: 10.2.1
@@ -135,6 +135,9 @@ importers:
'@playwright/test':
specifier: 1.58.0
version: 1.58.0
+ '@shikijs/markdown-it':
+ specifier: ^3.21.0
+ version: 3.21.0(markdown-it-async@2.2.0)
'@types/sanitize-html':
specifier: 2.16.0
version: 2.16.0
@@ -150,6 +153,9 @@ importers:
'@unocss/preset-wind4':
specifier: 66.6.0
version: 66.6.0
+ '@valibot/to-json-schema':
+ specifier: ^1.5.0
+ version: 1.5.0(valibot@1.2.0(typescript@5.9.3))
'@vite-pwa/assets-generator':
specifier: 1.0.2
version: 1.0.2
@@ -201,6 +207,9 @@ importers:
unocss:
specifier: 66.6.0
version: 66.6.0(@unocss/webpack@66.6.0(webpack@5.104.1(esbuild@0.27.2)))(postcss@8.5.6)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
+ unplugin-vue-markdown:
+ specifier: ^29.2.0
+ version: 29.2.0(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))
unplugin-vue-router:
specifier: 0.19.2
version: 0.19.2(@vue/compiler-sfc@3.5.27)(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
@@ -264,7 +273,7 @@ importers:
version: 12.6.2
docus:
specifier: 5.4.4
- version: 5.4.4(29b67e4ea82958d3bb639aa676be121d)
+ version: 5.4.4(ec964c5fe48ae1161e7f2464bd5d4112)
nuxt:
specifier: 4.3.0
version: 4.3.0(@parcel/watcher@2.5.6)(@types/node@24.10.9)(@vue/compiler-sfc@3.5.27)(better-sqlite3@12.6.2)(cac@6.7.14)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(optionator@0.9.4)(oxlint@1.42.0(oxlint-tsgolint@0.11.3))(rolldown@1.0.0-rc.1)(rollup@4.57.0)(terser@5.46.0)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-tsc@3.2.4(typescript@5.9.3))(yaml@2.8.2)
@@ -1710,6 +1719,18 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ '@mdit-vue/plugin-component@3.0.2':
+ resolution: {integrity: sha512-Fu53MajrZMOAjOIPGMTdTXgHLgGU9KwTqKtYc6WNYtFZNKw04euSfJ/zFg8eBY/2MlciVngkF7Gyc2IL7e8Bsw==}
+ engines: {node: '>=20.0.0'}
+
+ '@mdit-vue/plugin-frontmatter@3.0.2':
+ resolution: {integrity: sha512-QKKgIva31YtqHgSAz7S7hRcL7cHXiqdog4wxTfxeQCHo+9IP4Oi5/r1Y5E93nTPccpadDWzAwr3A0F+kAEnsVQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@mdit-vue/types@3.0.2':
+ resolution: {integrity: sha512-00aAZ0F0NLik6I6Yba2emGbHLxv+QYrPH00qQ5dFKXlAo1Ll2RHDXwY7nN2WAfrx2pP+WrvSRFTGFCNGdzBDHw==}
+ engines: {node: '>=20.0.0'}
+
'@miyaneee/rollup-plugin-json5@1.2.0':
resolution: {integrity: sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA==}
peerDependencies:
@@ -3386,6 +3407,14 @@ packages:
'@shikijs/langs@3.21.0':
resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==}
+ '@shikijs/markdown-it@3.21.0':
+ resolution: {integrity: sha512-1QBlCtdauoOu4limpl9WCf0A1PRq0SofB47NkNNGuSx4V2J4cwxX9Ksfzk71qJSZbJ1g8jesu771gsfSs1+5qQ==}
+ peerDependencies:
+ markdown-it-async: ^2.2.0
+ peerDependenciesMeta:
+ markdown-it-async:
+ optional: true
+
'@shikijs/themes@3.21.0':
resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==}
@@ -3998,6 +4027,11 @@ packages:
peerDependencies:
webpack: ^4 || ^5
+ '@valibot/to-json-schema@1.5.0':
+ resolution: {integrity: sha512-GE7DmSr1C2UCWPiV0upRH6mv0cCPsqYGs819fb6srCS1tWhyXrkGGe+zxUiwzn/L1BOfADH4sNjY/YHCuP8phQ==}
+ peerDependencies:
+ valibot: ^1.2.0
+
'@vercel/nft@1.3.0':
resolution: {integrity: sha512-i4EYGkCsIjzu4vorDUbqglZc5eFtQI2syHb++9ZUDm6TU4edVywGpVnYDein35x9sevONOn9/UabfQXuNXtuzQ==}
engines: {node: '>=20'}
@@ -4506,6 +4540,9 @@ packages:
resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
engines: {node: '>= 14'}
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -5512,6 +5549,10 @@ packages:
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
+ extend-shallow@2.0.1:
+ resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
+ engines: {node: '>=0.10.0'}
+
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -5810,6 +5851,10 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ gray-matter@4.0.3:
+ resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
+ engines: {node: '>=6.0'}
+
gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
@@ -6118,6 +6163,10 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
+ is-extendable@0.1.1:
+ resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
+ engines: {node: '>=0.10.0'}
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -6348,6 +6397,10 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+ js-yaml@3.14.2:
+ resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
+ hasBin: true
+
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
@@ -6402,6 +6455,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
kleur@3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
@@ -6698,6 +6755,9 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
+ markdown-it-async@2.2.0:
+ resolution: {integrity: sha512-sITME+kf799vMeO/ww/CjH6q+c05f6TLpn6VOmmWCGNqPJzSh+uFgZoMB9s0plNtW6afy63qglNAC3MhrhP/gg==}
+
markdown-it@14.1.0:
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
hasBin: true
@@ -8024,6 +8084,10 @@ packages:
scule@1.3.0:
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
+ section-matter@1.0.0:
+ resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
+ engines: {node: '>=4'}
+
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -8211,6 +8275,9 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
srvx@0.10.1:
resolution: {integrity: sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==}
engines: {node: '>=20.16.0'}
@@ -8293,6 +8360,10 @@ packages:
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
engines: {node: '>=12'}
+ strip-bom-string@1.0.0:
+ resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
+ engines: {node: '>=0.10.0'}
+
strip-comments@2.0.1:
resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==}
engines: {node: '>=10'}
@@ -8741,6 +8812,12 @@ packages:
'@nuxt/kit':
optional: true
+ unplugin-vue-markdown@29.2.0:
+ resolution: {integrity: sha512-/x2hFgQ6cWN1Kls+yK5mAI9YDmeTofftynVGgOy1llBlDX1ifaXsQBls/bpORaiwn7cxA7HkOo0wn/xKcrXBHA==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ vite: ^2.0.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0
+
unplugin-vue-router@0.16.2:
resolution: {integrity: sha512-lE6ZjnHaXfS2vFI/PSEwdKcdOo5RwAbCKUnPBIN9YwLgSWas3x+qivzQvJa/uxhKzJldE6WK43aDKjGj9Rij9w==}
peerDependencies:
@@ -10928,6 +11005,20 @@ snapshots:
- encoding
- supports-color
+ '@mdit-vue/plugin-component@3.0.2':
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+
+ '@mdit-vue/plugin-frontmatter@3.0.2':
+ dependencies:
+ '@mdit-vue/types': 3.0.2
+ '@types/markdown-it': 14.1.2
+ gray-matter: 4.0.3
+ markdown-it: 14.1.0
+
+ '@mdit-vue/types@3.0.2': {}
+
'@miyaneee/rollup-plugin-json5@1.2.0(rollup@4.57.0)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.0)
@@ -11028,7 +11119,7 @@ snapshots:
- magicast
- supports-color
- '@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))':
+ '@nuxt/content@3.11.0(@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3)))(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))':
dependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
'@nuxtjs/mdc': 0.20.0(magicast@0.5.1)
@@ -11079,6 +11170,7 @@ snapshots:
zod: 3.25.76
zod-to-json-schema: 3.25.1(zod@3.25.76)
optionalDependencies:
+ '@valibot/to-json-schema': 1.5.0(valibot@1.2.0(typescript@5.9.3))
better-sqlite3: 12.6.2
valibot: 1.2.0(typescript@5.9.3)
transitivePeerDependencies:
@@ -11567,7 +11659,7 @@ snapshots:
- magicast
- typescript
- '@nuxt/ui@4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)':
+ '@nuxt/ui@4.4.0(@nuxt/content@3.11.0(@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3)))(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)':
dependencies:
'@floating-ui/dom': 1.7.5
'@iconify/vue': 5.0.0(vue@3.5.27(typescript@5.9.3))
@@ -11636,7 +11728,7 @@ snapshots:
vaul-vue: 0.4.1(reka-ui@2.7.0(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))
vue-component-type-helpers: 3.2.4
optionalDependencies:
- '@nuxt/content': 3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))
+ '@nuxt/content': 3.11.0(@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3)))(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))
valibot: 1.2.0(typescript@5.9.3)
vue-router: 4.6.4(vue@3.5.27(typescript@5.9.3))
zod: 4.3.6
@@ -12752,6 +12844,13 @@ snapshots:
dependencies:
'@shikijs/types': 3.21.0
+ '@shikijs/markdown-it@3.21.0(markdown-it-async@2.2.0)':
+ dependencies:
+ markdown-it: 14.1.0
+ shiki: 3.21.0
+ optionalDependencies:
+ markdown-it-async: 2.2.0
+
'@shikijs/themes@3.21.0':
dependencies:
'@shikijs/types': 3.21.0
@@ -13452,6 +13551,10 @@ snapshots:
webpack: 5.104.1(esbuild@0.27.2)
webpack-sources: 3.3.3
+ '@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3))':
+ dependencies:
+ valibot: 1.2.0(typescript@5.9.3)
+
'@vercel/nft@1.3.0(rollup@4.57.0)':
dependencies:
'@mapbox/node-pre-gyp': 2.0.3
@@ -14046,6 +14149,10 @@ snapshots:
- bare-abort-controller
- react-native-b4a
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
argparse@2.0.1: {}
aria-hidden@1.2.6:
@@ -14690,15 +14797,15 @@ snapshots:
diff@8.0.3: {}
- docus@5.4.4(29b67e4ea82958d3bb639aa676be121d):
+ docus@5.4.4(ec964c5fe48ae1161e7f2464bd5d4112):
dependencies:
'@iconify-json/lucide': 1.2.87
'@iconify-json/simple-icons': 1.2.68
'@iconify-json/vscode-icons': 1.2.40
- '@nuxt/content': 3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))
+ '@nuxt/content': 3.11.0(@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3)))(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3))
'@nuxt/image': 2.0.0(db0@0.3.4(better-sqlite3@12.6.2))(ioredis@5.9.2)(magicast@0.5.1)
'@nuxt/kit': 4.3.0(magicast@0.5.1)
- '@nuxt/ui': 4.4.0(@nuxt/content@3.11.0(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)
+ '@nuxt/ui': 4.4.0(@nuxt/content@3.11.0(@valibot/to-json-schema@1.5.0(valibot@1.2.0(typescript@5.9.3)))(better-sqlite3@12.6.2)(magicast@0.5.1)(valibot@1.2.0(typescript@5.9.3)))(@tiptap/extensions@3.17.1(@tiptap/core@3.17.1(@tiptap/pm@3.17.1))(@tiptap/pm@3.17.1))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.5)(y-protocols@1.0.7(yjs@13.6.29))(yjs@13.6.29))(db0@0.3.4(better-sqlite3@12.6.2))(embla-carousel@8.6.0)(ioredis@5.9.2)(magicast@0.5.1)(tailwindcss@4.1.18)(typescript@5.9.3)(valibot@1.2.0(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2))(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.29)(zod@4.3.6)
'@nuxtjs/i18n': 10.2.1(@vue/compiler-dom@3.5.27)(db0@0.3.4(better-sqlite3@12.6.2))(eslint@9.39.2(jiti@2.6.1))(ioredis@5.9.2)(magicast@0.5.1)(rollup@4.57.0)(vue@3.5.27(typescript@5.9.3))
'@nuxtjs/mcp-toolkit': 0.6.2(hono@4.11.7)(magicast@0.5.1)(zod@4.3.6)
'@nuxtjs/mdc': 0.20.0(magicast@0.5.1)
@@ -15264,6 +15371,10 @@ snapshots:
exsolve@1.0.8: {}
+ extend-shallow@2.0.1:
+ dependencies:
+ is-extendable: 0.1.1
+
extend@3.0.2: {}
fake-indexeddb@6.2.5: {}
@@ -15607,6 +15718,13 @@ snapshots:
graceful-fs@4.2.11: {}
+ gray-matter@4.0.3:
+ dependencies:
+ js-yaml: 3.14.2
+ kind-of: 6.0.3
+ section-matter: 1.0.0
+ strip-bom-string: 1.0.0
+
gzip-size@6.0.0:
dependencies:
duplexer: 0.1.2
@@ -16031,6 +16149,8 @@ snapshots:
is-docker@3.0.0: {}
+ is-extendable@0.1.1: {}
+
is-extglob@2.1.1: {}
is-finalizationregistry@1.1.1:
@@ -16241,6 +16361,11 @@ snapshots:
js-tokens@9.0.1: {}
+ js-yaml@3.14.2:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
@@ -16294,6 +16419,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ kind-of@6.0.3: {}
+
kleur@3.0.3: {}
kleur@4.1.5: {}
@@ -16577,6 +16704,11 @@ snapshots:
dependencies:
semver: 7.7.3
+ markdown-it-async@2.2.0:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+
markdown-it@14.1.0:
dependencies:
argparse: 2.0.1
@@ -18602,6 +18734,11 @@ snapshots:
scule@1.3.0: {}
+ section-matter@1.0.0:
+ dependencies:
+ extend-shallow: 2.0.1
+ kind-of: 6.0.3
+
semver@6.3.1: {}
semver@7.7.3: {}
@@ -18852,6 +18989,8 @@ snapshots:
split2@4.2.0: {}
+ sprintf-js@1.0.3: {}
+
srvx@0.10.1: {}
standard-as-callback@2.1.0: {}
@@ -18967,6 +19106,8 @@ snapshots:
dependencies:
ansi-regex: 6.2.2
+ strip-bom-string@1.0.0: {}
+
strip-comments@2.0.1: {}
strip-final-newline@3.0.0: {}
@@ -19464,6 +19605,18 @@ snapshots:
optionalDependencies:
'@nuxt/kit': 4.3.0(magicast@0.5.1)
+ unplugin-vue-markdown@29.2.0(vite@7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)):
+ dependencies:
+ '@mdit-vue/plugin-component': 3.0.2
+ '@mdit-vue/plugin-frontmatter': 3.0.2
+ '@mdit-vue/types': 3.0.2
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ markdown-it-async: 2.2.0
+ unplugin: 2.3.11
+ unplugin-utils: 0.3.1
+ vite: 7.3.1(@types/node@24.10.9)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(yaml@2.8.2)
+
unplugin-vue-router@0.16.2(@vue/compiler-sfc@3.5.27)(vue-router@4.6.4(vue@3.5.27(typescript@5.9.3)))(vue@3.5.27(typescript@5.9.3)):
dependencies:
'@babel/generator': 7.28.6
diff --git a/shared/schemas/blog.ts b/shared/schemas/blog.ts
new file mode 100644
index 00000000..7b8ca834
--- /dev/null
+++ b/shared/schemas/blog.ts
@@ -0,0 +1,18 @@
+import * as v from 'valibot'
+
+export const BlogPostSchema = v.object({
+ author: v.string(),
+ title: v.string(),
+ date: v.string(),
+ description: v.string(),
+ slug: v.string(),
+ excerpt: v.optional(v.string()),
+ tags: v.optional(v.array(v.string())),
+ draft: v.optional(v.boolean()),
+})
+
+/**
+ * Inferred type for blog post frontmatter
+ */
+/** @public */
+export type BlogPostFrontmatter = v.InferOutput
diff --git a/shared/types/blog-post.ts b/shared/types/blog-post.ts
new file mode 100644
index 00000000..55289c4d
--- /dev/null
+++ b/shared/types/blog-post.ts
@@ -0,0 +1,7 @@
+export interface BlogPost {
+ author: string // Potentially Multiple?
+ title: string
+ topics: string[]
+ content: string // MarkDown File
+ published: string // DateTime
+}
diff --git a/uno.config.ts b/uno.config.ts
index 036dceb7..7586b1f5 100644
--- a/uno.config.ts
+++ b/uno.config.ts
@@ -1,6 +1,7 @@
import {
defineConfig,
presetIcons,
+ presetTypography,
presetWind4,
transformerDirectives,
transformerVariantGroup,
@@ -23,6 +24,7 @@ export default defineConfig({
custom: customIcons,
},
}),
+ presetTypography(),
],
transformers: [transformerDirectives(), transformerVariantGroup()],
theme: {