@terrymooreii/sia 2.1.6 → 2.1.8
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.
- package/bin/cli.js +7 -0
- package/docs/README.md +61 -1
- package/docs/creating-themes.md +216 -1
- package/docs/imgs/logo-sia-t.png +0 -0
- package/docs/imgs/logo-sia.png +0 -0
- package/docs/imgs/sia2.png +0 -0
- package/lib/assets.js +14 -7
- package/lib/build.js +8 -4
- package/lib/init.js +3 -3
- package/lib/templates.js +11 -4
- package/lib/theme-resolver.js +175 -0
- package/lib/theme.js +1524 -0
- package/package.json +1 -1
- package/readme.md +110 -1
- package/themes/developer/pages/blog.njk +1 -1
- package/themes/developer/pages/feed.njk +1 -1
- package/themes/developer/pages/index.njk +1 -1
- package/themes/developer/pages/notes.njk +1 -1
- package/themes/magazine/layouts/post.njk +2 -2
- package/themes/magazine/pages/blog.njk +1 -1
- package/themes/magazine/pages/index.njk +3 -3
- package/themes/magazine/pages/notes.njk +1 -1
- package/themes/main/includes/footer.njk +1 -1
- package/themes/main/pages/blog.njk +1 -1
- package/themes/main/pages/index.njk +2 -2
- package/themes/main/pages/tag.njk +3 -3
- package/themes/minimal/includes/footer.njk +1 -1
- package/themes/minimal/pages/blog.njk +1 -1
- package/themes/minimal/pages/index.njk +1 -1
- package/themes/minimal/pages/tag.njk +3 -3
- package/themes/minimal/pages/tags.njk +1 -1
package/lib/theme.js
ADDED
|
@@ -0,0 +1,1524 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ensure a directory exists
|
|
7
|
+
*/
|
|
8
|
+
function ensureDir(dirPath) {
|
|
9
|
+
if (!existsSync(dirPath)) {
|
|
10
|
+
mkdirSync(dirPath, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate package.json for the theme
|
|
16
|
+
*/
|
|
17
|
+
function getPackageJson(themeName, displayName, author) {
|
|
18
|
+
return {
|
|
19
|
+
name: `sia-theme-${themeName}`,
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
description: `${displayName} theme for Sia static site generator`,
|
|
22
|
+
main: 'index.js',
|
|
23
|
+
type: 'module',
|
|
24
|
+
keywords: [
|
|
25
|
+
'sia',
|
|
26
|
+
'sia-theme',
|
|
27
|
+
'static-site',
|
|
28
|
+
'theme'
|
|
29
|
+
],
|
|
30
|
+
author: author,
|
|
31
|
+
license: 'MIT',
|
|
32
|
+
repository: {
|
|
33
|
+
type: 'git',
|
|
34
|
+
url: ''
|
|
35
|
+
},
|
|
36
|
+
peerDependencies: {
|
|
37
|
+
'@terrymooreii/sia': '>=2.0.0'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate index.js that exports the theme directory
|
|
44
|
+
*/
|
|
45
|
+
function getIndexJs() {
|
|
46
|
+
return `import { fileURLToPath } from 'url';
|
|
47
|
+
import { dirname } from 'path';
|
|
48
|
+
|
|
49
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
50
|
+
const __dirname = dirname(__filename);
|
|
51
|
+
|
|
52
|
+
// Export the theme directory path for Sia to use
|
|
53
|
+
export const themeDir = __dirname;
|
|
54
|
+
export default themeDir;
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate README.md for the theme
|
|
60
|
+
*/
|
|
61
|
+
function getReadme(themeName, displayName, author) {
|
|
62
|
+
return `# ${displayName}
|
|
63
|
+
|
|
64
|
+
A custom theme for [Sia](https://github.com/terrymooreii/sia) static site generator.
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
\`\`\`bash
|
|
69
|
+
npm install sia-theme-${themeName}
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
In your site's \`_config.yml\`:
|
|
75
|
+
|
|
76
|
+
\`\`\`yaml
|
|
77
|
+
theme:
|
|
78
|
+
name: ${themeName}
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
## Theme Structure
|
|
82
|
+
|
|
83
|
+
\`\`\`
|
|
84
|
+
sia-theme-${themeName}/
|
|
85
|
+
├── package.json
|
|
86
|
+
├── index.js
|
|
87
|
+
├── README.md
|
|
88
|
+
├── layouts/
|
|
89
|
+
│ ├── base.njk # Base HTML template
|
|
90
|
+
│ ├── post.njk # Blog post template
|
|
91
|
+
│ ├── page.njk # Static page template
|
|
92
|
+
│ └── note.njk # Note template
|
|
93
|
+
├── includes/
|
|
94
|
+
│ ├── header.njk # Site header/navigation
|
|
95
|
+
│ ├── footer.njk # Site footer
|
|
96
|
+
│ ├── hero.njk # Homepage hero section
|
|
97
|
+
│ ├── pagination.njk # Pagination component
|
|
98
|
+
│ └── tag-list.njk # Tag cloud component
|
|
99
|
+
├── pages/
|
|
100
|
+
│ ├── index.njk # Homepage
|
|
101
|
+
│ ├── blog.njk # Blog listing
|
|
102
|
+
│ ├── notes.njk # Notes listing
|
|
103
|
+
│ ├── tags.njk # All tags page
|
|
104
|
+
│ ├── tag.njk # Single tag page
|
|
105
|
+
│ └── feed.njk # RSS feed
|
|
106
|
+
└── styles/
|
|
107
|
+
└── main.css # Theme styles
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
## Customization
|
|
111
|
+
|
|
112
|
+
### Available Template Variables
|
|
113
|
+
|
|
114
|
+
**Site data:**
|
|
115
|
+
- \`site.title\` - Site title
|
|
116
|
+
- \`site.description\` - Site description
|
|
117
|
+
- \`site.author\` - Site author
|
|
118
|
+
- \`site.url\` - Site URL
|
|
119
|
+
|
|
120
|
+
**Collections:**
|
|
121
|
+
- \`collections.posts\` - All blog posts
|
|
122
|
+
- \`collections.pages\` - All pages
|
|
123
|
+
- \`collections.notes\` - All notes
|
|
124
|
+
|
|
125
|
+
**Page data (in content templates):**
|
|
126
|
+
- \`page.title\` - Page title
|
|
127
|
+
- \`page.date\` - Page date
|
|
128
|
+
- \`page.tags\` - Page tags array
|
|
129
|
+
- \`page.content\` - Rendered HTML content
|
|
130
|
+
- \`page.excerpt\` - Text excerpt
|
|
131
|
+
|
|
132
|
+
**Pagination (in listing templates):**
|
|
133
|
+
- \`pagination.pageNumber\` - Current page
|
|
134
|
+
- \`pagination.totalPages\` - Total pages
|
|
135
|
+
- \`pagination.previousUrl\` - Previous page URL
|
|
136
|
+
- \`pagination.nextUrl\` - Next page URL
|
|
137
|
+
|
|
138
|
+
### Available Filters
|
|
139
|
+
|
|
140
|
+
- \`| date('format')\` - Format dates ('short', 'long', 'iso', 'rss', 'full')
|
|
141
|
+
- \`| url\` - Convert relative URLs to absolute with basePath
|
|
142
|
+
- \`| excerpt(length)\` - Get excerpt from content
|
|
143
|
+
- \`| readingTime\` - Estimate reading time
|
|
144
|
+
- \`| limit(n)\` - Limit array to n items
|
|
145
|
+
- \`| slug\` - Convert string to URL slug
|
|
146
|
+
- \`| where(key, value)\` - Filter array by property
|
|
147
|
+
- \`| sortBy(key, order)\` - Sort array by property
|
|
148
|
+
- \`| withTag(tag)\` - Filter items with specific tag
|
|
149
|
+
|
|
150
|
+
## Publishing
|
|
151
|
+
|
|
152
|
+
To publish your theme to npm:
|
|
153
|
+
|
|
154
|
+
1. Update the \`package.json\` with your details
|
|
155
|
+
2. Add a \`repository\` URL
|
|
156
|
+
3. Run \`npm publish\`
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Base layout template
|
|
166
|
+
*/
|
|
167
|
+
function getBaseLayout() {
|
|
168
|
+
return `<!DOCTYPE html>
|
|
169
|
+
<html lang="en">
|
|
170
|
+
<head>
|
|
171
|
+
{% include "meta.njk" %}
|
|
172
|
+
{% include "theme-script.njk" %}
|
|
173
|
+
|
|
174
|
+
<link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
|
|
175
|
+
|
|
176
|
+
{% block head %}{% endblock %}
|
|
177
|
+
</head>
|
|
178
|
+
<body>
|
|
179
|
+
{% include "header.njk" %}
|
|
180
|
+
|
|
181
|
+
<main class="main">
|
|
182
|
+
{% block content %}{% endblock %}
|
|
183
|
+
</main>
|
|
184
|
+
|
|
185
|
+
{% include "footer.njk" %}
|
|
186
|
+
</body>
|
|
187
|
+
</html>
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Post layout template
|
|
193
|
+
*/
|
|
194
|
+
function getPostLayout() {
|
|
195
|
+
return `{% extends "base.njk" %}
|
|
196
|
+
|
|
197
|
+
{% block content %}
|
|
198
|
+
<article class="post">
|
|
199
|
+
<header class="post-header">
|
|
200
|
+
<h1 class="post-title">
|
|
201
|
+
{{ page.title }}
|
|
202
|
+
{% if page.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
203
|
+
</h1>
|
|
204
|
+
|
|
205
|
+
<div class="post-meta">
|
|
206
|
+
<time datetime="{{ page.date | date('iso') }}">{{ page.date | date('long') }}</time>
|
|
207
|
+
{% if page.tags and page.tags.length %}
|
|
208
|
+
<span class="post-meta-divider">·</span>
|
|
209
|
+
<span class="post-tags">
|
|
210
|
+
{% for tag in page.tags %}
|
|
211
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
212
|
+
{% endfor %}
|
|
213
|
+
</span>
|
|
214
|
+
{% endif %}
|
|
215
|
+
<span class="post-meta-divider">·</span>
|
|
216
|
+
<span class="reading-time">{{ page.content | readingTime }}</span>
|
|
217
|
+
</div>
|
|
218
|
+
</header>
|
|
219
|
+
|
|
220
|
+
<div class="post-content prose">
|
|
221
|
+
{{ content | safe }}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<footer class="post-footer">
|
|
225
|
+
<nav class="post-nav">
|
|
226
|
+
<a href="{{ '/blog/' | url }}" class="btn btn-secondary">← Back to Blog</a>
|
|
227
|
+
</nav>
|
|
228
|
+
</footer>
|
|
229
|
+
</article>
|
|
230
|
+
{% endblock %}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Page layout template
|
|
236
|
+
*/
|
|
237
|
+
function getPageLayout() {
|
|
238
|
+
return `{% extends "base.njk" %}
|
|
239
|
+
|
|
240
|
+
{% block content %}
|
|
241
|
+
<article class="page">
|
|
242
|
+
<header class="page-header">
|
|
243
|
+
<h1 class="page-title">{{ page.title }}</h1>
|
|
244
|
+
</header>
|
|
245
|
+
|
|
246
|
+
<div class="page-content prose">
|
|
247
|
+
{{ content | safe }}
|
|
248
|
+
</div>
|
|
249
|
+
</article>
|
|
250
|
+
{% endblock %}
|
|
251
|
+
`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Note layout template
|
|
256
|
+
*/
|
|
257
|
+
function getNoteLayout() {
|
|
258
|
+
return `{% extends "base.njk" %}
|
|
259
|
+
|
|
260
|
+
{% block content %}
|
|
261
|
+
<article class="note">
|
|
262
|
+
<div class="note-content prose">
|
|
263
|
+
{{ content | safe }}
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<footer class="note-footer">
|
|
267
|
+
<time datetime="{{ page.date | date('iso') }}">{{ page.date | date('full_time') }}</time>
|
|
268
|
+
{% if page.tags and page.tags.length %}
|
|
269
|
+
<span class="note-tags">
|
|
270
|
+
{% for tag in page.tags %}
|
|
271
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
|
|
272
|
+
{% endfor %}
|
|
273
|
+
</span>
|
|
274
|
+
{% endif %}
|
|
275
|
+
</footer>
|
|
276
|
+
</article>
|
|
277
|
+
|
|
278
|
+
<nav class="note-nav">
|
|
279
|
+
<a href="{{ '/notes/' | url }}" class="btn btn-secondary">← All Notes</a>
|
|
280
|
+
</nav>
|
|
281
|
+
{% endblock %}
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Header include
|
|
287
|
+
*/
|
|
288
|
+
function getHeaderInclude() {
|
|
289
|
+
return `<header class="site-header">
|
|
290
|
+
<div class="container">
|
|
291
|
+
<a href="{{ '/' | url }}" class="site-logo">{{ site.title }}</a>
|
|
292
|
+
|
|
293
|
+
<nav class="site-nav">
|
|
294
|
+
<a href="{{ '/' | url }}" class="nav-link{% if page.url == ('/' | url) %} active{% endif %}">Home</a>
|
|
295
|
+
<a href="{{ '/blog/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/blog') %} active{% endif %}">Blog</a>
|
|
296
|
+
<a href="{{ '/notes/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/notes') %} active{% endif %}">Notes</a>
|
|
297
|
+
<a href="{{ '/tags/' | url }}" class="nav-link{% if page.url and page.url.startsWith('/tags') %} active{% endif %}">Tags</a>
|
|
298
|
+
{% if collections.pages %}
|
|
299
|
+
{% for p in collections.pages | limit(3) %}
|
|
300
|
+
<a href="{{ p.url }}" class="nav-link{% if page.url == p.url %} active{% endif %}">{{ p.title }}</a>
|
|
301
|
+
{% endfor %}
|
|
302
|
+
{% endif %}
|
|
303
|
+
|
|
304
|
+
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode" title="Toggle dark mode">
|
|
305
|
+
<svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
306
|
+
<circle cx="12" cy="12" r="5"></circle>
|
|
307
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
308
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
309
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
310
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
311
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
312
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
313
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
314
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
315
|
+
</svg>
|
|
316
|
+
<svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
317
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
|
318
|
+
</svg>
|
|
319
|
+
</button>
|
|
320
|
+
</nav>
|
|
321
|
+
</div>
|
|
322
|
+
</header>
|
|
323
|
+
|
|
324
|
+
<script>
|
|
325
|
+
(function() {
|
|
326
|
+
const toggle = document.getElementById('theme-toggle');
|
|
327
|
+
const html = document.documentElement;
|
|
328
|
+
|
|
329
|
+
function getPreferredTheme() {
|
|
330
|
+
const saved = localStorage.getItem('theme');
|
|
331
|
+
if (saved) return saved;
|
|
332
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function setTheme(theme) {
|
|
336
|
+
html.setAttribute('data-theme', theme);
|
|
337
|
+
localStorage.setItem('theme', theme);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
setTheme(getPreferredTheme());
|
|
341
|
+
|
|
342
|
+
toggle.addEventListener('click', function() {
|
|
343
|
+
const current = html.getAttribute('data-theme');
|
|
344
|
+
setTheme(current === 'dark' ? 'light' : 'dark');
|
|
345
|
+
});
|
|
346
|
+
})();
|
|
347
|
+
</script>
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Footer include
|
|
353
|
+
*/
|
|
354
|
+
function getFooterInclude() {
|
|
355
|
+
return `<footer class="site-footer">
|
|
356
|
+
<div class="container">
|
|
357
|
+
<p class="footer-text">
|
|
358
|
+
© {{ "now" | date('year') }} {{ site.author or site.title }}.
|
|
359
|
+
Built with <a href="https://github.com/terrymooreii/sia">Sia</a>.
|
|
360
|
+
</p>
|
|
361
|
+
|
|
362
|
+
<nav class="footer-nav">
|
|
363
|
+
<a href="{{ '/feed.xml' | url }}" class="footer-link">RSS</a>
|
|
364
|
+
<a href="{{ '/tags/' | url }}" class="footer-link">Tags</a>
|
|
365
|
+
</nav>
|
|
366
|
+
</div>
|
|
367
|
+
</footer>
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Hero include
|
|
373
|
+
*/
|
|
374
|
+
function getHeroInclude() {
|
|
375
|
+
return `{% if config.theme.showHero %}
|
|
376
|
+
<section class="hero">
|
|
377
|
+
<h1 class="hero-title">{{ site.title }}</h1>
|
|
378
|
+
<p class="hero-description">{{ site.description }}</p>
|
|
379
|
+
</section>
|
|
380
|
+
{% endif %}
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Pagination include
|
|
386
|
+
*/
|
|
387
|
+
function getPaginationInclude() {
|
|
388
|
+
return `{% if pagination and pagination.totalPages > 1 %}
|
|
389
|
+
<nav class="pagination" aria-label="Pagination">
|
|
390
|
+
<div class="pagination-info">
|
|
391
|
+
Page {{ pagination.pageNumber }} of {{ pagination.totalPages }}
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div class="pagination-links">
|
|
395
|
+
{% if pagination.previousUrl %}
|
|
396
|
+
<a href="{{ pagination.previousUrl }}" class="pagination-link pagination-prev" aria-label="Previous page">
|
|
397
|
+
← Newer
|
|
398
|
+
</a>
|
|
399
|
+
{% else %}
|
|
400
|
+
<span class="pagination-link pagination-prev disabled">← Newer</span>
|
|
401
|
+
{% endif %}
|
|
402
|
+
|
|
403
|
+
{% if pagination.nextUrl %}
|
|
404
|
+
<a href="{{ pagination.nextUrl }}" class="pagination-link pagination-next" aria-label="Next page">
|
|
405
|
+
Older →
|
|
406
|
+
</a>
|
|
407
|
+
{% else %}
|
|
408
|
+
<span class="pagination-link pagination-next disabled">Older →</span>
|
|
409
|
+
{% endif %}
|
|
410
|
+
</div>
|
|
411
|
+
</nav>
|
|
412
|
+
{% endif %}
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Tag list include
|
|
418
|
+
*/
|
|
419
|
+
function getTagListInclude() {
|
|
420
|
+
return `{% if allTags and allTags.length %}
|
|
421
|
+
<div class="tag-cloud">
|
|
422
|
+
{% for tag in allTags %}
|
|
423
|
+
<a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="tag">
|
|
424
|
+
{{ tag.name }}
|
|
425
|
+
<span class="tag-count">({{ tag.count }})</span>
|
|
426
|
+
</a>
|
|
427
|
+
{% endfor %}
|
|
428
|
+
</div>
|
|
429
|
+
{% endif %}
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Index page
|
|
435
|
+
*/
|
|
436
|
+
function getIndexPage() {
|
|
437
|
+
return `{% extends "base.njk" %}
|
|
438
|
+
|
|
439
|
+
{% block content %}
|
|
440
|
+
{% include "hero.njk" %}
|
|
441
|
+
|
|
442
|
+
<section class="section">
|
|
443
|
+
<div class="section-header">
|
|
444
|
+
<h2 class="section-title">Latest Posts</h2>
|
|
445
|
+
<a href="{{ '/blog/' | url }}" class="section-link">View all →</a>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<div class="post-list">
|
|
449
|
+
{% for post in collections.posts | limit(5) %}
|
|
450
|
+
<article class="post-card">
|
|
451
|
+
<h3 class="post-card-title">
|
|
452
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
453
|
+
{% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
454
|
+
</h3>
|
|
455
|
+
<div class="post-card-meta">
|
|
456
|
+
<time datetime="{{ post.date | date('iso') }}">{{ post.date | date('short') }}</time>
|
|
457
|
+
{% if post.tags and post.tags.length %}
|
|
458
|
+
<span class="post-card-tags">
|
|
459
|
+
{% for tag in post.tags | limit(3) %}
|
|
460
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
|
|
461
|
+
{% endfor %}
|
|
462
|
+
</span>
|
|
463
|
+
{% endif %}
|
|
464
|
+
</div>
|
|
465
|
+
<p class="post-card-excerpt">{{ post.excerpt }}</p>
|
|
466
|
+
</article>
|
|
467
|
+
{% else %}
|
|
468
|
+
<p class="empty-message">No posts yet. Create your first post with <code>npx sia new post "My First Post"</code></p>
|
|
469
|
+
{% endfor %}
|
|
470
|
+
</div>
|
|
471
|
+
</section>
|
|
472
|
+
|
|
473
|
+
{% if collections.notes and collections.notes.length %}
|
|
474
|
+
<section class="section">
|
|
475
|
+
<div class="section-header">
|
|
476
|
+
<h2 class="section-title">Recent Notes</h2>
|
|
477
|
+
<a href="{{ '/notes/' | url }}" class="section-link">View all →</a>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
480
|
+
<div class="notes-grid">
|
|
481
|
+
{% for note in collections.notes | limit(3) %}
|
|
482
|
+
<article class="note-card">
|
|
483
|
+
<div class="note-card-content">{{ note.excerptHtml | safe }}</div>
|
|
484
|
+
<footer class="note-card-footer">
|
|
485
|
+
<time datetime="{{ note.date | date('iso') }}">{{ note.date | date('full_time') }}</time>
|
|
486
|
+
<a href="{{ note.url }}" class="note-card-link">View →</a>
|
|
487
|
+
</footer>
|
|
488
|
+
</article>
|
|
489
|
+
{% endfor %}
|
|
490
|
+
</div>
|
|
491
|
+
</section>
|
|
492
|
+
{% endif %}
|
|
493
|
+
{% endblock %}
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Blog page
|
|
499
|
+
*/
|
|
500
|
+
function getBlogPage() {
|
|
501
|
+
return `{% extends "base.njk" %}
|
|
502
|
+
|
|
503
|
+
{% block content %}
|
|
504
|
+
<header class="page-header">
|
|
505
|
+
<h1 class="page-title">Blog</h1>
|
|
506
|
+
<p class="page-description">All posts, newest first</p>
|
|
507
|
+
</header>
|
|
508
|
+
|
|
509
|
+
<div class="post-list">
|
|
510
|
+
{% for post in posts %}
|
|
511
|
+
<article class="post-card">
|
|
512
|
+
<h2 class="post-card-title">
|
|
513
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
514
|
+
{% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
515
|
+
</h2>
|
|
516
|
+
<div class="post-card-meta">
|
|
517
|
+
<time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
|
|
518
|
+
<span class="post-card-reading-time">{{ post.content | readingTime }}</span>
|
|
519
|
+
</div>
|
|
520
|
+
{% if post.tags and post.tags.length %}
|
|
521
|
+
<div class="post-card-tags">
|
|
522
|
+
{% for tag in post.tags %}
|
|
523
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
|
|
524
|
+
{% endfor %}
|
|
525
|
+
</div>
|
|
526
|
+
{% endif %}
|
|
527
|
+
<p class="post-card-excerpt">{{ post.excerpt }}</p>
|
|
528
|
+
<a href="{{ post.url }}" class="post-card-link">Read more →</a>
|
|
529
|
+
</article>
|
|
530
|
+
{% else %}
|
|
531
|
+
<p class="empty-message">No posts yet. Create your first post with <code>npx sia new post "My First Post"</code></p>
|
|
532
|
+
{% endfor %}
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
{% include "pagination.njk" %}
|
|
536
|
+
{% endblock %}
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Notes page
|
|
542
|
+
*/
|
|
543
|
+
function getNotesPage() {
|
|
544
|
+
return `{% extends "base.njk" %}
|
|
545
|
+
|
|
546
|
+
{% block content %}
|
|
547
|
+
<header class="page-header">
|
|
548
|
+
<h1 class="page-title">Notes</h1>
|
|
549
|
+
<p class="page-description">Short thoughts and quick updates</p>
|
|
550
|
+
</header>
|
|
551
|
+
|
|
552
|
+
<div class="notes-stream">
|
|
553
|
+
{% for note in notes %}
|
|
554
|
+
<article class="note-item">
|
|
555
|
+
<div class="note-item-content prose">
|
|
556
|
+
{{ note.content | safe }}
|
|
557
|
+
</div>
|
|
558
|
+
<footer class="note-item-footer">
|
|
559
|
+
<time datetime="{{ note.date | date('iso') }}">{{ note.date | date('full_time') }}</time>
|
|
560
|
+
{% if note.tags and note.tags.length %}
|
|
561
|
+
<div class="note-item-tags">
|
|
562
|
+
{% for tag in note.tags %}
|
|
563
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag tag-sm">{{ tag }}</a>
|
|
564
|
+
{% endfor %}
|
|
565
|
+
</div>
|
|
566
|
+
{% endif %}
|
|
567
|
+
<a href="{{ note.url }}" class="note-item-permalink">Permalink</a>
|
|
568
|
+
</footer>
|
|
569
|
+
</article>
|
|
570
|
+
{% else %}
|
|
571
|
+
<p class="empty-message">No notes yet. Create your first note with <code>npx sia new note "Quick thought"</code></p>
|
|
572
|
+
{% endfor %}
|
|
573
|
+
</div>
|
|
574
|
+
|
|
575
|
+
{% include "pagination.njk" %}
|
|
576
|
+
{% endblock %}
|
|
577
|
+
`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Tags page
|
|
582
|
+
*/
|
|
583
|
+
function getTagsPage() {
|
|
584
|
+
return `{% extends "base.njk" %}
|
|
585
|
+
|
|
586
|
+
{% block content %}
|
|
587
|
+
<header class="page-header">
|
|
588
|
+
<h1 class="page-title">Tags</h1>
|
|
589
|
+
<p class="page-description">Browse all {{ allTags.length }} tags</p>
|
|
590
|
+
</header>
|
|
591
|
+
|
|
592
|
+
{% if allTags and allTags.length %}
|
|
593
|
+
<div class="tags-page">
|
|
594
|
+
{% include "tag-list.njk" %}
|
|
595
|
+
|
|
596
|
+
<div class="tags-detail">
|
|
597
|
+
{% for tag in allTags %}
|
|
598
|
+
<section class="tag-section" id="{{ tag.slug }}">
|
|
599
|
+
<h2 class="tag-section-title">
|
|
600
|
+
<a href="{{ '/tags/' | url }}{{ tag.slug }}/">{{ tag.name }}</a>
|
|
601
|
+
<span class="tag-section-count">({{ tag.count }})</span>
|
|
602
|
+
</h2>
|
|
603
|
+
<ul class="tag-posts">
|
|
604
|
+
{% for item in tag.items | limit(5) %}
|
|
605
|
+
<li>
|
|
606
|
+
<a href="{{ item.url }}">{{ item.title or item.excerpt }}</a>
|
|
607
|
+
<time datetime="{{ item.date | date('iso') }}">{{ item.date | date('short') }}</time>
|
|
608
|
+
</li>
|
|
609
|
+
{% endfor %}
|
|
610
|
+
{% if tag.count > 5 %}
|
|
611
|
+
<li><a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="more-link">View all {{ tag.count }} →</a></li>
|
|
612
|
+
{% endif %}
|
|
613
|
+
</ul>
|
|
614
|
+
</section>
|
|
615
|
+
{% endfor %}
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
{% else %}
|
|
619
|
+
<p class="empty-message">No tags yet. Add tags to your posts in the front matter.</p>
|
|
620
|
+
{% endif %}
|
|
621
|
+
{% endblock %}
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Tag page (single tag)
|
|
627
|
+
*/
|
|
628
|
+
function getTagPage() {
|
|
629
|
+
return `{% extends "base.njk" %}
|
|
630
|
+
|
|
631
|
+
{% block content %}
|
|
632
|
+
<header class="page-header">
|
|
633
|
+
<h1 class="page-title">Tagged: {{ tag.name }}</h1>
|
|
634
|
+
<p class="page-description">{{ tag.count }} item{% if tag.count != 1 %}s{% endif %} tagged with "{{ tag.name }}"</p>
|
|
635
|
+
</header>
|
|
636
|
+
|
|
637
|
+
<div class="post-list">
|
|
638
|
+
{% for post in posts %}
|
|
639
|
+
<article class="post-card">
|
|
640
|
+
<h2 class="post-card-title">
|
|
641
|
+
<a href="{{ post.url }}">{{ post.title or post.excerpt }}</a>
|
|
642
|
+
{% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
643
|
+
</h2>
|
|
644
|
+
<div class="post-card-meta">
|
|
645
|
+
<time datetime="{{ post.date | date('iso') }}">{{ post.date | date('long') }}</time>
|
|
646
|
+
</div>
|
|
647
|
+
{% if post.tags and post.tags.length %}
|
|
648
|
+
<div class="post-card-tags">
|
|
649
|
+
{% for t in post.tags %}
|
|
650
|
+
<a href="{{ '/tags/' | url }}{{ t | slug }}/" class="tag tag-sm{% if t | slug == tag.slug %} active{% endif %}">{{ t }}</a>
|
|
651
|
+
{% endfor %}
|
|
652
|
+
</div>
|
|
653
|
+
{% endif %}
|
|
654
|
+
{% if post.excerpt %}
|
|
655
|
+
<p class="post-card-excerpt">{{ post.excerpt }}</p>
|
|
656
|
+
{% endif %}
|
|
657
|
+
</article>
|
|
658
|
+
{% endfor %}
|
|
659
|
+
</div>
|
|
660
|
+
|
|
661
|
+
{% include "pagination.njk" %}
|
|
662
|
+
|
|
663
|
+
<nav class="page-nav">
|
|
664
|
+
<a href="{{ '/tags/' | url }}" class="btn btn-secondary">← All Tags</a>
|
|
665
|
+
</nav>
|
|
666
|
+
{% endblock %}
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Feed page (RSS)
|
|
672
|
+
*/
|
|
673
|
+
function getFeedPage() {
|
|
674
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
675
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
676
|
+
<channel>
|
|
677
|
+
<title>{{ site.title | safe }}</title>
|
|
678
|
+
<description>{{ site.description | safe }}</description>
|
|
679
|
+
<link>{{ site.url | safe }}</link>
|
|
680
|
+
<atom:link href="{{ site.url | safe }}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
681
|
+
<language>en-us</language>
|
|
682
|
+
<lastBuildDate>{{ buildDate | safe }}</lastBuildDate>
|
|
683
|
+
<generator>Sia Static Site Generator</generator>
|
|
684
|
+
{% for post in posts | limit(20) %}
|
|
685
|
+
<item>
|
|
686
|
+
<title><![CDATA[{{ post.title | safe }}]]></title>
|
|
687
|
+
<link>{{ site.url | safe }}{{ post.url | safe }}</link>
|
|
688
|
+
<guid isPermaLink="true">{{ site.url | safe }}{{ post.url | safe }}</guid>
|
|
689
|
+
<pubDate>{{ post.date | date('rss') }}</pubDate>
|
|
690
|
+
{% if post.tags and post.tags.length %}
|
|
691
|
+
{% for tag in post.tags %}
|
|
692
|
+
<category>{{ tag | safe }}</category>
|
|
693
|
+
{% endfor %}
|
|
694
|
+
{% endif %}
|
|
695
|
+
<description><![CDATA[{{ post.excerpt | safe }}]]></description>
|
|
696
|
+
<content:encoded><![CDATA[{{ post.content | safe }}]]></content:encoded>
|
|
697
|
+
</item>
|
|
698
|
+
{% endfor %}
|
|
699
|
+
</channel>
|
|
700
|
+
</rss>
|
|
701
|
+
`;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Main CSS file
|
|
706
|
+
*/
|
|
707
|
+
function getMainCss(themeName, displayName) {
|
|
708
|
+
return `/* ${displayName} Theme for Sia
|
|
709
|
+
* Customize this file to create your unique theme!
|
|
710
|
+
*/
|
|
711
|
+
|
|
712
|
+
/* ===== CSS Variables ===== */
|
|
713
|
+
:root {
|
|
714
|
+
/* Light Mode Colors */
|
|
715
|
+
--color-bg: #ffffff;
|
|
716
|
+
--color-bg-alt: #f8f9fa;
|
|
717
|
+
--color-text: #212529;
|
|
718
|
+
--color-text-muted: #6c757d;
|
|
719
|
+
--color-text-light: #adb5bd;
|
|
720
|
+
--color-primary: #0d6efd;
|
|
721
|
+
--color-primary-hover: #0b5ed7;
|
|
722
|
+
--color-border: #dee2e6;
|
|
723
|
+
--color-border-light: #e9ecef;
|
|
724
|
+
--color-accent: #dc3545;
|
|
725
|
+
--color-code-bg: #f1f3f5;
|
|
726
|
+
|
|
727
|
+
/* Typography */
|
|
728
|
+
--font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
729
|
+
--font-serif: Georgia, 'Times New Roman', serif;
|
|
730
|
+
--font-mono: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
731
|
+
|
|
732
|
+
/* Sizing */
|
|
733
|
+
--container-width: 720px;
|
|
734
|
+
--container-wide: 1024px;
|
|
735
|
+
--spacing-xs: 0.25rem;
|
|
736
|
+
--spacing-sm: 0.5rem;
|
|
737
|
+
--spacing-md: 1rem;
|
|
738
|
+
--spacing-lg: 1.5rem;
|
|
739
|
+
--spacing-xl: 2rem;
|
|
740
|
+
--spacing-2xl: 3rem;
|
|
741
|
+
--spacing-3xl: 4rem;
|
|
742
|
+
|
|
743
|
+
/* Border Radius */
|
|
744
|
+
--radius-sm: 4px;
|
|
745
|
+
--radius-md: 6px;
|
|
746
|
+
--radius-lg: 8px;
|
|
747
|
+
|
|
748
|
+
/* Shadows */
|
|
749
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
750
|
+
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* Dark Mode */
|
|
754
|
+
[data-theme="dark"] {
|
|
755
|
+
--color-bg: #1a1a2e;
|
|
756
|
+
--color-bg-alt: #16213e;
|
|
757
|
+
--color-text: #eaeaea;
|
|
758
|
+
--color-text-muted: #a0a0a0;
|
|
759
|
+
--color-text-light: #6a6a6a;
|
|
760
|
+
--color-primary: #4dabf7;
|
|
761
|
+
--color-primary-hover: #74c0fc;
|
|
762
|
+
--color-border: #2d3748;
|
|
763
|
+
--color-border-light: #1e2533;
|
|
764
|
+
--color-accent: #f06595;
|
|
765
|
+
--color-code-bg: #0d1117;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/* ===== Reset & Base ===== */
|
|
769
|
+
*, *::before, *::after {
|
|
770
|
+
box-sizing: border-box;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
html {
|
|
774
|
+
font-size: 16px;
|
|
775
|
+
line-height: 1.6;
|
|
776
|
+
-webkit-font-smoothing: antialiased;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
body {
|
|
780
|
+
margin: 0;
|
|
781
|
+
padding: 0;
|
|
782
|
+
font-family: var(--font-sans);
|
|
783
|
+
color: var(--color-text);
|
|
784
|
+
background-color: var(--color-bg);
|
|
785
|
+
min-height: 100vh;
|
|
786
|
+
display: flex;
|
|
787
|
+
flex-direction: column;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
a {
|
|
791
|
+
color: var(--color-primary);
|
|
792
|
+
text-decoration: none;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
a:hover {
|
|
796
|
+
text-decoration: underline;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
img {
|
|
800
|
+
max-width: 100%;
|
|
801
|
+
height: auto;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/* ===== Layout ===== */
|
|
805
|
+
.container {
|
|
806
|
+
width: 100%;
|
|
807
|
+
max-width: var(--container-width);
|
|
808
|
+
margin: 0 auto;
|
|
809
|
+
padding: 0 var(--spacing-lg);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.main {
|
|
813
|
+
flex: 1;
|
|
814
|
+
padding: var(--spacing-2xl) 0 var(--spacing-3xl);
|
|
815
|
+
max-width: var(--container-width);
|
|
816
|
+
margin: 0 auto;
|
|
817
|
+
width: 100%;
|
|
818
|
+
padding-left: var(--spacing-lg);
|
|
819
|
+
padding-right: var(--spacing-lg);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/* ===== Header ===== */
|
|
823
|
+
.site-header {
|
|
824
|
+
border-bottom: 1px solid var(--color-border);
|
|
825
|
+
padding: var(--spacing-lg) 0;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.site-header .container {
|
|
829
|
+
display: flex;
|
|
830
|
+
align-items: center;
|
|
831
|
+
justify-content: space-between;
|
|
832
|
+
max-width: var(--container-wide);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.site-logo {
|
|
836
|
+
font-size: 1.25rem;
|
|
837
|
+
font-weight: 700;
|
|
838
|
+
color: var(--color-text);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
.site-logo:hover {
|
|
842
|
+
color: var(--color-primary);
|
|
843
|
+
text-decoration: none;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.site-nav {
|
|
847
|
+
display: flex;
|
|
848
|
+
gap: var(--spacing-lg);
|
|
849
|
+
align-items: center;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.nav-link {
|
|
853
|
+
color: var(--color-text-muted);
|
|
854
|
+
font-size: 0.9rem;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.nav-link:hover,
|
|
858
|
+
.nav-link.active {
|
|
859
|
+
color: var(--color-text);
|
|
860
|
+
text-decoration: none;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/* Theme Toggle */
|
|
864
|
+
.theme-toggle {
|
|
865
|
+
display: flex;
|
|
866
|
+
align-items: center;
|
|
867
|
+
justify-content: center;
|
|
868
|
+
width: 36px;
|
|
869
|
+
height: 36px;
|
|
870
|
+
padding: 0;
|
|
871
|
+
background: transparent;
|
|
872
|
+
border: 1px solid var(--color-border);
|
|
873
|
+
border-radius: var(--radius-md);
|
|
874
|
+
cursor: pointer;
|
|
875
|
+
color: var(--color-text-muted);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.theme-toggle:hover {
|
|
879
|
+
color: var(--color-text);
|
|
880
|
+
border-color: var(--color-text-muted);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.theme-toggle .icon-sun { display: none; }
|
|
884
|
+
.theme-toggle .icon-moon { display: block; }
|
|
885
|
+
|
|
886
|
+
[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
|
|
887
|
+
[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
|
|
888
|
+
|
|
889
|
+
/* ===== Footer ===== */
|
|
890
|
+
.site-footer {
|
|
891
|
+
border-top: 1px solid var(--color-border);
|
|
892
|
+
padding: var(--spacing-xl) 0;
|
|
893
|
+
margin-top: auto;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.site-footer .container {
|
|
897
|
+
display: flex;
|
|
898
|
+
align-items: center;
|
|
899
|
+
justify-content: space-between;
|
|
900
|
+
max-width: var(--container-wide);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.footer-text {
|
|
904
|
+
color: var(--color-text-muted);
|
|
905
|
+
font-size: 0.875rem;
|
|
906
|
+
margin: 0;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.footer-nav {
|
|
910
|
+
display: flex;
|
|
911
|
+
gap: var(--spacing-lg);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.footer-link {
|
|
915
|
+
color: var(--color-text-muted);
|
|
916
|
+
font-size: 0.875rem;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/* ===== Hero ===== */
|
|
920
|
+
.hero {
|
|
921
|
+
text-align: center;
|
|
922
|
+
padding: var(--spacing-3xl) 0;
|
|
923
|
+
margin-bottom: var(--spacing-2xl);
|
|
924
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.hero-title {
|
|
928
|
+
font-size: 2.5rem;
|
|
929
|
+
font-weight: 700;
|
|
930
|
+
margin: 0 0 var(--spacing-md);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
.hero-description {
|
|
934
|
+
font-size: 1.25rem;
|
|
935
|
+
color: var(--color-text-muted);
|
|
936
|
+
margin: 0;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/* ===== Page Header ===== */
|
|
940
|
+
.page-header {
|
|
941
|
+
margin-bottom: var(--spacing-2xl);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.page-title {
|
|
945
|
+
font-size: 2rem;
|
|
946
|
+
font-weight: 700;
|
|
947
|
+
margin: 0 0 var(--spacing-sm);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.page-description {
|
|
951
|
+
color: var(--color-text-muted);
|
|
952
|
+
margin: 0;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* ===== Sections ===== */
|
|
956
|
+
.section {
|
|
957
|
+
margin-bottom: var(--spacing-3xl);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.section-header {
|
|
961
|
+
display: flex;
|
|
962
|
+
align-items: baseline;
|
|
963
|
+
justify-content: space-between;
|
|
964
|
+
margin-bottom: var(--spacing-xl);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.section-title {
|
|
968
|
+
font-size: 1.5rem;
|
|
969
|
+
font-weight: 600;
|
|
970
|
+
margin: 0;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
.section-link {
|
|
974
|
+
font-size: 0.9rem;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/* ===== Post Cards ===== */
|
|
978
|
+
.post-list {
|
|
979
|
+
display: flex;
|
|
980
|
+
flex-direction: column;
|
|
981
|
+
gap: var(--spacing-xl);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.post-card {
|
|
985
|
+
padding-bottom: var(--spacing-xl);
|
|
986
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.post-card:last-child {
|
|
990
|
+
border-bottom: none;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.post-card-title {
|
|
994
|
+
font-size: 1.25rem;
|
|
995
|
+
font-weight: 600;
|
|
996
|
+
margin: 0 0 var(--spacing-sm);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.post-card-title a {
|
|
1000
|
+
color: var(--color-text);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
.post-card-title a:hover {
|
|
1004
|
+
color: var(--color-primary);
|
|
1005
|
+
text-decoration: none;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
.post-card-meta {
|
|
1009
|
+
font-size: 0.875rem;
|
|
1010
|
+
color: var(--color-text-muted);
|
|
1011
|
+
margin-bottom: var(--spacing-sm);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.post-card-tags {
|
|
1015
|
+
display: flex;
|
|
1016
|
+
flex-wrap: wrap;
|
|
1017
|
+
gap: var(--spacing-xs);
|
|
1018
|
+
margin-bottom: var(--spacing-sm);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.post-card-excerpt {
|
|
1022
|
+
color: var(--color-text-muted);
|
|
1023
|
+
margin: 0;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.post-card-link {
|
|
1027
|
+
font-size: 0.9rem;
|
|
1028
|
+
display: inline-block;
|
|
1029
|
+
margin-top: var(--spacing-sm);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/* ===== Tags ===== */
|
|
1033
|
+
.tag {
|
|
1034
|
+
display: inline-block;
|
|
1035
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
1036
|
+
background: var(--color-bg-alt);
|
|
1037
|
+
border-radius: var(--radius-sm);
|
|
1038
|
+
font-size: 0.8rem;
|
|
1039
|
+
color: var(--color-text-muted);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
.tag:hover {
|
|
1043
|
+
background: var(--color-primary);
|
|
1044
|
+
color: white;
|
|
1045
|
+
text-decoration: none;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.tag-sm {
|
|
1049
|
+
padding: 2px var(--spacing-xs);
|
|
1050
|
+
font-size: 0.75rem;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.tag-cloud {
|
|
1054
|
+
display: flex;
|
|
1055
|
+
flex-wrap: wrap;
|
|
1056
|
+
gap: var(--spacing-sm);
|
|
1057
|
+
margin-bottom: var(--spacing-2xl);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.tag-count {
|
|
1061
|
+
opacity: 0.7;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/* ===== Notes ===== */
|
|
1065
|
+
.notes-grid {
|
|
1066
|
+
display: grid;
|
|
1067
|
+
gap: var(--spacing-lg);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.note-card {
|
|
1071
|
+
padding: var(--spacing-lg);
|
|
1072
|
+
background: var(--color-bg-alt);
|
|
1073
|
+
border-radius: var(--radius-lg);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.note-card-content {
|
|
1077
|
+
margin-bottom: var(--spacing-md);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.note-card-footer {
|
|
1081
|
+
display: flex;
|
|
1082
|
+
justify-content: space-between;
|
|
1083
|
+
font-size: 0.875rem;
|
|
1084
|
+
color: var(--color-text-muted);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.notes-stream {
|
|
1088
|
+
display: flex;
|
|
1089
|
+
flex-direction: column;
|
|
1090
|
+
gap: var(--spacing-xl);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.note-item {
|
|
1094
|
+
padding-bottom: var(--spacing-xl);
|
|
1095
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.note-item-footer {
|
|
1099
|
+
display: flex;
|
|
1100
|
+
gap: var(--spacing-lg);
|
|
1101
|
+
font-size: 0.875rem;
|
|
1102
|
+
color: var(--color-text-muted);
|
|
1103
|
+
margin-top: var(--spacing-md);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/* ===== Post/Page Content ===== */
|
|
1107
|
+
.post-header,
|
|
1108
|
+
.note-footer {
|
|
1109
|
+
margin-bottom: var(--spacing-xl);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.post-title {
|
|
1113
|
+
font-size: 2rem;
|
|
1114
|
+
font-weight: 700;
|
|
1115
|
+
margin: 0 0 var(--spacing-md);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.post-meta {
|
|
1119
|
+
color: var(--color-text-muted);
|
|
1120
|
+
font-size: 0.9rem;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
.post-meta-divider {
|
|
1124
|
+
margin: 0 var(--spacing-sm);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.post-content,
|
|
1128
|
+
.page-content,
|
|
1129
|
+
.note-content {
|
|
1130
|
+
font-size: 1.05rem;
|
|
1131
|
+
line-height: 1.7;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.post-footer {
|
|
1135
|
+
margin-top: var(--spacing-2xl);
|
|
1136
|
+
padding-top: var(--spacing-xl);
|
|
1137
|
+
border-top: 1px solid var(--color-border-light);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/* ===== Prose (Content Styling) ===== */
|
|
1141
|
+
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
|
|
1142
|
+
margin-top: var(--spacing-2xl);
|
|
1143
|
+
margin-bottom: var(--spacing-md);
|
|
1144
|
+
font-weight: 600;
|
|
1145
|
+
line-height: 1.3;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.prose h1 { font-size: 2rem; }
|
|
1149
|
+
.prose h2 { font-size: 1.5rem; }
|
|
1150
|
+
.prose h3 { font-size: 1.25rem; }
|
|
1151
|
+
.prose h4 { font-size: 1.1rem; }
|
|
1152
|
+
|
|
1153
|
+
.prose p {
|
|
1154
|
+
margin: 0 0 var(--spacing-lg);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.prose ul, .prose ol {
|
|
1158
|
+
margin: 0 0 var(--spacing-lg);
|
|
1159
|
+
padding-left: var(--spacing-xl);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.prose li {
|
|
1163
|
+
margin-bottom: var(--spacing-sm);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
.prose blockquote {
|
|
1167
|
+
margin: var(--spacing-xl) 0;
|
|
1168
|
+
padding: var(--spacing-md) var(--spacing-xl);
|
|
1169
|
+
border-left: 4px solid var(--color-primary);
|
|
1170
|
+
background: var(--color-bg-alt);
|
|
1171
|
+
font-style: italic;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.prose pre {
|
|
1175
|
+
margin: var(--spacing-xl) 0;
|
|
1176
|
+
padding: var(--spacing-lg);
|
|
1177
|
+
background: var(--color-code-bg);
|
|
1178
|
+
border-radius: var(--radius-md);
|
|
1179
|
+
overflow-x: auto;
|
|
1180
|
+
font-family: var(--font-mono);
|
|
1181
|
+
font-size: 0.9rem;
|
|
1182
|
+
line-height: 1.5;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
.prose code {
|
|
1186
|
+
font-family: var(--font-mono);
|
|
1187
|
+
font-size: 0.9em;
|
|
1188
|
+
padding: 2px 6px;
|
|
1189
|
+
background: var(--color-code-bg);
|
|
1190
|
+
border-radius: var(--radius-sm);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.prose pre code {
|
|
1194
|
+
padding: 0;
|
|
1195
|
+
background: none;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.prose img {
|
|
1199
|
+
margin: var(--spacing-xl) 0;
|
|
1200
|
+
border-radius: var(--radius-md);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.prose hr {
|
|
1204
|
+
border: none;
|
|
1205
|
+
border-top: 1px solid var(--color-border);
|
|
1206
|
+
margin: var(--spacing-2xl) 0;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.prose table {
|
|
1210
|
+
width: 100%;
|
|
1211
|
+
border-collapse: collapse;
|
|
1212
|
+
margin: var(--spacing-xl) 0;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.prose th, .prose td {
|
|
1216
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
1217
|
+
border: 1px solid var(--color-border);
|
|
1218
|
+
text-align: left;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.prose th {
|
|
1222
|
+
background: var(--color-bg-alt);
|
|
1223
|
+
font-weight: 600;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/* ===== Pagination ===== */
|
|
1227
|
+
.pagination {
|
|
1228
|
+
display: flex;
|
|
1229
|
+
justify-content: center;
|
|
1230
|
+
align-items: center;
|
|
1231
|
+
gap: var(--spacing-xl);
|
|
1232
|
+
margin-top: var(--spacing-2xl);
|
|
1233
|
+
padding-top: var(--spacing-xl);
|
|
1234
|
+
border-top: 1px solid var(--color-border-light);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.pagination-info {
|
|
1238
|
+
color: var(--color-text-muted);
|
|
1239
|
+
font-size: 0.9rem;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
.pagination-links {
|
|
1243
|
+
display: flex;
|
|
1244
|
+
gap: var(--spacing-md);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.pagination-link {
|
|
1248
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
1249
|
+
border: 1px solid var(--color-border);
|
|
1250
|
+
border-radius: var(--radius-md);
|
|
1251
|
+
font-size: 0.9rem;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.pagination-link.disabled {
|
|
1255
|
+
color: var(--color-text-light);
|
|
1256
|
+
pointer-events: none;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/* ===== Buttons ===== */
|
|
1260
|
+
.btn {
|
|
1261
|
+
display: inline-block;
|
|
1262
|
+
padding: var(--spacing-sm) var(--spacing-lg);
|
|
1263
|
+
border-radius: var(--radius-md);
|
|
1264
|
+
font-size: 0.9rem;
|
|
1265
|
+
text-decoration: none;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.btn-secondary {
|
|
1269
|
+
background: var(--color-bg-alt);
|
|
1270
|
+
color: var(--color-text);
|
|
1271
|
+
border: 1px solid var(--color-border);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.btn-secondary:hover {
|
|
1275
|
+
background: var(--color-border-light);
|
|
1276
|
+
text-decoration: none;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/* ===== Utilities ===== */
|
|
1280
|
+
.draft-badge {
|
|
1281
|
+
display: inline-block;
|
|
1282
|
+
padding: 2px 8px;
|
|
1283
|
+
background: var(--color-accent);
|
|
1284
|
+
color: white;
|
|
1285
|
+
font-size: 0.7rem;
|
|
1286
|
+
font-weight: 600;
|
|
1287
|
+
border-radius: var(--radius-sm);
|
|
1288
|
+
text-transform: uppercase;
|
|
1289
|
+
margin-left: var(--spacing-sm);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
.empty-message {
|
|
1293
|
+
color: var(--color-text-muted);
|
|
1294
|
+
font-style: italic;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/* ===== Tags Page ===== */
|
|
1298
|
+
.tags-page {
|
|
1299
|
+
margin-top: var(--spacing-xl);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.tags-detail {
|
|
1303
|
+
margin-top: var(--spacing-2xl);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.tag-section {
|
|
1307
|
+
margin-bottom: var(--spacing-2xl);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
.tag-section-title {
|
|
1311
|
+
font-size: 1.25rem;
|
|
1312
|
+
margin: 0 0 var(--spacing-md);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.tag-section-count {
|
|
1316
|
+
color: var(--color-text-muted);
|
|
1317
|
+
font-weight: normal;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
.tag-posts {
|
|
1321
|
+
list-style: none;
|
|
1322
|
+
padding: 0;
|
|
1323
|
+
margin: 0;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.tag-posts li {
|
|
1327
|
+
display: flex;
|
|
1328
|
+
justify-content: space-between;
|
|
1329
|
+
align-items: baseline;
|
|
1330
|
+
padding: var(--spacing-sm) 0;
|
|
1331
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.tag-posts time {
|
|
1335
|
+
color: var(--color-text-muted);
|
|
1336
|
+
font-size: 0.875rem;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.more-link {
|
|
1340
|
+
font-size: 0.9rem;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/* ===== Responsive ===== */
|
|
1344
|
+
@media (max-width: 768px) {
|
|
1345
|
+
.site-header .container {
|
|
1346
|
+
flex-direction: column;
|
|
1347
|
+
gap: var(--spacing-md);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.site-nav {
|
|
1351
|
+
flex-wrap: wrap;
|
|
1352
|
+
justify-content: center;
|
|
1353
|
+
gap: var(--spacing-md);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.site-footer .container {
|
|
1357
|
+
flex-direction: column;
|
|
1358
|
+
gap: var(--spacing-md);
|
|
1359
|
+
text-align: center;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
.hero-title {
|
|
1363
|
+
font-size: 2rem;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.post-title,
|
|
1367
|
+
.page-title {
|
|
1368
|
+
font-size: 1.5rem;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
`;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* Create theme scaffold
|
|
1376
|
+
*/
|
|
1377
|
+
export async function createTheme(themeName, options = {}) {
|
|
1378
|
+
// Normalize theme name (lowercase, hyphenated)
|
|
1379
|
+
const normalizedName = themeName
|
|
1380
|
+
.toLowerCase()
|
|
1381
|
+
.replace(/\s+/g, '-')
|
|
1382
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
1383
|
+
|
|
1384
|
+
const packageName = `sia-theme-${normalizedName}`;
|
|
1385
|
+
const targetDir = resolve(process.cwd(), packageName);
|
|
1386
|
+
|
|
1387
|
+
console.log(`\n🎨 Creating new Sia theme: ${packageName}\n`);
|
|
1388
|
+
|
|
1389
|
+
// Check if directory exists
|
|
1390
|
+
if (existsSync(targetDir)) {
|
|
1391
|
+
const { proceed } = await prompts({
|
|
1392
|
+
type: 'confirm',
|
|
1393
|
+
name: 'proceed',
|
|
1394
|
+
message: `Directory "${packageName}" already exists. Continue anyway?`,
|
|
1395
|
+
initial: false
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
if (!proceed) {
|
|
1399
|
+
console.log('\n❌ Cancelled.\n');
|
|
1400
|
+
process.exit(0);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Get theme details
|
|
1405
|
+
let answers;
|
|
1406
|
+
|
|
1407
|
+
if (options.quick) {
|
|
1408
|
+
answers = {
|
|
1409
|
+
displayName: themeName.charAt(0).toUpperCase() + themeName.slice(1) + ' Theme',
|
|
1410
|
+
author: 'Anonymous'
|
|
1411
|
+
};
|
|
1412
|
+
} else {
|
|
1413
|
+
answers = await prompts([
|
|
1414
|
+
{
|
|
1415
|
+
type: 'text',
|
|
1416
|
+
name: 'displayName',
|
|
1417
|
+
message: 'Theme display name:',
|
|
1418
|
+
initial: themeName.charAt(0).toUpperCase() + themeName.slice(1) + ' Theme'
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
type: 'text',
|
|
1422
|
+
name: 'author',
|
|
1423
|
+
message: 'Author name:',
|
|
1424
|
+
initial: 'Anonymous'
|
|
1425
|
+
}
|
|
1426
|
+
]);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if (!answers.displayName) {
|
|
1430
|
+
console.log('\n❌ Cancelled.\n');
|
|
1431
|
+
process.exit(0);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
console.log('\n📁 Creating theme structure...\n');
|
|
1435
|
+
|
|
1436
|
+
// Create directories
|
|
1437
|
+
ensureDir(targetDir);
|
|
1438
|
+
ensureDir(join(targetDir, 'layouts'));
|
|
1439
|
+
ensureDir(join(targetDir, 'includes'));
|
|
1440
|
+
ensureDir(join(targetDir, 'pages'));
|
|
1441
|
+
ensureDir(join(targetDir, 'styles'));
|
|
1442
|
+
|
|
1443
|
+
// Create package.json
|
|
1444
|
+
writeFileSync(
|
|
1445
|
+
join(targetDir, 'package.json'),
|
|
1446
|
+
JSON.stringify(getPackageJson(normalizedName, answers.displayName, answers.author), null, 2),
|
|
1447
|
+
'utf-8'
|
|
1448
|
+
);
|
|
1449
|
+
console.log(' ✓ package.json');
|
|
1450
|
+
|
|
1451
|
+
// Create index.js
|
|
1452
|
+
writeFileSync(join(targetDir, 'index.js'), getIndexJs(), 'utf-8');
|
|
1453
|
+
console.log(' ✓ index.js');
|
|
1454
|
+
|
|
1455
|
+
// Create README.md
|
|
1456
|
+
writeFileSync(
|
|
1457
|
+
join(targetDir, 'README.md'),
|
|
1458
|
+
getReadme(normalizedName, answers.displayName, answers.author),
|
|
1459
|
+
'utf-8'
|
|
1460
|
+
);
|
|
1461
|
+
console.log(' ✓ README.md');
|
|
1462
|
+
|
|
1463
|
+
// Create layouts
|
|
1464
|
+
writeFileSync(join(targetDir, 'layouts', 'base.njk'), getBaseLayout(), 'utf-8');
|
|
1465
|
+
writeFileSync(join(targetDir, 'layouts', 'post.njk'), getPostLayout(), 'utf-8');
|
|
1466
|
+
writeFileSync(join(targetDir, 'layouts', 'page.njk'), getPageLayout(), 'utf-8');
|
|
1467
|
+
writeFileSync(join(targetDir, 'layouts', 'note.njk'), getNoteLayout(), 'utf-8');
|
|
1468
|
+
console.log(' ✓ layouts/');
|
|
1469
|
+
|
|
1470
|
+
// Create includes
|
|
1471
|
+
writeFileSync(join(targetDir, 'includes', 'header.njk'), getHeaderInclude(), 'utf-8');
|
|
1472
|
+
writeFileSync(join(targetDir, 'includes', 'footer.njk'), getFooterInclude(), 'utf-8');
|
|
1473
|
+
writeFileSync(join(targetDir, 'includes', 'hero.njk'), getHeroInclude(), 'utf-8');
|
|
1474
|
+
writeFileSync(join(targetDir, 'includes', 'pagination.njk'), getPaginationInclude(), 'utf-8');
|
|
1475
|
+
writeFileSync(join(targetDir, 'includes', 'tag-list.njk'), getTagListInclude(), 'utf-8');
|
|
1476
|
+
console.log(' ✓ includes/');
|
|
1477
|
+
|
|
1478
|
+
// Create pages
|
|
1479
|
+
writeFileSync(join(targetDir, 'pages', 'index.njk'), getIndexPage(), 'utf-8');
|
|
1480
|
+
writeFileSync(join(targetDir, 'pages', 'blog.njk'), getBlogPage(), 'utf-8');
|
|
1481
|
+
writeFileSync(join(targetDir, 'pages', 'notes.njk'), getNotesPage(), 'utf-8');
|
|
1482
|
+
writeFileSync(join(targetDir, 'pages', 'tags.njk'), getTagsPage(), 'utf-8');
|
|
1483
|
+
writeFileSync(join(targetDir, 'pages', 'tag.njk'), getTagPage(), 'utf-8');
|
|
1484
|
+
writeFileSync(join(targetDir, 'pages', 'feed.njk'), getFeedPage(), 'utf-8');
|
|
1485
|
+
console.log(' ✓ pages/');
|
|
1486
|
+
|
|
1487
|
+
// Create styles
|
|
1488
|
+
writeFileSync(
|
|
1489
|
+
join(targetDir, 'styles', 'main.css'),
|
|
1490
|
+
getMainCss(normalizedName, answers.displayName),
|
|
1491
|
+
'utf-8'
|
|
1492
|
+
);
|
|
1493
|
+
console.log(' ✓ styles/');
|
|
1494
|
+
|
|
1495
|
+
// Success message
|
|
1496
|
+
console.log('\n✨ Theme created successfully!\n');
|
|
1497
|
+
console.log('Next steps:\n');
|
|
1498
|
+
console.log(` cd ${packageName}`);
|
|
1499
|
+
console.log(' # Edit the theme files to customize');
|
|
1500
|
+
console.log(' npm publish # When ready to share\n');
|
|
1501
|
+
console.log('To use this theme in a Sia site:\n');
|
|
1502
|
+
console.log(` npm install ${packageName}`);
|
|
1503
|
+
console.log(` # Then set theme.name: "${normalizedName}" in _config.yml\n`);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Theme command handler for CLI
|
|
1508
|
+
*/
|
|
1509
|
+
export async function themeCommand(themeName, options) {
|
|
1510
|
+
if (!themeName) {
|
|
1511
|
+
console.error('❌ Please provide a theme name: sia theme <name>');
|
|
1512
|
+
process.exit(1);
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
try {
|
|
1516
|
+
await createTheme(themeName, options);
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
console.error('❌ Failed to create theme:', err.message);
|
|
1519
|
+
process.exit(1);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
export default { createTheme, themeCommand };
|
|
1524
|
+
|