@terrymooreii/sia 2.1.5 → 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/_config.yml +3 -1
- package/bin/cli.js +7 -0
- package/docs/README.md +135 -0
- package/docs/creating-themes.md +987 -0
- package/docs/front-matter.md +557 -0
- package/docs/markdown-guide.md +536 -0
- package/docs/template-reference.md +581 -0
- package/lib/assets.js +15 -8
- package/lib/build.js +8 -4
- package/lib/config.js +3 -1
- package/lib/content.js +74 -2
- package/lib/init.js +3 -3
- package/lib/templates.js +14 -6
- package/lib/theme-resolver.js +175 -0
- package/lib/theme.js +1524 -0
- package/package.json +1 -1
- package/readme.md +51 -2
- package/themes/developer/includes/hero.njk +6 -0
- package/themes/developer/pages/index.njk +2 -5
- package/themes/magazine/includes/hero.njk +8 -0
- package/themes/magazine/pages/index.njk +4 -9
- package/themes/main/includes/footer.njk +1 -1
- package/themes/main/includes/hero.njk +6 -0
- package/themes/main/pages/index.njk +2 -5
- package/themes/minimal/includes/footer.njk +1 -1
- package/themes/minimal/includes/hero.njk +6 -0
- package/themes/minimal/pages/index.njk +2 -5
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
# Creating Themes
|
|
2
|
+
|
|
3
|
+
This guide explains how to create custom themes for Sia, distribute them as npm packages, and customize existing themes.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Theme Overview](#theme-overview)
|
|
8
|
+
- [Theme Structure](#theme-structure)
|
|
9
|
+
- [Required Files](#required-files)
|
|
10
|
+
- [Layouts](#layouts)
|
|
11
|
+
- [Pages](#pages)
|
|
12
|
+
- [Includes](#includes)
|
|
13
|
+
- [Styles](#styles)
|
|
14
|
+
- [Shared Includes](#shared-includes)
|
|
15
|
+
- [Dark Mode Support](#dark-mode-support)
|
|
16
|
+
- [External Theme Packages](#external-theme-packages)
|
|
17
|
+
- [Creating a Theme Package](#creating-a-theme-package)
|
|
18
|
+
- [Publishing Your Theme](#publishing-your-theme)
|
|
19
|
+
- [Customizing Existing Themes](#customizing-existing-themes)
|
|
20
|
+
- [Built-in Themes](#built-in-themes)
|
|
21
|
+
- [Best Practices](#best-practices)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Theme Overview
|
|
26
|
+
|
|
27
|
+
Sia themes control the visual appearance and structure of your site. A theme consists of:
|
|
28
|
+
|
|
29
|
+
- **Layouts** - Base templates that wrap content (post, page, note layouts)
|
|
30
|
+
- **Pages** - Templates for listing pages (homepage, blog, tags)
|
|
31
|
+
- **Includes** - Reusable components (header, footer, pagination)
|
|
32
|
+
- **Styles** - CSS files for styling
|
|
33
|
+
|
|
34
|
+
### Selecting a Theme
|
|
35
|
+
|
|
36
|
+
Set your theme in `_config.yml`:
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
theme:
|
|
40
|
+
name: minimal # Options: main, minimal, developer, magazine
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The theme configuration is an object that allows for additional theme-specific options in the future.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Theme Structure
|
|
48
|
+
|
|
49
|
+
A complete theme follows this directory structure:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
themes/your-theme/
|
|
53
|
+
├── layouts/
|
|
54
|
+
│ ├── base.njk # Base HTML structure
|
|
55
|
+
│ ├── post.njk # Blog post layout
|
|
56
|
+
│ ├── page.njk # Static page layout
|
|
57
|
+
│ └── note.njk # Note layout
|
|
58
|
+
├── includes/
|
|
59
|
+
│ ├── header.njk # Site header/navigation
|
|
60
|
+
│ ├── footer.njk # Site footer
|
|
61
|
+
│ ├── hero.njk # Homepage hero section
|
|
62
|
+
│ ├── pagination.njk # Pagination component
|
|
63
|
+
│ └── tag-list.njk # Tag cloud/list component
|
|
64
|
+
├── pages/
|
|
65
|
+
│ ├── index.njk # Homepage
|
|
66
|
+
│ ├── blog.njk # Blog listing
|
|
67
|
+
│ ├── notes.njk # Notes listing
|
|
68
|
+
│ ├── tags.njk # All tags page
|
|
69
|
+
│ ├── tag.njk # Individual tag page
|
|
70
|
+
│ └── feed.njk # RSS feed template
|
|
71
|
+
└── styles/
|
|
72
|
+
└── main.css # Theme styles
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Required Files
|
|
78
|
+
|
|
79
|
+
### Layouts (Required)
|
|
80
|
+
|
|
81
|
+
| File | Purpose |
|
|
82
|
+
|------|---------|
|
|
83
|
+
| `base.njk` | Base HTML structure, includes head and body |
|
|
84
|
+
| `post.njk` | Template for blog posts |
|
|
85
|
+
| `page.njk` | Template for static pages |
|
|
86
|
+
| `note.njk` | Template for notes |
|
|
87
|
+
|
|
88
|
+
### Pages (Required)
|
|
89
|
+
|
|
90
|
+
| File | Purpose |
|
|
91
|
+
|------|---------|
|
|
92
|
+
| `index.njk` | Homepage |
|
|
93
|
+
| `blog.njk` | Blog listing with pagination |
|
|
94
|
+
| `notes.njk` | Notes listing with pagination |
|
|
95
|
+
| `tags.njk` | All tags overview page |
|
|
96
|
+
| `tag.njk` | Individual tag page with pagination |
|
|
97
|
+
| `feed.njk` | RSS feed (XML) |
|
|
98
|
+
|
|
99
|
+
### Includes (Recommended)
|
|
100
|
+
|
|
101
|
+
| File | Purpose |
|
|
102
|
+
|------|---------|
|
|
103
|
+
| `header.njk` | Site header and navigation |
|
|
104
|
+
| `footer.njk` | Site footer |
|
|
105
|
+
| `hero.njk` | Homepage hero section |
|
|
106
|
+
| `pagination.njk` | Pagination navigation |
|
|
107
|
+
| `tag-list.njk` | Tag cloud or list |
|
|
108
|
+
|
|
109
|
+
### Styles (Required)
|
|
110
|
+
|
|
111
|
+
| File | Purpose |
|
|
112
|
+
|------|---------|
|
|
113
|
+
| `main.css` | All theme styles |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Layouts
|
|
118
|
+
|
|
119
|
+
### base.njk
|
|
120
|
+
|
|
121
|
+
The base layout provides the HTML structure for all pages:
|
|
122
|
+
|
|
123
|
+
```nunjucks
|
|
124
|
+
<!DOCTYPE html>
|
|
125
|
+
<html lang="en">
|
|
126
|
+
<head>
|
|
127
|
+
{% include "meta.njk" %}
|
|
128
|
+
{% include "theme-script.njk" %}
|
|
129
|
+
|
|
130
|
+
<link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
|
|
131
|
+
|
|
132
|
+
{% block head %}{% endblock %}
|
|
133
|
+
</head>
|
|
134
|
+
<body>
|
|
135
|
+
{% include "header.njk" %}
|
|
136
|
+
|
|
137
|
+
<main class="main">
|
|
138
|
+
{% block content %}{% endblock %}
|
|
139
|
+
</main>
|
|
140
|
+
|
|
141
|
+
{% include "footer.njk" %}
|
|
142
|
+
</body>
|
|
143
|
+
</html>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Key points:**
|
|
147
|
+
- Include `meta.njk` for SEO meta tags
|
|
148
|
+
- Include `theme-script.njk` to prevent dark mode flash
|
|
149
|
+
- Use `{% block content %}` for page-specific content
|
|
150
|
+
- Use the `url` filter for all paths
|
|
151
|
+
|
|
152
|
+
### post.njk
|
|
153
|
+
|
|
154
|
+
Template for blog posts:
|
|
155
|
+
|
|
156
|
+
```nunjucks
|
|
157
|
+
{% extends "base.njk" %}
|
|
158
|
+
|
|
159
|
+
{% block content %}
|
|
160
|
+
<article class="post">
|
|
161
|
+
<header class="post-header">
|
|
162
|
+
<h1 class="post-title">
|
|
163
|
+
{{ page.title }}
|
|
164
|
+
{% if page.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
165
|
+
</h1>
|
|
166
|
+
|
|
167
|
+
<div class="post-meta">
|
|
168
|
+
<time datetime="{{ page.date | date('iso') }}">
|
|
169
|
+
{{ page.date | date('long') }}
|
|
170
|
+
</time>
|
|
171
|
+
|
|
172
|
+
{% if page.tags and page.tags.length %}
|
|
173
|
+
<span class="post-tags">
|
|
174
|
+
{% for tag in page.tags %}
|
|
175
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
|
|
176
|
+
{% endfor %}
|
|
177
|
+
</span>
|
|
178
|
+
{% endif %}
|
|
179
|
+
|
|
180
|
+
<span class="reading-time">{{ page.content | readingTime }}</span>
|
|
181
|
+
</div>
|
|
182
|
+
</header>
|
|
183
|
+
|
|
184
|
+
<div class="post-content prose">
|
|
185
|
+
{{ content | safe }}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<footer class="post-footer">
|
|
189
|
+
<a href="{{ '/blog/' | url }}">← Back to Blog</a>
|
|
190
|
+
</footer>
|
|
191
|
+
</article>
|
|
192
|
+
{% endblock %}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### page.njk
|
|
196
|
+
|
|
197
|
+
Template for static pages:
|
|
198
|
+
|
|
199
|
+
```nunjucks
|
|
200
|
+
{% extends "base.njk" %}
|
|
201
|
+
|
|
202
|
+
{% block content %}
|
|
203
|
+
<article class="page">
|
|
204
|
+
<header class="page-header">
|
|
205
|
+
<h1 class="page-title">{{ page.title }}</h1>
|
|
206
|
+
</header>
|
|
207
|
+
|
|
208
|
+
<div class="page-content prose">
|
|
209
|
+
{{ content | safe }}
|
|
210
|
+
</div>
|
|
211
|
+
</article>
|
|
212
|
+
{% endblock %}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### note.njk
|
|
216
|
+
|
|
217
|
+
Template for notes:
|
|
218
|
+
|
|
219
|
+
```nunjucks
|
|
220
|
+
{% extends "base.njk" %}
|
|
221
|
+
|
|
222
|
+
{% block content %}
|
|
223
|
+
<article class="note">
|
|
224
|
+
<div class="note-content prose">
|
|
225
|
+
{{ content | safe }}
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<footer class="note-footer">
|
|
229
|
+
<time datetime="{{ page.date | date('iso') }}">
|
|
230
|
+
{{ page.date | date('full_time') }}
|
|
231
|
+
</time>
|
|
232
|
+
|
|
233
|
+
{% if page.tags and page.tags.length %}
|
|
234
|
+
<span class="note-tags">
|
|
235
|
+
{% for tag in page.tags %}
|
|
236
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
|
|
237
|
+
{% endfor %}
|
|
238
|
+
</span>
|
|
239
|
+
{% endif %}
|
|
240
|
+
</footer>
|
|
241
|
+
</article>
|
|
242
|
+
|
|
243
|
+
<nav class="note-nav">
|
|
244
|
+
<a href="{{ '/notes/' | url }}">← All Notes</a>
|
|
245
|
+
</nav>
|
|
246
|
+
{% endblock %}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Pages
|
|
252
|
+
|
|
253
|
+
### index.njk (Homepage)
|
|
254
|
+
|
|
255
|
+
```nunjucks
|
|
256
|
+
{% extends "base.njk" %}
|
|
257
|
+
|
|
258
|
+
{% block content %}
|
|
259
|
+
{% include "hero.njk" %}
|
|
260
|
+
|
|
261
|
+
<section class="recent-posts">
|
|
262
|
+
<h2>Latest Posts</h2>
|
|
263
|
+
|
|
264
|
+
{% for post in collections.posts | limit(5) %}
|
|
265
|
+
<article class="post-card">
|
|
266
|
+
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
|
|
267
|
+
<time>{{ post.date | date('short') }}</time>
|
|
268
|
+
<p>{{ post.excerpt }}</p>
|
|
269
|
+
</article>
|
|
270
|
+
{% else %}
|
|
271
|
+
<p>No posts yet.</p>
|
|
272
|
+
{% endfor %}
|
|
273
|
+
|
|
274
|
+
<a href="{{ '/blog/' | url }}">View all posts →</a>
|
|
275
|
+
</section>
|
|
276
|
+
|
|
277
|
+
{% if collections.notes and collections.notes.length %}
|
|
278
|
+
<section class="recent-notes">
|
|
279
|
+
<h2>Recent Notes</h2>
|
|
280
|
+
|
|
281
|
+
{% for note in collections.notes | limit(3) %}
|
|
282
|
+
<article class="note-card">
|
|
283
|
+
<div>{{ note.excerptHtml | safe }}</div>
|
|
284
|
+
<time>{{ note.date | date('full_time') }}</time>
|
|
285
|
+
</article>
|
|
286
|
+
{% endfor %}
|
|
287
|
+
</section>
|
|
288
|
+
{% endif %}
|
|
289
|
+
{% endblock %}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### blog.njk (Blog Listing)
|
|
293
|
+
|
|
294
|
+
```nunjucks
|
|
295
|
+
{% extends "base.njk" %}
|
|
296
|
+
|
|
297
|
+
{% block content %}
|
|
298
|
+
<header class="page-header">
|
|
299
|
+
<h1>Blog</h1>
|
|
300
|
+
<p>All posts, newest first</p>
|
|
301
|
+
</header>
|
|
302
|
+
|
|
303
|
+
<div class="post-list">
|
|
304
|
+
{% for post in posts %}
|
|
305
|
+
<article class="post-card">
|
|
306
|
+
<h2>
|
|
307
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
308
|
+
{% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
309
|
+
</h2>
|
|
310
|
+
<div class="post-meta">
|
|
311
|
+
<time>{{ post.date | date('long') }}</time>
|
|
312
|
+
<span>{{ post.content | readingTime }}</span>
|
|
313
|
+
</div>
|
|
314
|
+
{% if post.tags and post.tags.length %}
|
|
315
|
+
<div class="tags">
|
|
316
|
+
{% for tag in post.tags %}
|
|
317
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/">{{ tag }}</a>
|
|
318
|
+
{% endfor %}
|
|
319
|
+
</div>
|
|
320
|
+
{% endif %}
|
|
321
|
+
<p>{{ post.excerpt }}</p>
|
|
322
|
+
</article>
|
|
323
|
+
{% else %}
|
|
324
|
+
<p>No posts yet.</p>
|
|
325
|
+
{% endfor %}
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
{% include "pagination.njk" %}
|
|
329
|
+
{% endblock %}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### tag.njk (Individual Tag Page)
|
|
333
|
+
|
|
334
|
+
```nunjucks
|
|
335
|
+
{% extends "base.njk" %}
|
|
336
|
+
|
|
337
|
+
{% block content %}
|
|
338
|
+
<header class="page-header">
|
|
339
|
+
<h1>Tagged: {{ tag.name }}</h1>
|
|
340
|
+
<p>{{ tag.count }} item{% if tag.count != 1 %}s{% endif %}</p>
|
|
341
|
+
</header>
|
|
342
|
+
|
|
343
|
+
<div class="post-list">
|
|
344
|
+
{% for post in posts %}
|
|
345
|
+
<article class="post-card">
|
|
346
|
+
<h2><a href="{{ post.url }}">{{ post.title or post.excerpt }}</a></h2>
|
|
347
|
+
<time>{{ post.date | date('long') }}</time>
|
|
348
|
+
</article>
|
|
349
|
+
{% endfor %}
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{% include "pagination.njk" %}
|
|
353
|
+
|
|
354
|
+
<a href="{{ '/tags/' | url }}">← All Tags</a>
|
|
355
|
+
{% endblock %}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### feed.njk (RSS Feed)
|
|
359
|
+
|
|
360
|
+
```nunjucks
|
|
361
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
362
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
363
|
+
<channel>
|
|
364
|
+
<title>{{ site.title | safe }}</title>
|
|
365
|
+
<description>{{ site.description | safe }}</description>
|
|
366
|
+
<link>{{ site.url | safe }}</link>
|
|
367
|
+
<atom:link href="{{ site.url | safe }}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
368
|
+
<language>en-us</language>
|
|
369
|
+
<lastBuildDate>{{ buildDate | safe }}</lastBuildDate>
|
|
370
|
+
<generator>Sia Static Site Generator</generator>
|
|
371
|
+
{% for post in posts | limit(20) %}
|
|
372
|
+
<item>
|
|
373
|
+
<title><![CDATA[{{ post.title | safe }}]]></title>
|
|
374
|
+
<link>{{ site.url | safe }}{{ post.url | safe }}</link>
|
|
375
|
+
<guid isPermaLink="true">{{ site.url | safe }}{{ post.url | safe }}</guid>
|
|
376
|
+
<pubDate>{{ post.date | date('rss') }}</pubDate>
|
|
377
|
+
{% for tag in post.tags %}
|
|
378
|
+
<category>{{ tag | safe }}</category>
|
|
379
|
+
{% endfor %}
|
|
380
|
+
<description><![CDATA[{{ post.excerpt | safe }}]]></description>
|
|
381
|
+
<content:encoded><![CDATA[{{ post.content | safe }}]]></content:encoded>
|
|
382
|
+
</item>
|
|
383
|
+
{% endfor %}
|
|
384
|
+
</channel>
|
|
385
|
+
</rss>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Includes
|
|
391
|
+
|
|
392
|
+
### header.njk
|
|
393
|
+
|
|
394
|
+
```nunjucks
|
|
395
|
+
<header class="site-header">
|
|
396
|
+
<a href="{{ '/' | url }}" class="site-logo">{{ site.title }}</a>
|
|
397
|
+
|
|
398
|
+
<nav class="site-nav">
|
|
399
|
+
<a href="{{ '/' | url }}">Home</a>
|
|
400
|
+
<a href="{{ '/blog/' | url }}">Blog</a>
|
|
401
|
+
<a href="{{ '/notes/' | url }}">Notes</a>
|
|
402
|
+
<a href="{{ '/tags/' | url }}">Tags</a>
|
|
403
|
+
|
|
404
|
+
{% for p in collections.pages | limit(3) %}
|
|
405
|
+
<a href="{{ p.url }}">{{ p.title }}</a>
|
|
406
|
+
{% endfor %}
|
|
407
|
+
|
|
408
|
+
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode">
|
|
409
|
+
<!-- Sun/Moon icons -->
|
|
410
|
+
</button>
|
|
411
|
+
</nav>
|
|
412
|
+
</header>
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### footer.njk
|
|
416
|
+
|
|
417
|
+
```nunjucks
|
|
418
|
+
<footer class="site-footer">
|
|
419
|
+
<p>© {{ 'now' | date('year') }} {{ site.title }}</p>
|
|
420
|
+
<p>Built with <a href="https://github.com/terrymooreii/sia">Sia</a></p>
|
|
421
|
+
</footer>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### pagination.njk
|
|
425
|
+
|
|
426
|
+
```nunjucks
|
|
427
|
+
{% if pagination and pagination.totalPages > 1 %}
|
|
428
|
+
<nav class="pagination">
|
|
429
|
+
<span>Page {{ pagination.pageNumber }} of {{ pagination.totalPages }}</span>
|
|
430
|
+
|
|
431
|
+
{% if pagination.previousUrl %}
|
|
432
|
+
<a href="{{ pagination.previousUrl }}">← Newer</a>
|
|
433
|
+
{% else %}
|
|
434
|
+
<span class="disabled">← Newer</span>
|
|
435
|
+
{% endif %}
|
|
436
|
+
|
|
437
|
+
{% if pagination.nextUrl %}
|
|
438
|
+
<a href="{{ pagination.nextUrl }}">Older →</a>
|
|
439
|
+
{% else %}
|
|
440
|
+
<span class="disabled">Older →</span>
|
|
441
|
+
{% endif %}
|
|
442
|
+
</nav>
|
|
443
|
+
{% endif %}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### tag-list.njk
|
|
447
|
+
|
|
448
|
+
```nunjucks
|
|
449
|
+
{% if allTags and allTags.length %}
|
|
450
|
+
<div class="tag-list">
|
|
451
|
+
{% for tag in allTags %}
|
|
452
|
+
<a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="tag">
|
|
453
|
+
{{ tag.name }} ({{ tag.count }})
|
|
454
|
+
</a>
|
|
455
|
+
{% endfor %}
|
|
456
|
+
</div>
|
|
457
|
+
{% endif %}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### hero.njk
|
|
461
|
+
|
|
462
|
+
The hero section is displayed on the homepage when `config.theme.showHero` is enabled:
|
|
463
|
+
|
|
464
|
+
```nunjucks
|
|
465
|
+
{% if config.theme.showHero %}
|
|
466
|
+
<section class="hero">
|
|
467
|
+
<h1 class="hero-title">{{ site.title }}</h1>
|
|
468
|
+
<p class="hero-description">{{ site.description }}</p>
|
|
469
|
+
</section>
|
|
470
|
+
{% endif %}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Enable the hero section in your `_config.yml`:
|
|
474
|
+
|
|
475
|
+
```yaml
|
|
476
|
+
theme:
|
|
477
|
+
name: main
|
|
478
|
+
showHero: true
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Styles
|
|
484
|
+
|
|
485
|
+
### CSS Structure
|
|
486
|
+
|
|
487
|
+
A typical `main.css` structure:
|
|
488
|
+
|
|
489
|
+
```css
|
|
490
|
+
/* Reset and base styles */
|
|
491
|
+
*, *::before, *::after {
|
|
492
|
+
box-sizing: border-box;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* CSS Variables for theming */
|
|
496
|
+
:root {
|
|
497
|
+
--color-bg: #ffffff;
|
|
498
|
+
--color-text: #1a1a1a;
|
|
499
|
+
--color-primary: #0066cc;
|
|
500
|
+
--color-muted: #666666;
|
|
501
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
502
|
+
--font-mono: ui-monospace, monospace;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
[data-theme="dark"] {
|
|
506
|
+
--color-bg: #1a1a1a;
|
|
507
|
+
--color-text: #f0f0f0;
|
|
508
|
+
--color-primary: #66b3ff;
|
|
509
|
+
--color-muted: #999999;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
body {
|
|
513
|
+
font-family: var(--font-sans);
|
|
514
|
+
background: var(--color-bg);
|
|
515
|
+
color: var(--color-text);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* Layout */
|
|
519
|
+
.main {
|
|
520
|
+
max-width: 800px;
|
|
521
|
+
margin: 0 auto;
|
|
522
|
+
padding: 2rem;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/* Typography */
|
|
526
|
+
.prose h1, .prose h2, .prose h3 { ... }
|
|
527
|
+
.prose p { ... }
|
|
528
|
+
.prose a { ... }
|
|
529
|
+
.prose code { ... }
|
|
530
|
+
.prose pre { ... }
|
|
531
|
+
|
|
532
|
+
/* Components */
|
|
533
|
+
.post-card { ... }
|
|
534
|
+
.tag { ... }
|
|
535
|
+
.pagination { ... }
|
|
536
|
+
|
|
537
|
+
/* Header and Footer */
|
|
538
|
+
.site-header { ... }
|
|
539
|
+
.site-footer { ... }
|
|
540
|
+
|
|
541
|
+
/* Responsive */
|
|
542
|
+
@media (max-width: 768px) {
|
|
543
|
+
.main { padding: 1rem; }
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Important CSS Classes
|
|
548
|
+
|
|
549
|
+
Style these classes for full theme support:
|
|
550
|
+
|
|
551
|
+
| Class | Used For |
|
|
552
|
+
|-------|----------|
|
|
553
|
+
| `.prose` | Content container (markdown output) |
|
|
554
|
+
| `.post-card` | Post preview in listings |
|
|
555
|
+
| `.note-card` | Note preview in listings |
|
|
556
|
+
| `.tag` | Tag links |
|
|
557
|
+
| `.draft-badge` | Draft indicator |
|
|
558
|
+
| `.pagination` | Pagination navigation |
|
|
559
|
+
| `.site-header` | Site header |
|
|
560
|
+
| `.site-footer` | Site footer |
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Shared Includes
|
|
565
|
+
|
|
566
|
+
Sia provides shared includes available to all themes:
|
|
567
|
+
|
|
568
|
+
### meta.njk
|
|
569
|
+
|
|
570
|
+
SEO meta tags (Open Graph, Twitter Cards):
|
|
571
|
+
|
|
572
|
+
```nunjucks
|
|
573
|
+
{% include "meta.njk" %}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
This automatically generates:
|
|
577
|
+
- Basic meta tags (charset, viewport, description)
|
|
578
|
+
- Open Graph tags for social sharing
|
|
579
|
+
- Twitter Card tags
|
|
580
|
+
- Article metadata for blog posts
|
|
581
|
+
- RSS feed link
|
|
582
|
+
- Canonical URL
|
|
583
|
+
|
|
584
|
+
### theme-script.njk
|
|
585
|
+
|
|
586
|
+
Prevents flash of wrong theme on page load:
|
|
587
|
+
|
|
588
|
+
```nunjucks
|
|
589
|
+
{% include "theme-script.njk" %}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Include this in `<head>` before stylesheets to prevent a flash of light mode when the user prefers dark mode.
|
|
593
|
+
|
|
594
|
+
---
|
|
595
|
+
|
|
596
|
+
## Dark Mode Support
|
|
597
|
+
|
|
598
|
+
### HTML Structure
|
|
599
|
+
|
|
600
|
+
Use the `data-theme` attribute on `<html>`:
|
|
601
|
+
|
|
602
|
+
```html
|
|
603
|
+
<html data-theme="light">
|
|
604
|
+
<!-- or -->
|
|
605
|
+
<html data-theme="dark">
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### CSS Variables
|
|
609
|
+
|
|
610
|
+
Define colors for both themes:
|
|
611
|
+
|
|
612
|
+
```css
|
|
613
|
+
:root {
|
|
614
|
+
--color-bg: #ffffff;
|
|
615
|
+
--color-text: #1a1a1a;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
[data-theme="dark"] {
|
|
619
|
+
--color-bg: #1a1a1a;
|
|
620
|
+
--color-text: #f0f0f0;
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Theme Toggle Script
|
|
625
|
+
|
|
626
|
+
Add a toggle button in your header:
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
const toggle = document.getElementById('theme-toggle');
|
|
630
|
+
const html = document.documentElement;
|
|
631
|
+
|
|
632
|
+
function getPreferredTheme() {
|
|
633
|
+
const saved = localStorage.getItem('theme');
|
|
634
|
+
if (saved) return saved;
|
|
635
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function setTheme(theme) {
|
|
639
|
+
html.setAttribute('data-theme', theme);
|
|
640
|
+
localStorage.setItem('theme', theme);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Initialize
|
|
644
|
+
setTheme(getPreferredTheme());
|
|
645
|
+
|
|
646
|
+
// Toggle handler
|
|
647
|
+
toggle.addEventListener('click', () => {
|
|
648
|
+
const current = html.getAttribute('data-theme');
|
|
649
|
+
setTheme(current === 'dark' ? 'light' : 'dark');
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## External Theme Packages
|
|
656
|
+
|
|
657
|
+
Sia supports distributing themes as npm packages, making it easy to share themes with the community.
|
|
658
|
+
|
|
659
|
+
### How Theme Resolution Works
|
|
660
|
+
|
|
661
|
+
When Sia loads a theme, it follows this resolution order:
|
|
662
|
+
|
|
663
|
+
1. **Built-in themes** - First checks the `themes/` folder in the Sia package
|
|
664
|
+
2. **npm packages** - If not found, looks for `sia-theme-{name}` in your `package.json` dependencies
|
|
665
|
+
3. **Fallback** - Falls back to the "main" theme if nothing is found
|
|
666
|
+
|
|
667
|
+
### Using an External Theme
|
|
668
|
+
|
|
669
|
+
To use an external theme in your Sia site:
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
# Install the theme package
|
|
673
|
+
npm install sia-theme-awesome
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Then configure it in your `_config.yml`:
|
|
677
|
+
|
|
678
|
+
```yaml
|
|
679
|
+
theme:
|
|
680
|
+
name: awesome # Sia will look for sia-theme-awesome
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
That's it! Sia automatically detects and uses the theme from `node_modules`.
|
|
684
|
+
|
|
685
|
+
### Theme Package Requirements
|
|
686
|
+
|
|
687
|
+
External theme packages must:
|
|
688
|
+
|
|
689
|
+
1. **Follow naming convention** - Package name must be `sia-theme-{name}`
|
|
690
|
+
2. **Export theme directory** - Include an `index.js` that exports the theme path
|
|
691
|
+
3. **Include required files** - Have `layouts/` and `pages/` directories (minimum)
|
|
692
|
+
4. **Follow theme structure** - Match the same structure as built-in themes
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Creating a Theme Package
|
|
697
|
+
|
|
698
|
+
### Using the Theme Generator
|
|
699
|
+
|
|
700
|
+
The easiest way to create a new theme is with the built-in generator:
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
sia theme my-awesome-theme
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
This creates a complete theme package with all necessary files:
|
|
707
|
+
|
|
708
|
+
```
|
|
709
|
+
sia-theme-my-awesome-theme/
|
|
710
|
+
├── package.json # npm package configuration
|
|
711
|
+
├── index.js # Exports theme directory path
|
|
712
|
+
├── README.md # Theme documentation
|
|
713
|
+
├── layouts/
|
|
714
|
+
│ ├── base.njk # Base HTML template
|
|
715
|
+
│ ├── post.njk # Blog post layout
|
|
716
|
+
│ ├── page.njk # Static page layout
|
|
717
|
+
│ └── note.njk # Note layout
|
|
718
|
+
├── includes/
|
|
719
|
+
│ ├── header.njk # Site header/navigation
|
|
720
|
+
│ ├── footer.njk # Site footer
|
|
721
|
+
│ ├── hero.njk # Homepage hero section
|
|
722
|
+
│ ├── pagination.njk # Pagination component
|
|
723
|
+
│ └── tag-list.njk # Tag cloud component
|
|
724
|
+
├── pages/
|
|
725
|
+
│ ├── index.njk # Homepage
|
|
726
|
+
│ ├── blog.njk # Blog listing
|
|
727
|
+
│ ├── notes.njk # Notes listing
|
|
728
|
+
│ ├── tags.njk # All tags page
|
|
729
|
+
│ ├── tag.njk # Single tag page
|
|
730
|
+
│ └── feed.njk # RSS feed
|
|
731
|
+
└── styles/
|
|
732
|
+
└── main.css # Theme styles
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Generator Options
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
# Interactive mode (prompts for details)
|
|
739
|
+
sia theme my-theme
|
|
740
|
+
|
|
741
|
+
# Quick mode (skip prompts, use defaults)
|
|
742
|
+
sia theme my-theme --quick
|
|
743
|
+
sia theme my-theme -q
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### package.json Structure
|
|
747
|
+
|
|
748
|
+
The generated `package.json` includes:
|
|
749
|
+
|
|
750
|
+
```json
|
|
751
|
+
{
|
|
752
|
+
"name": "sia-theme-my-theme",
|
|
753
|
+
"version": "1.0.0",
|
|
754
|
+
"description": "My Theme theme for Sia static site generator",
|
|
755
|
+
"main": "index.js",
|
|
756
|
+
"type": "module",
|
|
757
|
+
"keywords": [
|
|
758
|
+
"sia",
|
|
759
|
+
"sia-theme",
|
|
760
|
+
"static-site",
|
|
761
|
+
"theme"
|
|
762
|
+
],
|
|
763
|
+
"author": "Your Name",
|
|
764
|
+
"license": "MIT",
|
|
765
|
+
"peerDependencies": {
|
|
766
|
+
"@terrymooreii/sia": ">=2.0.0"
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### index.js Structure
|
|
772
|
+
|
|
773
|
+
The `index.js` exports the theme directory for Sia to locate:
|
|
774
|
+
|
|
775
|
+
```javascript
|
|
776
|
+
import { fileURLToPath } from 'url';
|
|
777
|
+
import { dirname } from 'path';
|
|
778
|
+
|
|
779
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
780
|
+
const __dirname = dirname(__filename);
|
|
781
|
+
|
|
782
|
+
// Export the theme directory path for Sia to use
|
|
783
|
+
export const themeDir = __dirname;
|
|
784
|
+
export default themeDir;
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Manual Theme Creation
|
|
788
|
+
|
|
789
|
+
If you prefer to create a theme manually:
|
|
790
|
+
|
|
791
|
+
1. Create a new directory: `mkdir sia-theme-my-theme`
|
|
792
|
+
2. Initialize npm: `npm init`
|
|
793
|
+
3. Set the package name to `sia-theme-{name}`
|
|
794
|
+
4. Create the required directory structure
|
|
795
|
+
5. Add an `index.js` that exports the directory path
|
|
796
|
+
6. Add all required template files
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Publishing Your Theme
|
|
801
|
+
|
|
802
|
+
### Preparing for Publication
|
|
803
|
+
|
|
804
|
+
1. **Test locally** - Link your theme and test with a Sia site:
|
|
805
|
+
|
|
806
|
+
```bash
|
|
807
|
+
# In your theme directory
|
|
808
|
+
npm link
|
|
809
|
+
|
|
810
|
+
# In a Sia site directory
|
|
811
|
+
npm link sia-theme-my-theme
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
2. **Update package.json** - Add repository, bugs, and homepage URLs:
|
|
815
|
+
|
|
816
|
+
```json
|
|
817
|
+
{
|
|
818
|
+
"repository": {
|
|
819
|
+
"type": "git",
|
|
820
|
+
"url": "https://github.com/username/sia-theme-my-theme.git"
|
|
821
|
+
},
|
|
822
|
+
"bugs": {
|
|
823
|
+
"url": "https://github.com/username/sia-theme-my-theme/issues"
|
|
824
|
+
},
|
|
825
|
+
"homepage": "https://github.com/username/sia-theme-my-theme#readme"
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
3. **Write documentation** - Update the README with:
|
|
830
|
+
- Screenshots of the theme
|
|
831
|
+
- Installation instructions
|
|
832
|
+
- Configuration options
|
|
833
|
+
- Customization tips
|
|
834
|
+
|
|
835
|
+
### Publishing to npm
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
# Login to npm (if not already)
|
|
839
|
+
npm login
|
|
840
|
+
|
|
841
|
+
# Publish the package
|
|
842
|
+
npm publish
|
|
843
|
+
|
|
844
|
+
# Or publish with public access if scoped
|
|
845
|
+
npm publish --access public
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Versioning
|
|
849
|
+
|
|
850
|
+
Follow semantic versioning:
|
|
851
|
+
|
|
852
|
+
- **Patch** (1.0.1) - Bug fixes, minor style tweaks
|
|
853
|
+
- **Minor** (1.1.0) - New features, backward-compatible changes
|
|
854
|
+
- **Major** (2.0.0) - Breaking changes to templates or configuration
|
|
855
|
+
|
|
856
|
+
### Theme Discovery
|
|
857
|
+
|
|
858
|
+
To help users find your theme:
|
|
859
|
+
|
|
860
|
+
1. Use `sia-theme` in your npm keywords
|
|
861
|
+
2. Add a clear description
|
|
862
|
+
3. Include screenshots in your README
|
|
863
|
+
4. Consider creating a demo site
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
## Customizing Existing Themes
|
|
868
|
+
|
|
869
|
+
You don't need to create a full theme to customize your site.
|
|
870
|
+
|
|
871
|
+
### Override Layouts
|
|
872
|
+
|
|
873
|
+
Create `_layouts/post.njk` to override the post layout:
|
|
874
|
+
|
|
875
|
+
```nunjucks
|
|
876
|
+
{% extends "base.njk" %}
|
|
877
|
+
|
|
878
|
+
{% block content %}
|
|
879
|
+
<!-- Your custom post layout -->
|
|
880
|
+
{% endblock %}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### Override Includes
|
|
884
|
+
|
|
885
|
+
Create `_includes/header.njk` to override the header:
|
|
886
|
+
|
|
887
|
+
```nunjucks
|
|
888
|
+
<header class="my-custom-header">
|
|
889
|
+
<!-- Your custom header -->
|
|
890
|
+
</header>
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
### Custom Styles
|
|
894
|
+
|
|
895
|
+
Create `styles/main.css` to use your own styles instead of the theme's:
|
|
896
|
+
|
|
897
|
+
```css
|
|
898
|
+
/* Your custom styles */
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
### Priority Order
|
|
902
|
+
|
|
903
|
+
Sia loads templates in this order (first found wins):
|
|
904
|
+
|
|
905
|
+
1. `_layouts/` - Your custom layouts
|
|
906
|
+
2. `_includes/` - Your custom includes
|
|
907
|
+
3. Theme layouts (`themes/[theme]/layouts/`)
|
|
908
|
+
4. Theme includes (`themes/[theme]/includes/`)
|
|
909
|
+
5. Theme pages (`themes/[theme]/pages/`)
|
|
910
|
+
6. Shared includes (`themes/_shared/includes/`)
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## Built-in Themes
|
|
915
|
+
|
|
916
|
+
### main
|
|
917
|
+
|
|
918
|
+
The default full-featured theme with:
|
|
919
|
+
- Clean, modern design
|
|
920
|
+
- Responsive layout
|
|
921
|
+
- Dark mode support
|
|
922
|
+
- Full tag cloud
|
|
923
|
+
- Reading time estimates
|
|
924
|
+
|
|
925
|
+
### minimal
|
|
926
|
+
|
|
927
|
+
A simple, content-focused theme with:
|
|
928
|
+
- Minimal styling
|
|
929
|
+
- Fast loading
|
|
930
|
+
- Clean typography
|
|
931
|
+
- Dark mode support
|
|
932
|
+
|
|
933
|
+
### developer
|
|
934
|
+
|
|
935
|
+
A theme with sidebar navigation:
|
|
936
|
+
- Sidebar with recent posts and tags
|
|
937
|
+
- Code-focused styling
|
|
938
|
+
- Dark mode optimized for code
|
|
939
|
+
- Technical aesthetic
|
|
940
|
+
|
|
941
|
+
### magazine
|
|
942
|
+
|
|
943
|
+
A publication-style theme with:
|
|
944
|
+
- Grid-based layout
|
|
945
|
+
- Featured post support
|
|
946
|
+
- Image-forward design
|
|
947
|
+
- Dark mode support
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
## Best Practices
|
|
952
|
+
|
|
953
|
+
### Accessibility
|
|
954
|
+
|
|
955
|
+
- Use semantic HTML (`<article>`, `<nav>`, `<main>`, etc.)
|
|
956
|
+
- Include ARIA labels for interactive elements
|
|
957
|
+
- Ensure sufficient color contrast
|
|
958
|
+
- Support keyboard navigation
|
|
959
|
+
|
|
960
|
+
### Performance
|
|
961
|
+
|
|
962
|
+
- Minimize CSS file size
|
|
963
|
+
- Use system fonts when possible
|
|
964
|
+
- Lazy load images where appropriate
|
|
965
|
+
- Test on slow connections
|
|
966
|
+
|
|
967
|
+
### SEO
|
|
968
|
+
|
|
969
|
+
- Always include `meta.njk` for proper meta tags
|
|
970
|
+
- Use proper heading hierarchy (h1 → h2 → h3)
|
|
971
|
+
- Include alt text for images
|
|
972
|
+
- Use canonical URLs
|
|
973
|
+
|
|
974
|
+
### Responsive Design
|
|
975
|
+
|
|
976
|
+
- Test on multiple screen sizes
|
|
977
|
+
- Use relative units (rem, em, %)
|
|
978
|
+
- Make navigation mobile-friendly
|
|
979
|
+
- Consider touch targets on mobile
|
|
980
|
+
|
|
981
|
+
### Template Tips
|
|
982
|
+
|
|
983
|
+
1. **Always use the `url` filter** for paths to support basePath hosting
|
|
984
|
+
2. **Use `safe` filter** for HTML content
|
|
985
|
+
3. **Check for empty collections** before iterating
|
|
986
|
+
4. **Handle missing data** gracefully with conditionals
|
|
987
|
+
5. **Keep templates DRY** with includes and macros
|