@aureuma/svelta 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/package.json +34 -2
  2. package/packages/blogkit/CHANGELOG.md +12 -0
  3. package/packages/blogkit/dist/index.d.ts +1 -1
  4. package/packages/blogkit/dist/server/blog.d.ts +68 -1
  5. package/packages/blogkit/dist/server/blog.js +248 -0
  6. package/packages/blogkit/dist/server/index.d.ts +1 -1
  7. package/packages/blogkit/dist/server/index.js +1 -1
  8. package/packages/blogkit/dist/types/blog.d.ts +10 -0
  9. package/.changeset/README.md +0 -8
  10. package/.changeset/config.json +0 -14
  11. package/.changeset/publish-blogkit.md +0 -5
  12. package/.github/workflows/release.yml +0 -65
  13. package/docs/mintlify-blog-study.md +0 -697
  14. package/packages/blogkit/package.json +0 -66
  15. package/packages/blogkit/src/lib/components/blog/Avatar.svelte +0 -15
  16. package/packages/blogkit/src/lib/components/blog/BackLink.svelte +0 -23
  17. package/packages/blogkit/src/lib/components/blog/BlogCard.svelte +0 -37
  18. package/packages/blogkit/src/lib/components/blog/BlogHeroCard.svelte +0 -36
  19. package/packages/blogkit/src/lib/components/blog/Container.svelte +0 -8
  20. package/packages/blogkit/src/lib/components/blog/ImageLightbox.svelte +0 -58
  21. package/packages/blogkit/src/lib/components/blog/MorePosts.svelte +0 -15
  22. package/packages/blogkit/src/lib/components/blog/ShareButtons.svelte +0 -113
  23. package/packages/blogkit/src/lib/components/blog/SummaryCard.svelte +0 -11
  24. package/packages/blogkit/src/lib/components/blog/TagTabs.svelte +0 -32
  25. package/packages/blogkit/src/lib/index.ts +0 -15
  26. package/packages/blogkit/src/lib/server/blog.ts +0 -264
  27. package/packages/blogkit/src/lib/server/index.ts +0 -2
  28. package/packages/blogkit/src/lib/theme/ThemeSwitcher.svelte +0 -34
  29. package/packages/blogkit/src/lib/theme/index.ts +0 -3
  30. package/packages/blogkit/src/lib/theme/store.ts +0 -64
  31. package/packages/blogkit/src/lib/types/blog.ts +0 -36
  32. package/packages/blogkit/svelte.config.js +0 -8
  33. package/packages/blogkit/tsconfig.json +0 -5
  34. package/playwright.config.ts +0 -24
  35. package/postcss.config.cjs +0 -6
  36. package/src/app.css +0 -146
  37. package/src/app.d.ts +0 -13
  38. package/src/app.html +0 -26
  39. package/src/content/blog/ai-summary-cards-with-frontmatter.md +0 -32
  40. package/src/content/blog/announcing-svelta-blog.md +0 -19
  41. package/src/content/blog/best-practices-ship-with-checklists.md +0 -26
  42. package/src/content/blog/building-a-mintlify-inspired-blog.md +0 -49
  43. package/src/content/blog/design-tokens-that-scale.md +0 -47
  44. package/src/content/blog/for-founders-why-speed-matters.md +0 -23
  45. package/src/content/blog/infinite-scroll-with-intersection-observer.md +0 -37
  46. package/src/content/blog/markdown-kitchen-sink.md +0 -101
  47. package/src/content/blog/markdown-pipeline-mdsvex-shiki.md +0 -39
  48. package/src/content/blog/rss-feeds-that-actually-work.md +0 -25
  49. package/src/content/blog/tag-tabs-and-mobile-fade-masks.md +0 -25
  50. package/src/lib/assets/favicon.svg +0 -1
  51. package/src/lib/components/site/SiteFooter.svelte +0 -24
  52. package/src/lib/components/site/SiteHeader.svelte +0 -36
  53. package/src/lib/content/authors.ts +0 -28
  54. package/src/lib/index.ts +0 -1
  55. package/src/lib/server/blog.ts +0 -22
  56. package/src/lib/server/rss.ts +0 -58
  57. package/src/lib/server/seo.ts +0 -31
  58. package/src/lib/stores/theme.ts +0 -10
  59. package/src/lib/types/blog.ts +0 -1
  60. package/src/routes/+layout.svelte +0 -31
  61. package/src/routes/+page.svelte +0 -44
  62. package/src/routes/blog/+page.server.ts +0 -28
  63. package/src/routes/blog/+page.svelte +0 -122
  64. package/src/routes/blog/[slug]/+page.server.ts +0 -39
  65. package/src/routes/blog/[slug]/+page.svelte +0 -118
  66. package/src/routes/blog/posts.json/+server.ts +0 -32
  67. package/src/routes/feed.xml/+server.ts +0 -21
  68. package/static/blog/authors/alex.svg +0 -13
  69. package/static/blog/authors/maria.svg +0 -13
  70. package/static/blog/authors/shawn.svg +0 -13
  71. package/static/blog/covers/ai-summary.svg +0 -38
  72. package/static/blog/covers/design-tokens.svg +0 -37
  73. package/static/blog/covers/infinite-scroll.svg +0 -38
  74. package/static/blog/covers/kitchen-sink.svg +0 -36
  75. package/static/blog/covers/markdown-pipeline.svg +0 -41
  76. package/static/blog/covers/mintlify-style.svg +0 -35
  77. package/static/blog/covers/rss.svg +0 -34
  78. package/static/robots.txt +0 -3
  79. package/svelte.config.js +0 -70
  80. package/tailwind.config.cjs +0 -133
  81. package/tests/blog.spec.ts +0 -63
  82. package/tsconfig.json +0 -21
  83. package/vite.config.ts +0 -14
