@terrymooreii/sia 2.1.6 → 2.1.7

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/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
+