@@ -1,264 +0,0 @@
1
- import type { BlogAuthor, BlogCategory, BlogPost, BlogPostFull } from '../types/blog';
2
- import { DEV } from 'esm-env';
3
- import matter from 'gray-matter';
4
- import readingTime from 'reading-time';
5
- import { z } from 'zod';
6
-
7
- const frontmatterSchema = z.object({
8
- title: z.string(),
9
- date: z.string(),
10
- category: z.string(),
11
- author: z.string(),
12
- cover: z.string(),
13
- excerpt: z.string().optional(),
14
- summaryAI: z.string().optional(),
15
- tags: z.array(z.string()).optional(),
16
- featured: z.boolean().optional(),
17
- draft: z.boolean().optional()
18
- });
19
-
20
- export type BlogFrontmatter = z.infer<typeof frontmatterSchema>;
21
-
22
- export type BlogFrontmatterAdapter = (args: {
23
- data: unknown;
24
- content: string;
25
- slug: string;
26
- path: string;
27
- }) => BlogFrontmatter;
28
-
29
- type CompiledModule = {
30
- default: BlogPostFull['component'];
31
- };
32
-
33
- export type BlogCreateConfig = {
34
- compiledModules: Record<string, () => Promise<CompiledModule>>;
35
- rawModules: Record<string, () => Promise<string>>;
36
- getAuthor: (id: string) => BlogAuthor;
37
- categoryOrder?: string[];
38
- // Optional adapter for apps with existing frontmatter schemas.
39
- mapFrontmatter?: BlogFrontmatterAdapter;
40
- };
41
-
42
- const DEFAULT_CATEGORY_ORDER = [
43
- 'all',
44
- 'ai-trends',
45
- 'announcements',
46
- 'for-founders',
47
- 'engineering',
48
- 'design',
49
- 'best-practices'
50
- ];
51
-
52
- function slugify(input: string): string {
53
- return input
54
- .toLowerCase()
55
- .trim()
56
- .replace(/['"]/g, '')
57
- .replace(/[^a-z0-9]+/g, '-')
58
- .replace(/^-+|-+$/g, '');
59
- }
60
-
61
- function normalizeCategory(label: string): BlogCategory {
62
- const slug = slugify(label);
63
- return { label, slug };
64
- }
65
-
66
- function parseISODate(date: string): Date {
67
- // Prefer stable UTC parsing for YYYY-MM-DD.
68
- if (/^\\d{4}-\\d{2}-\\d{2}$/.test(date)) {
69
- const d = new Date(`${date}T00:00:00Z`);
70
- if (!Number.isNaN(d.getTime())) return d;
71
- }
72
- const d = new Date(date);
73
- if (Number.isNaN(d.getTime())) throw new Error(`Invalid date: ${date}`);
74
- return d;
75
- }
76
-
77
- const fmtLong = new Intl.DateTimeFormat('en-US', {
78
- month: 'long',
79
- day: 'numeric',
80
- year: 'numeric',
81
- timeZone: 'UTC'
82
- });
83
- const fmtShort = new Intl.DateTimeFormat('en-US', {
84
- month: 'short',
85
- day: 'numeric',
86
- year: 'numeric',
87
- timeZone: 'UTC'
88
- });
89
-
90
- function stripForExcerpt(markdown: string): string {
91
- return (
92
- markdown
93
- // remove fenced code blocks
94
- .replace(/```[\s\S]*?```/g, '')
95
- // remove images
96
- .replace(/!\[[^\]]*\]\([^)]*\)/g, '')
97
- // remove links but keep text
98
- .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
99
- // remove headings markers
100
- .replace(/^#{1,6}\s+/gm, '')
101
- // remove blockquote markers
102
- .replace(/^>\s+/gm, '')
103
- // remove emphasis markers
104
- .replace(/[*_`]/g, '')
105
- // collapse whitespace
106
- .replace(/\s+/g, ' ')
107
- .trim()
108
- );
109
- }
110
-
111
- function excerptFromContent(content: string): string {
112
- const text = stripForExcerpt(content);
113
- if (!text) return '';
114
- return text.length > 180 ? `${text.slice(0, 177).trimEnd()}...` : text;
115
- }
116
-
117
- function minutesToLabels(minutes: number) {
118
- const m = Math.max(1, Math.round(minutes));
119
- const unit = m === 1 ? 'minute' : 'minutes';
120
- return {
121
- minutes: m,
122
- short: `${m} min read`,
123
- long: `${m} ${unit} read`
124
- };
125
- }
126
-
127
- export function createBlog(config: BlogCreateConfig) {
128
- const categoryOrder = config.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
129
-
130
- let cachedMetaIndex: BlogPost[] | null = null;
131
- let cachedFullIndex: BlogPostFull[] | null = null;
132
- let cachedSlugToPath: Map<string, string> | null = null;
133
-
134
- function getSlugToPath(): Map<string, string> {
135
- if (!DEV && cachedSlugToPath) return cachedSlugToPath;
136
-
137
- const m = new Map<string, string>();
138
- const paths = Object.keys(config.rawModules).sort();
139
- for (const path of paths) {
140
- const file = path.split('/').pop();
141
- const slug = file?.replace(/\.md(?:\?.*)?$/, '');
142
- if (!slug) continue;
143
- m.set(slug, path);
144
- }
145
-
146
- if (!DEV) cachedSlugToPath = m;
147
- return m;
148
- }
149
-
150
- async function buildMetaIndex(): Promise<BlogPost[]> {
151
- if (!DEV && cachedMetaIndex) return cachedMetaIndex;
152
-
153
- const posts: BlogPost[] = [];
154
- const paths = Object.keys(config.rawModules).sort();
155
-
156
- for (const path of paths) {
157
- const file = path.split('/').pop();
158
- // Glob keys can include query strings depending on bundler usage; normalize aggressively.
159
- const slug = file?.replace(/\.md(?:\?.*)?$/, '');
160
- if (!slug) continue;
161
-
162
- const rawFn = config.rawModules[path];
163
- if (!rawFn) continue;
164
-
165
- const raw = await rawFn();
166
-
167
- const { data, content } = matter(raw);
168
- const metadata: BlogFrontmatter = config.mapFrontmatter
169
- ? config.mapFrontmatter({ data, content, slug, path })
170
- : frontmatterSchema.parse(data);
171
- if (metadata.draft) continue;
172
-
173
- const dateObj = parseISODate(metadata.date);
174
- const rt = minutesToLabels(readingTime(content).minutes);
175
- const category = normalizeCategory(metadata.category);
176
- const excerpt = metadata.excerpt?.trim() || excerptFromContent(content);
177
-
178
- posts.push({
179
- slug,
180
- title: metadata.title.trim(),
181
- excerpt,
182
- category,
183
- tags: metadata.tags ?? [],
184
- author: config.getAuthor(metadata.author),
185
- date: metadata.date,
186
- dateLong: fmtLong.format(dateObj),
187
- dateShort: fmtShort.format(dateObj),
188
- readingMinutes: rt.minutes,
189
- readingTimeShort: rt.short,
190
- readingTimeLong: rt.long,
191
- cover: metadata.cover,
192
- summaryAI: metadata.summaryAI,
193
- featured: Boolean(metadata.featured)
194
- });
195
- }
196
-
197
- posts.sort((a, b) => parseISODate(b.date).getTime() - parseISODate(a.date).getTime());
198
-
199
- if (!DEV) cachedMetaIndex = posts;
200
- return posts;
201
- }
202
-
203
- async function getAllPosts(): Promise<BlogPost[]> {
204
- return buildMetaIndex();
205
- }
206
-
207
- async function getAllPostsFull(): Promise<BlogPostFull[]> {
208
- if (!DEV && cachedFullIndex) return cachedFullIndex;
209
-
210
- const meta = await buildMetaIndex();
211
- const slugToPath = getSlugToPath();
212
-
213
- const full: BlogPostFull[] = [];
214
- for (const post of meta) {
215
- const path = slugToPath.get(post.slug);
216
- const compiledFn = path ? config.compiledModules[path] : undefined;
217
- if (!compiledFn) continue;
218
- const compiled = await compiledFn();
219
- full.push({ ...post, component: compiled.default });
220
- }
221
-
222
- if (!DEV) cachedFullIndex = full;
223
- return full;
224
- }
225
-
226
- async function getPostBySlug(slug: string): Promise<BlogPostFull | null> {
227
- const meta = await buildMetaIndex();
228
- const post = meta.find((p) => p.slug === slug) ?? null;
229
- if (!post) return null;
230
-
231
- const path = getSlugToPath().get(slug);
232
- const compiledFn = path ? config.compiledModules[path] : undefined;
233
- if (!compiledFn) return null;
234
-
235
- const compiled = await compiledFn();
236
- return { ...post, component: compiled.default };
237
- }
238
-
239
- async function getCategories(): Promise<BlogCategory[]> {
240
- const posts = await getAllPosts();
241
- const map = new Map<string, string>();
242
-
243
- for (const p of posts) map.set(p.category.slug, p.category.label);
244
-
245
- return Array.from(map.entries())
246
- .map(([slug, label]) => ({ slug, label }))
247
- .sort((a, b) => {
248
- const ai = categoryOrder.indexOf(a.slug);
249
- const bi = categoryOrder.indexOf(b.slug);
250
- if (ai === -1 && bi === -1) return a.label.localeCompare(b.label);
251
- if (ai === -1) return 1;
252
- if (bi === -1) return -1;
253
- return ai - bi;
254
- });
255
- }
256
-
257
- async function pickHero(posts?: BlogPost[]): Promise<BlogPost> {
258
- const list = posts ?? (await getAllPosts());
259
- const featured = list.filter((p) => p.featured);
260
- return (featured[0] ?? list[0])!;
261
- }
262
-
263
- return { getAllPosts, getAllPostsFull, getPostBySlug, getCategories, pickHero };
264
- }
@@ -1,2 +0,0 @@
1
- export { createBlog, type BlogCreateConfig } from './blog';
2
-
@@ -1,34 +0,0 @@
1
- <script lang="ts">
2
- import type { ThemeController, ThemeMode } from './store';
3
-
4
- export let controller: ThemeController;
5
-
6
- const options: { id: ThemeMode; label: string }[] = [
7
- { id: 'system', label: 'System' },
8
- { id: 'light', label: 'Light' },
9
- { id: 'dark', label: 'Dark' }
10
- ];
11
-
12
- // Store auto-subscriptions only work on identifiers, so alias it.
13
- const themeMode = controller.themeMode;
14
- $: currentMode = $themeMode;
15
- </script>
16
-
17
- <div class="flex items-center gap-2">
18
- <span class="text-xs font-mono uppercase tracking-[0.6px] text-text-muted">Theme</span>
19
- <div class="inline-flex rounded-full border border-border-soft/10 bg-background-soft p-1">
20
- {#each options as opt (opt.id)}
21
- <button
22
- type="button"
23
- class="rounded-full px-3 py-1 text-xs font-mono uppercase tracking-[0.6px] transition
24
- hover:bg-background-main/60
25
- {(currentMode === opt.id && 'bg-background-main shadow-sm') || 'text-text-sub'}"
26
- onclick={() => controller.setThemeMode(opt.id)}
27
- aria-pressed={currentMode === opt.id}
28
- >
29
- {opt.label}
30
- </button>
31
- {/each}
32
- </div>
33
- </div>
34
-
@@ -1,3 +0,0 @@
1
- export { createThemeController, type ThemeController, type ThemeMode } from './store';
2
- export { default as ThemeSwitcher } from './ThemeSwitcher.svelte';
3
-
@@ -1,64 +0,0 @@
1
- import { BROWSER } from 'esm-env';
2
- import { writable, type Writable } from 'svelte/store';
3
-
4
- export type ThemeMode = 'system' | 'light' | 'dark';
5
-
6
- export type ThemeController = {
7
- storageKey: string;
8
- themeMode: Writable<ThemeMode>;
9
- initTheme: () => void | (() => void);
10
- setThemeMode: (mode: ThemeMode) => void;
11
- };
12
-
13
- function resolve(mode: ThemeMode): 'light' | 'dark' {
14
- if (mode === 'light' || mode === 'dark') return mode;
15
- const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false;
16
- return prefersDark ? 'dark' : 'light';
17
- }
18
-
19
- function apply(mode: ThemeMode) {
20
- const resolved = resolve(mode);
21
- document.documentElement.classList.remove('light', 'dark');
22
- document.documentElement.classList.add(resolved);
23
- document.documentElement.dataset.theme = mode;
24
- }
25
-
26
- export function createThemeController(opts?: { storageKey?: string; defaultMode?: ThemeMode }): ThemeController {
27
- const storageKey = opts?.storageKey ?? 'blogkit-theme';
28
- const defaultMode = opts?.defaultMode ?? 'system';
29
- const themeMode = writable<ThemeMode>(defaultMode);
30
-
31
- function readStored(): ThemeMode {
32
- const v = localStorage.getItem(storageKey);
33
- if (v === 'light' || v === 'dark' || v === 'system') return v;
34
- return defaultMode;
35
- }
36
-
37
- function initTheme() {
38
- if (!BROWSER) return;
39
-
40
- const mode = readStored();
41
- themeMode.set(mode);
42
- apply(mode);
43
-
44
- const mq = window.matchMedia?.('(prefers-color-scheme: dark)');
45
- const onChange = () => {
46
- let current: ThemeMode = mode;
47
- const unsub = themeMode.subscribe((v) => (current = v));
48
- unsub();
49
- if (current === 'system') apply('system');
50
- };
51
-
52
- mq?.addEventListener?.('change', onChange);
53
- return () => mq?.removeEventListener?.('change', onChange);
54
- }
55
-
56
- function setThemeMode(mode: ThemeMode) {
57
- themeMode.set(mode);
58
- if (!BROWSER) return;
59
- localStorage.setItem(storageKey, mode);
60
- apply(mode);
61
- }
62
-
63
- return { storageKey, themeMode, initTheme, setThemeMode };
64
- }
@@ -1,36 +0,0 @@
1
- import type { ComponentType } from 'svelte';
2
-
3
- export type BlogCategory = {
4
- label: string;
5
- slug: string; // "engineering"
6
- };
7
-
8
- export type BlogAuthor = {
9
- id: string;
10
- name: string;
11
- title: string;
12
- avatar: string; // public path under /static
13
- };
14
-
15
- export type BlogPost = {
16
- slug: string;
17
- title: string;
18
- excerpt: string;
19
- category: BlogCategory;
20
- tags: string[];
21
- author: BlogAuthor;
22
- date: string; // ISO (YYYY-MM-DD)
23
- dateLong: string; // "February 8, 2026"
24
- dateShort: string; // "Feb 8, 2026"
25
- readingMinutes: number;
26
- readingTimeShort: string; // "5 min read"
27
- readingTimeLong: string; // "5 minutes read"
28
- cover: string; // public path under /static
29
- summaryAI?: string;
30
- featured: boolean;
31
- };
32
-
33
- export type BlogPostFull = BlogPost & {
34
- component: ComponentType;
35
- };
36
-
@@ -1,8 +0,0 @@
1
- import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
2
-
3
- /** @type {import('@sveltejs/kit').Config} */
4
- const config = {
5
- preprocess: vitePreprocess()
6
- };
7
-
8
- export default config;
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": ["src/**/*.ts", "src/**/*.svelte"],
4
- "exclude": ["dist/**"]
5
- }
@@ -1,24 +0,0 @@
1
- import { defineConfig, devices } from '@playwright/test';
2
-
3
- export default defineConfig({
4
- testDir: './tests',
5
- timeout: 30_000,
6
- expect: {
7
- timeout: 10_000
8
- },
9
- use: {
10
- baseURL: 'http://localhost:4173',
11
- trace: 'on-first-retry'
12
- },
13
- webServer: {
14
- command: 'npm run build && npm run preview -- --port 4173',
15
- port: 4173,
16
- reuseExistingServer: !process.env.CI
17
- },
18
- projects: [
19
- {
20
- name: 'chromium',
21
- use: { ...devices['Desktop Chrome'] }
22
- }
23
- ]
24
- });
@@ -1,6 +0,0 @@
1
- module.exports = {
2
- plugins: {
3
- '@tailwindcss/postcss': {},
4
- autoprefixer: {}
5
- }
6
- };
package/src/app.css DELETED
@@ -1,146 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- /* Theme tokens (Mintlify-inspired, not copied) */
6
- html.light {
7
- color-scheme: light;
8
- --c-background-main: 252 252 252;
9
- --c-background-soft: 245 246 248;
10
- --c-background-invert: 8 12 20;
11
- --c-text-main: 12 18 28;
12
- --c-text-sub: 71 85 105;
13
- --c-text-muted: 100 116 139;
14
- --c-text-invert: 248 250 252;
15
- --c-border-soft: 15 23 42;
16
- --c-brand: 34 197 94;
17
- --c-brand-soft: 220 252 231;
18
- }
19
-
20
- html.dark {
21
- color-scheme: dark;
22
- --c-background-main: 9 12 18;
23
- --c-background-soft: 14 19 28;
24
- --c-background-invert: 250 250 252;
25
- --c-text-main: 238 242 247;
26
- --c-text-sub: 148 163 184;
27
- --c-text-muted: 100 116 139;
28
- --c-text-invert: 10 12 16;
29
- --c-border-soft: 148 163 184;
30
- --c-brand: 52 211 153;
31
- --c-brand-soft: 5 46 22;
32
- }
33
-
34
- html {
35
- background: rgb(var(--c-background-main));
36
- color: rgb(var(--c-text-main));
37
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
38
- Apple Color Emoji, Segoe UI Emoji;
39
- text-rendering: geometricPrecision;
40
- -webkit-font-smoothing: antialiased;
41
- -moz-osx-font-smoothing: grayscale;
42
- }
43
-
44
- body {
45
- min-height: 100dvh;
46
- }
47
-
48
- /* Utilities */
49
- .no-scrollbar {
50
- -ms-overflow-style: none;
51
- scrollbar-width: none;
52
- }
53
- .no-scrollbar::-webkit-scrollbar {
54
- display: none;
55
- }
56
-
57
- /* For the mobile tag bar fade edges */
58
- .fade-mask-x {
59
- -webkit-mask-image: linear-gradient(
60
- to right,
61
- transparent 0,
62
- black 24px,
63
- black calc(100% - 24px),
64
- transparent 100%
65
- );
66
- mask-image: linear-gradient(
67
- to right,
68
- transparent 0,
69
- black 24px,
70
- black calc(100% - 24px),
71
- transparent 100%
72
- );
73
- }
74
-
75
- /* Shiki blocks (Mintlify-like proportions) */
76
- pre.shiki {
77
- border-radius: 6px;
78
- padding: 12px 16px;
79
- line-height: 24px;
80
- font-size: 14px;
81
- overflow-x: auto;
82
- border: 1px solid rgb(var(--c-border-soft) / 0.12);
83
- /* Shiki provides inline light theme styles; we keep a soft fallback. */
84
- background: rgb(var(--c-background-soft));
85
- }
86
-
87
- html.dark pre.shiki {
88
- border-color: rgb(var(--c-border-soft) / 0.2);
89
- background: var(--shiki-dark-bg) !important;
90
- color: var(--shiki-dark) !important;
91
- }
92
-
93
- html.dark pre.shiki span {
94
- color: var(--shiki-dark, inherit) !important;
95
- font-style: var(--shiki-dark-font-style, inherit) !important;
96
- }
97
-
98
- pre.shiki code {
99
- background: transparent !important;
100
- padding: 0 !important;
101
- border-radius: 0 !important;
102
- }
103
-
104
- /* Give in-article images the “frame” + zoom affordance */
105
- .blog-prose img {
106
- cursor: zoom-in;
107
- }
108
-
109
- /* Mintlify-like heading anchors (subtle + only on hover) */
110
- .blog-prose .heading-anchor {
111
- color: inherit;
112
- text-decoration: none;
113
- }
114
- .blog-prose .heading-anchor::after {
115
- content: '#';
116
- margin-left: 8px;
117
- font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
118
- 'Courier New', monospace;
119
- font-size: 12px;
120
- opacity: 0;
121
- color: rgb(var(--c-text-muted));
122
- transition: opacity 120ms ease;
123
- }
124
- .blog-prose h2:hover .heading-anchor::after,
125
- .blog-prose h3:hover .heading-anchor::after {
126
- opacity: 1;
127
- }
128
-
129
- /* Restore spacing around Shiki blocks (we zeroed out base `pre` in typography) */
130
- .blog-prose pre.shiki {
131
- margin: 24px 0;
132
- }
133
-
134
- /* Markdown edge-case hardening */
135
- .blog-prose {
136
- overflow-wrap: anywhere;
137
- }
138
- .blog-prose table {
139
- display: block;
140
- max-width: 100%;
141
- overflow-x: auto;
142
- -webkit-overflow-scrolling: touch;
143
- }
144
- .blog-prose table thead th {
145
- white-space: nowrap;
146
- }
package/src/app.d.ts DELETED
@@ -1,13 +0,0 @@
1
- // See https://svelte.dev/docs/kit/types#app.d.ts
2
- // for information about these interfaces
3
- declare global {
4
- namespace App {
5
- // interface Error {}
6
- // interface Locals {}
7
- // interface PageData {}
8
- // interface PageState {}
9
- // interface Platform {}
10
- }
11
- }
12
-
13
- export {};
package/src/app.html DELETED
@@ -1,26 +0,0 @@
1
- <!doctype html>
2
- <html lang="en" class="light">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <script>
7
- (() => {
8
- try {
9
- const stored = localStorage.getItem('svelta-theme') || 'system';
10
- const prefersDark =
11
- window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
12
- const resolved = stored === 'system' ? (prefersDark ? 'dark' : 'light') : stored;
13
- document.documentElement.classList.remove('light', 'dark');
14
- document.documentElement.classList.add(resolved);
15
- document.documentElement.dataset.theme = stored;
16
- } catch {
17
- document.documentElement.classList.add('light');
18
- }
19
- })();
20
- </script>
21
- %sveltekit.head%
22
- </head>
23
- <body data-sveltekit-preload-data="hover">
24
- <div style="display: contents">%sveltekit.body%</div>
25
- </body>
26
- </html>
@@ -1,32 +0,0 @@
1
- ---
2
- title: "AI Summary Cards With Frontmatter"
3
- date: "2026-02-03"
4
- category: "AI trends"
5
- author: "maria"
6
- cover: "/blog/covers/ai-summary.svg"
7
- tags:
8
- - "UX"
9
- - "Content"
10
- excerpt: "A skimmable summary card that appears near the top of the post, controlled by a single frontmatter field."
11
- summaryAI: "Add an optional `summaryAI` field in frontmatter. If present, render a soft card after the hero image with a mono uppercase label (“AI SUMMARY”) and a short paragraph. If absent, omit the card entirely (Mintlify does this on some posts)."
12
- ---
13
-
14
- Readers don’t always want the whole story. They often want the gist, then decide.
15
-
16
- ## Placement
17
-
18
- The summary works best **after the hero image** and **before the article body**.
19
-
20
- ## Implementation idea
21
-
22
- ```md
23
- summaryAI: "One paragraph that makes the post skimmable."
24
- ```
25
-
26
- Then in the post page:
27
-
28
- ```svelte
29
- {#if post.summaryAI}
30
- <SummaryCard summary={post.summaryAI} />
31
- {/if}
32
- ```
@@ -1,19 +0,0 @@
1
- ---
2
- title: "Announcing the svelta Blog"
3
- date: "2026-01-30"
4
- category: "Announcements"
5
- author: "shawn"
6
- cover: "/blog/covers/mintlify-style.svg"
7
- tags:
8
- - "Launch"
9
- excerpt: "A small, fast blog system that feels like product documentation: clean, structured, and easy to skim."
10
- ---
11
-
12
- We’re publishing more of the thinking behind svelta: engineering decisions, design constraints, and practical patterns.
13
-
14
- ## What you’ll see here
15
-
16
- - Implementation notes (with real code)
17
- - Design systems that stay crisp in dark mode
18
- - Product-focused writing, not marketing fluff
19
-