@terrymooreii/sia 2.1.4 → 2.1.6
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/docs/README.md +132 -0
- package/docs/creating-themes.md +772 -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 +1 -1
- package/lib/build.js +4 -4
- package/lib/config.js +3 -1
- package/lib/content.js +74 -2
- package/lib/templates.js +3 -2
- package/package.json +1 -1
- package/readme.md +2 -1
- package/themes/_shared/includes/meta.njk +5 -4
- package/themes/developer/includes/hero.njk +6 -0
- package/themes/developer/pages/blog.njk +1 -1
- package/themes/developer/pages/index.njk +3 -6
- package/themes/developer/pages/notes.njk +1 -1
- package/themes/developer/pages/tag.njk +1 -1
- package/themes/magazine/includes/hero.njk +8 -0
- package/themes/magazine/layouts/post.njk +1 -1
- package/themes/magazine/pages/blog.njk +1 -1
- package/themes/magazine/pages/index.njk +1 -6
- package/themes/magazine/pages/notes.njk +1 -1
- package/themes/magazine/pages/tag.njk +1 -1
- package/themes/main/includes/hero.njk +6 -0
- package/themes/main/pages/index.njk +1 -4
- package/themes/minimal/includes/hero.njk +6 -0
- package/themes/minimal/pages/index.njk +2 -5
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
# Creating Themes
|
|
2
|
+
|
|
3
|
+
This guide explains how to create custom themes for Sia and how to 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
|
+
- [Customizing Existing Themes](#customizing-existing-themes)
|
|
17
|
+
- [Built-in Themes](#built-in-themes)
|
|
18
|
+
- [Best Practices](#best-practices)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Theme Overview
|
|
23
|
+
|
|
24
|
+
Sia themes control the visual appearance and structure of your site. A theme consists of:
|
|
25
|
+
|
|
26
|
+
- **Layouts** - Base templates that wrap content (post, page, note layouts)
|
|
27
|
+
- **Pages** - Templates for listing pages (homepage, blog, tags)
|
|
28
|
+
- **Includes** - Reusable components (header, footer, pagination)
|
|
29
|
+
- **Styles** - CSS files for styling
|
|
30
|
+
|
|
31
|
+
### Selecting a Theme
|
|
32
|
+
|
|
33
|
+
Set your theme in `_config.yml`:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
theme:
|
|
37
|
+
name: minimal # Options: main, minimal, developer, magazine
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The theme configuration is an object that allows for additional theme-specific options in the future.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Theme Structure
|
|
45
|
+
|
|
46
|
+
A complete theme follows this directory structure:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
themes/your-theme/
|
|
50
|
+
├── layouts/
|
|
51
|
+
│ ├── base.njk # Base HTML structure
|
|
52
|
+
│ ├── post.njk # Blog post layout
|
|
53
|
+
│ ├── page.njk # Static page layout
|
|
54
|
+
│ └── note.njk # Note layout
|
|
55
|
+
├── includes/
|
|
56
|
+
│ ├── header.njk # Site header/navigation
|
|
57
|
+
│ ├── footer.njk # Site footer
|
|
58
|
+
│ ├── hero.njk # Homepage hero section
|
|
59
|
+
│ ├── pagination.njk # Pagination component
|
|
60
|
+
│ └── tag-list.njk # Tag cloud/list component
|
|
61
|
+
├── pages/
|
|
62
|
+
│ ├── index.njk # Homepage
|
|
63
|
+
│ ├── blog.njk # Blog listing
|
|
64
|
+
│ ├── notes.njk # Notes listing
|
|
65
|
+
│ ├── tags.njk # All tags page
|
|
66
|
+
│ ├── tag.njk # Individual tag page
|
|
67
|
+
│ └── feed.njk # RSS feed template
|
|
68
|
+
└── styles/
|
|
69
|
+
└── main.css # Theme styles
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Required Files
|
|
75
|
+
|
|
76
|
+
### Layouts (Required)
|
|
77
|
+
|
|
78
|
+
| File | Purpose |
|
|
79
|
+
|------|---------|
|
|
80
|
+
| `base.njk` | Base HTML structure, includes head and body |
|
|
81
|
+
| `post.njk` | Template for blog posts |
|
|
82
|
+
| `page.njk` | Template for static pages |
|
|
83
|
+
| `note.njk` | Template for notes |
|
|
84
|
+
|
|
85
|
+
### Pages (Required)
|
|
86
|
+
|
|
87
|
+
| File | Purpose |
|
|
88
|
+
|------|---------|
|
|
89
|
+
| `index.njk` | Homepage |
|
|
90
|
+
| `blog.njk` | Blog listing with pagination |
|
|
91
|
+
| `notes.njk` | Notes listing with pagination |
|
|
92
|
+
| `tags.njk` | All tags overview page |
|
|
93
|
+
| `tag.njk` | Individual tag page with pagination |
|
|
94
|
+
| `feed.njk` | RSS feed (XML) |
|
|
95
|
+
|
|
96
|
+
### Includes (Recommended)
|
|
97
|
+
|
|
98
|
+
| File | Purpose |
|
|
99
|
+
|------|---------|
|
|
100
|
+
| `header.njk` | Site header and navigation |
|
|
101
|
+
| `footer.njk` | Site footer |
|
|
102
|
+
| `hero.njk` | Homepage hero section |
|
|
103
|
+
| `pagination.njk` | Pagination navigation |
|
|
104
|
+
| `tag-list.njk` | Tag cloud or list |
|
|
105
|
+
|
|
106
|
+
### Styles (Required)
|
|
107
|
+
|
|
108
|
+
| File | Purpose |
|
|
109
|
+
|------|---------|
|
|
110
|
+
| `main.css` | All theme styles |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Layouts
|
|
115
|
+
|
|
116
|
+
### base.njk
|
|
117
|
+
|
|
118
|
+
The base layout provides the HTML structure for all pages:
|
|
119
|
+
|
|
120
|
+
```nunjucks
|
|
121
|
+
<!DOCTYPE html>
|
|
122
|
+
<html lang="en">
|
|
123
|
+
<head>
|
|
124
|
+
{% include "meta.njk" %}
|
|
125
|
+
{% include "theme-script.njk" %}
|
|
126
|
+
|
|
127
|
+
<link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
|
|
128
|
+
|
|
129
|
+
{% block head %}{% endblock %}
|
|
130
|
+
</head>
|
|
131
|
+
<body>
|
|
132
|
+
{% include "header.njk" %}
|
|
133
|
+
|
|
134
|
+
<main class="main">
|
|
135
|
+
{% block content %}{% endblock %}
|
|
136
|
+
</main>
|
|
137
|
+
|
|
138
|
+
{% include "footer.njk" %}
|
|
139
|
+
</body>
|
|
140
|
+
</html>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Key points:**
|
|
144
|
+
- Include `meta.njk` for SEO meta tags
|
|
145
|
+
- Include `theme-script.njk` to prevent dark mode flash
|
|
146
|
+
- Use `{% block content %}` for page-specific content
|
|
147
|
+
- Use the `url` filter for all paths
|
|
148
|
+
|
|
149
|
+
### post.njk
|
|
150
|
+
|
|
151
|
+
Template for blog posts:
|
|
152
|
+
|
|
153
|
+
```nunjucks
|
|
154
|
+
{% extends "base.njk" %}
|
|
155
|
+
|
|
156
|
+
{% block content %}
|
|
157
|
+
<article class="post">
|
|
158
|
+
<header class="post-header">
|
|
159
|
+
<h1 class="post-title">
|
|
160
|
+
{{ page.title }}
|
|
161
|
+
{% if page.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
162
|
+
</h1>
|
|
163
|
+
|
|
164
|
+
<div class="post-meta">
|
|
165
|
+
<time datetime="{{ page.date | date('iso') }}">
|
|
166
|
+
{{ page.date | date('long') }}
|
|
167
|
+
</time>
|
|
168
|
+
|
|
169
|
+
{% if page.tags and page.tags.length %}
|
|
170
|
+
<span class="post-tags">
|
|
171
|
+
{% for tag in page.tags %}
|
|
172
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
|
|
173
|
+
{% endfor %}
|
|
174
|
+
</span>
|
|
175
|
+
{% endif %}
|
|
176
|
+
|
|
177
|
+
<span class="reading-time">{{ page.content | readingTime }}</span>
|
|
178
|
+
</div>
|
|
179
|
+
</header>
|
|
180
|
+
|
|
181
|
+
<div class="post-content prose">
|
|
182
|
+
{{ content | safe }}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<footer class="post-footer">
|
|
186
|
+
<a href="{{ '/blog/' | url }}">← Back to Blog</a>
|
|
187
|
+
</footer>
|
|
188
|
+
</article>
|
|
189
|
+
{% endblock %}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### page.njk
|
|
193
|
+
|
|
194
|
+
Template for static pages:
|
|
195
|
+
|
|
196
|
+
```nunjucks
|
|
197
|
+
{% extends "base.njk" %}
|
|
198
|
+
|
|
199
|
+
{% block content %}
|
|
200
|
+
<article class="page">
|
|
201
|
+
<header class="page-header">
|
|
202
|
+
<h1 class="page-title">{{ page.title }}</h1>
|
|
203
|
+
</header>
|
|
204
|
+
|
|
205
|
+
<div class="page-content prose">
|
|
206
|
+
{{ content | safe }}
|
|
207
|
+
</div>
|
|
208
|
+
</article>
|
|
209
|
+
{% endblock %}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### note.njk
|
|
213
|
+
|
|
214
|
+
Template for notes:
|
|
215
|
+
|
|
216
|
+
```nunjucks
|
|
217
|
+
{% extends "base.njk" %}
|
|
218
|
+
|
|
219
|
+
{% block content %}
|
|
220
|
+
<article class="note">
|
|
221
|
+
<div class="note-content prose">
|
|
222
|
+
{{ content | safe }}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<footer class="note-footer">
|
|
226
|
+
<time datetime="{{ page.date | date('iso') }}">
|
|
227
|
+
{{ page.date | date('full_time') }}
|
|
228
|
+
</time>
|
|
229
|
+
|
|
230
|
+
{% if page.tags and page.tags.length %}
|
|
231
|
+
<span class="note-tags">
|
|
232
|
+
{% for tag in page.tags %}
|
|
233
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/" class="tag">{{ tag }}</a>
|
|
234
|
+
{% endfor %}
|
|
235
|
+
</span>
|
|
236
|
+
{% endif %}
|
|
237
|
+
</footer>
|
|
238
|
+
</article>
|
|
239
|
+
|
|
240
|
+
<nav class="note-nav">
|
|
241
|
+
<a href="{{ '/notes/' | url }}">← All Notes</a>
|
|
242
|
+
</nav>
|
|
243
|
+
{% endblock %}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Pages
|
|
249
|
+
|
|
250
|
+
### index.njk (Homepage)
|
|
251
|
+
|
|
252
|
+
```nunjucks
|
|
253
|
+
{% extends "base.njk" %}
|
|
254
|
+
|
|
255
|
+
{% block content %}
|
|
256
|
+
{% include "hero.njk" %}
|
|
257
|
+
|
|
258
|
+
<section class="recent-posts">
|
|
259
|
+
<h2>Latest Posts</h2>
|
|
260
|
+
|
|
261
|
+
{% for post in collections.posts | limit(5) %}
|
|
262
|
+
<article class="post-card">
|
|
263
|
+
<h3><a href="{{ post.url }}">{{ post.title }}</a></h3>
|
|
264
|
+
<time>{{ post.date | date('short') }}</time>
|
|
265
|
+
<p>{{ post.excerpt }}</p>
|
|
266
|
+
</article>
|
|
267
|
+
{% else %}
|
|
268
|
+
<p>No posts yet.</p>
|
|
269
|
+
{% endfor %}
|
|
270
|
+
|
|
271
|
+
<a href="{{ '/blog/' | url }}">View all posts →</a>
|
|
272
|
+
</section>
|
|
273
|
+
|
|
274
|
+
{% if collections.notes and collections.notes.length %}
|
|
275
|
+
<section class="recent-notes">
|
|
276
|
+
<h2>Recent Notes</h2>
|
|
277
|
+
|
|
278
|
+
{% for note in collections.notes | limit(3) %}
|
|
279
|
+
<article class="note-card">
|
|
280
|
+
<div>{{ note.excerptHtml | safe }}</div>
|
|
281
|
+
<time>{{ note.date | date('full_time') }}</time>
|
|
282
|
+
</article>
|
|
283
|
+
{% endfor %}
|
|
284
|
+
</section>
|
|
285
|
+
{% endif %}
|
|
286
|
+
{% endblock %}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### blog.njk (Blog Listing)
|
|
290
|
+
|
|
291
|
+
```nunjucks
|
|
292
|
+
{% extends "base.njk" %}
|
|
293
|
+
|
|
294
|
+
{% block content %}
|
|
295
|
+
<header class="page-header">
|
|
296
|
+
<h1>Blog</h1>
|
|
297
|
+
<p>All posts, newest first</p>
|
|
298
|
+
</header>
|
|
299
|
+
|
|
300
|
+
<div class="post-list">
|
|
301
|
+
{% for post in posts %}
|
|
302
|
+
<article class="post-card">
|
|
303
|
+
<h2>
|
|
304
|
+
<a href="{{ post.url }}">{{ post.title }}</a>
|
|
305
|
+
{% if post.draft %}<span class="draft-badge">Draft</span>{% endif %}
|
|
306
|
+
</h2>
|
|
307
|
+
<div class="post-meta">
|
|
308
|
+
<time>{{ post.date | date('long') }}</time>
|
|
309
|
+
<span>{{ post.content | readingTime }}</span>
|
|
310
|
+
</div>
|
|
311
|
+
{% if post.tags and post.tags.length %}
|
|
312
|
+
<div class="tags">
|
|
313
|
+
{% for tag in post.tags %}
|
|
314
|
+
<a href="{{ '/tags/' | url }}{{ tag | slug }}/">{{ tag }}</a>
|
|
315
|
+
{% endfor %}
|
|
316
|
+
</div>
|
|
317
|
+
{% endif %}
|
|
318
|
+
<p>{{ post.excerpt }}</p>
|
|
319
|
+
</article>
|
|
320
|
+
{% else %}
|
|
321
|
+
<p>No posts yet.</p>
|
|
322
|
+
{% endfor %}
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
{% include "pagination.njk" %}
|
|
326
|
+
{% endblock %}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### tag.njk (Individual Tag Page)
|
|
330
|
+
|
|
331
|
+
```nunjucks
|
|
332
|
+
{% extends "base.njk" %}
|
|
333
|
+
|
|
334
|
+
{% block content %}
|
|
335
|
+
<header class="page-header">
|
|
336
|
+
<h1>Tagged: {{ tag.name }}</h1>
|
|
337
|
+
<p>{{ tag.count }} item{% if tag.count != 1 %}s{% endif %}</p>
|
|
338
|
+
</header>
|
|
339
|
+
|
|
340
|
+
<div class="post-list">
|
|
341
|
+
{% for post in posts %}
|
|
342
|
+
<article class="post-card">
|
|
343
|
+
<h2><a href="{{ post.url }}">{{ post.title or post.excerpt }}</a></h2>
|
|
344
|
+
<time>{{ post.date | date('long') }}</time>
|
|
345
|
+
</article>
|
|
346
|
+
{% endfor %}
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
{% include "pagination.njk" %}
|
|
350
|
+
|
|
351
|
+
<a href="{{ '/tags/' | url }}">← All Tags</a>
|
|
352
|
+
{% endblock %}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### feed.njk (RSS Feed)
|
|
356
|
+
|
|
357
|
+
```nunjucks
|
|
358
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
359
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
360
|
+
<channel>
|
|
361
|
+
<title>{{ site.title | safe }}</title>
|
|
362
|
+
<description>{{ site.description | safe }}</description>
|
|
363
|
+
<link>{{ site.url | safe }}</link>
|
|
364
|
+
<atom:link href="{{ site.url | safe }}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
365
|
+
<language>en-us</language>
|
|
366
|
+
<lastBuildDate>{{ buildDate | safe }}</lastBuildDate>
|
|
367
|
+
<generator>Sia Static Site Generator</generator>
|
|
368
|
+
{% for post in posts | limit(20) %}
|
|
369
|
+
<item>
|
|
370
|
+
<title><![CDATA[{{ post.title | safe }}]]></title>
|
|
371
|
+
<link>{{ site.url | safe }}{{ post.url | safe }}</link>
|
|
372
|
+
<guid isPermaLink="true">{{ site.url | safe }}{{ post.url | safe }}</guid>
|
|
373
|
+
<pubDate>{{ post.date | date('rss') }}</pubDate>
|
|
374
|
+
{% for tag in post.tags %}
|
|
375
|
+
<category>{{ tag | safe }}</category>
|
|
376
|
+
{% endfor %}
|
|
377
|
+
<description><![CDATA[{{ post.excerpt | safe }}]]></description>
|
|
378
|
+
<content:encoded><![CDATA[{{ post.content | safe }}]]></content:encoded>
|
|
379
|
+
</item>
|
|
380
|
+
{% endfor %}
|
|
381
|
+
</channel>
|
|
382
|
+
</rss>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Includes
|
|
388
|
+
|
|
389
|
+
### header.njk
|
|
390
|
+
|
|
391
|
+
```nunjucks
|
|
392
|
+
<header class="site-header">
|
|
393
|
+
<a href="{{ '/' | url }}" class="site-logo">{{ site.title }}</a>
|
|
394
|
+
|
|
395
|
+
<nav class="site-nav">
|
|
396
|
+
<a href="{{ '/' | url }}">Home</a>
|
|
397
|
+
<a href="{{ '/blog/' | url }}">Blog</a>
|
|
398
|
+
<a href="{{ '/notes/' | url }}">Notes</a>
|
|
399
|
+
<a href="{{ '/tags/' | url }}">Tags</a>
|
|
400
|
+
|
|
401
|
+
{% for p in collections.pages | limit(3) %}
|
|
402
|
+
<a href="{{ p.url }}">{{ p.title }}</a>
|
|
403
|
+
{% endfor %}
|
|
404
|
+
|
|
405
|
+
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode">
|
|
406
|
+
<!-- Sun/Moon icons -->
|
|
407
|
+
</button>
|
|
408
|
+
</nav>
|
|
409
|
+
</header>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### footer.njk
|
|
413
|
+
|
|
414
|
+
```nunjucks
|
|
415
|
+
<footer class="site-footer">
|
|
416
|
+
<p>© {{ 'now' | date('year') }} {{ site.title }}</p>
|
|
417
|
+
<p>Built with <a href="https://github.com/terrymooreii/sia">Sia</a></p>
|
|
418
|
+
</footer>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### pagination.njk
|
|
422
|
+
|
|
423
|
+
```nunjucks
|
|
424
|
+
{% if pagination and pagination.totalPages > 1 %}
|
|
425
|
+
<nav class="pagination">
|
|
426
|
+
<span>Page {{ pagination.pageNumber }} of {{ pagination.totalPages }}</span>
|
|
427
|
+
|
|
428
|
+
{% if pagination.previousUrl %}
|
|
429
|
+
<a href="{{ pagination.previousUrl }}">← Newer</a>
|
|
430
|
+
{% else %}
|
|
431
|
+
<span class="disabled">← Newer</span>
|
|
432
|
+
{% endif %}
|
|
433
|
+
|
|
434
|
+
{% if pagination.nextUrl %}
|
|
435
|
+
<a href="{{ pagination.nextUrl }}">Older →</a>
|
|
436
|
+
{% else %}
|
|
437
|
+
<span class="disabled">Older →</span>
|
|
438
|
+
{% endif %}
|
|
439
|
+
</nav>
|
|
440
|
+
{% endif %}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### tag-list.njk
|
|
444
|
+
|
|
445
|
+
```nunjucks
|
|
446
|
+
{% if allTags and allTags.length %}
|
|
447
|
+
<div class="tag-list">
|
|
448
|
+
{% for tag in allTags %}
|
|
449
|
+
<a href="{{ '/tags/' | url }}{{ tag.slug }}/" class="tag">
|
|
450
|
+
{{ tag.name }} ({{ tag.count }})
|
|
451
|
+
</a>
|
|
452
|
+
{% endfor %}
|
|
453
|
+
</div>
|
|
454
|
+
{% endif %}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### hero.njk
|
|
458
|
+
|
|
459
|
+
The hero section is displayed on the homepage when `config.theme.showHero` is enabled:
|
|
460
|
+
|
|
461
|
+
```nunjucks
|
|
462
|
+
{% if config.theme.showHero %}
|
|
463
|
+
<section class="hero">
|
|
464
|
+
<h1 class="hero-title">{{ site.title }}</h1>
|
|
465
|
+
<p class="hero-description">{{ site.description }}</p>
|
|
466
|
+
</section>
|
|
467
|
+
{% endif %}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Enable the hero section in your `_config.yml`:
|
|
471
|
+
|
|
472
|
+
```yaml
|
|
473
|
+
theme:
|
|
474
|
+
name: main
|
|
475
|
+
showHero: true
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## Styles
|
|
481
|
+
|
|
482
|
+
### CSS Structure
|
|
483
|
+
|
|
484
|
+
A typical `main.css` structure:
|
|
485
|
+
|
|
486
|
+
```css
|
|
487
|
+
/* Reset and base styles */
|
|
488
|
+
*, *::before, *::after {
|
|
489
|
+
box-sizing: border-box;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* CSS Variables for theming */
|
|
493
|
+
:root {
|
|
494
|
+
--color-bg: #ffffff;
|
|
495
|
+
--color-text: #1a1a1a;
|
|
496
|
+
--color-primary: #0066cc;
|
|
497
|
+
--color-muted: #666666;
|
|
498
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
499
|
+
--font-mono: ui-monospace, monospace;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
[data-theme="dark"] {
|
|
503
|
+
--color-bg: #1a1a1a;
|
|
504
|
+
--color-text: #f0f0f0;
|
|
505
|
+
--color-primary: #66b3ff;
|
|
506
|
+
--color-muted: #999999;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
body {
|
|
510
|
+
font-family: var(--font-sans);
|
|
511
|
+
background: var(--color-bg);
|
|
512
|
+
color: var(--color-text);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* Layout */
|
|
516
|
+
.main {
|
|
517
|
+
max-width: 800px;
|
|
518
|
+
margin: 0 auto;
|
|
519
|
+
padding: 2rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* Typography */
|
|
523
|
+
.prose h1, .prose h2, .prose h3 { ... }
|
|
524
|
+
.prose p { ... }
|
|
525
|
+
.prose a { ... }
|
|
526
|
+
.prose code { ... }
|
|
527
|
+
.prose pre { ... }
|
|
528
|
+
|
|
529
|
+
/* Components */
|
|
530
|
+
.post-card { ... }
|
|
531
|
+
.tag { ... }
|
|
532
|
+
.pagination { ... }
|
|
533
|
+
|
|
534
|
+
/* Header and Footer */
|
|
535
|
+
.site-header { ... }
|
|
536
|
+
.site-footer { ... }
|
|
537
|
+
|
|
538
|
+
/* Responsive */
|
|
539
|
+
@media (max-width: 768px) {
|
|
540
|
+
.main { padding: 1rem; }
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Important CSS Classes
|
|
545
|
+
|
|
546
|
+
Style these classes for full theme support:
|
|
547
|
+
|
|
548
|
+
| Class | Used For |
|
|
549
|
+
|-------|----------|
|
|
550
|
+
| `.prose` | Content container (markdown output) |
|
|
551
|
+
| `.post-card` | Post preview in listings |
|
|
552
|
+
| `.note-card` | Note preview in listings |
|
|
553
|
+
| `.tag` | Tag links |
|
|
554
|
+
| `.draft-badge` | Draft indicator |
|
|
555
|
+
| `.pagination` | Pagination navigation |
|
|
556
|
+
| `.site-header` | Site header |
|
|
557
|
+
| `.site-footer` | Site footer |
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Shared Includes
|
|
562
|
+
|
|
563
|
+
Sia provides shared includes available to all themes:
|
|
564
|
+
|
|
565
|
+
### meta.njk
|
|
566
|
+
|
|
567
|
+
SEO meta tags (Open Graph, Twitter Cards):
|
|
568
|
+
|
|
569
|
+
```nunjucks
|
|
570
|
+
{% include "meta.njk" %}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
This automatically generates:
|
|
574
|
+
- Basic meta tags (charset, viewport, description)
|
|
575
|
+
- Open Graph tags for social sharing
|
|
576
|
+
- Twitter Card tags
|
|
577
|
+
- Article metadata for blog posts
|
|
578
|
+
- RSS feed link
|
|
579
|
+
- Canonical URL
|
|
580
|
+
|
|
581
|
+
### theme-script.njk
|
|
582
|
+
|
|
583
|
+
Prevents flash of wrong theme on page load:
|
|
584
|
+
|
|
585
|
+
```nunjucks
|
|
586
|
+
{% include "theme-script.njk" %}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Include this in `<head>` before stylesheets to prevent a flash of light mode when the user prefers dark mode.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Dark Mode Support
|
|
594
|
+
|
|
595
|
+
### HTML Structure
|
|
596
|
+
|
|
597
|
+
Use the `data-theme` attribute on `<html>`:
|
|
598
|
+
|
|
599
|
+
```html
|
|
600
|
+
<html data-theme="light">
|
|
601
|
+
<!-- or -->
|
|
602
|
+
<html data-theme="dark">
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### CSS Variables
|
|
606
|
+
|
|
607
|
+
Define colors for both themes:
|
|
608
|
+
|
|
609
|
+
```css
|
|
610
|
+
:root {
|
|
611
|
+
--color-bg: #ffffff;
|
|
612
|
+
--color-text: #1a1a1a;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
[data-theme="dark"] {
|
|
616
|
+
--color-bg: #1a1a1a;
|
|
617
|
+
--color-text: #f0f0f0;
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Theme Toggle Script
|
|
622
|
+
|
|
623
|
+
Add a toggle button in your header:
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
const toggle = document.getElementById('theme-toggle');
|
|
627
|
+
const html = document.documentElement;
|
|
628
|
+
|
|
629
|
+
function getPreferredTheme() {
|
|
630
|
+
const saved = localStorage.getItem('theme');
|
|
631
|
+
if (saved) return saved;
|
|
632
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function setTheme(theme) {
|
|
636
|
+
html.setAttribute('data-theme', theme);
|
|
637
|
+
localStorage.setItem('theme', theme);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Initialize
|
|
641
|
+
setTheme(getPreferredTheme());
|
|
642
|
+
|
|
643
|
+
// Toggle handler
|
|
644
|
+
toggle.addEventListener('click', () => {
|
|
645
|
+
const current = html.getAttribute('data-theme');
|
|
646
|
+
setTheme(current === 'dark' ? 'light' : 'dark');
|
|
647
|
+
});
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Customizing Existing Themes
|
|
653
|
+
|
|
654
|
+
You don't need to create a full theme to customize your site.
|
|
655
|
+
|
|
656
|
+
### Override Layouts
|
|
657
|
+
|
|
658
|
+
Create `_layouts/post.njk` to override the post layout:
|
|
659
|
+
|
|
660
|
+
```nunjucks
|
|
661
|
+
{% extends "base.njk" %}
|
|
662
|
+
|
|
663
|
+
{% block content %}
|
|
664
|
+
<!-- Your custom post layout -->
|
|
665
|
+
{% endblock %}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### Override Includes
|
|
669
|
+
|
|
670
|
+
Create `_includes/header.njk` to override the header:
|
|
671
|
+
|
|
672
|
+
```nunjucks
|
|
673
|
+
<header class="my-custom-header">
|
|
674
|
+
<!-- Your custom header -->
|
|
675
|
+
</header>
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
### Custom Styles
|
|
679
|
+
|
|
680
|
+
Create `styles/main.css` to use your own styles instead of the theme's:
|
|
681
|
+
|
|
682
|
+
```css
|
|
683
|
+
/* Your custom styles */
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Priority Order
|
|
687
|
+
|
|
688
|
+
Sia loads templates in this order (first found wins):
|
|
689
|
+
|
|
690
|
+
1. `_layouts/` - Your custom layouts
|
|
691
|
+
2. `_includes/` - Your custom includes
|
|
692
|
+
3. Theme layouts (`themes/[theme]/layouts/`)
|
|
693
|
+
4. Theme includes (`themes/[theme]/includes/`)
|
|
694
|
+
5. Theme pages (`themes/[theme]/pages/`)
|
|
695
|
+
6. Shared includes (`themes/_shared/includes/`)
|
|
696
|
+
|
|
697
|
+
---
|
|
698
|
+
|
|
699
|
+
## Built-in Themes
|
|
700
|
+
|
|
701
|
+
### main
|
|
702
|
+
|
|
703
|
+
The default full-featured theme with:
|
|
704
|
+
- Clean, modern design
|
|
705
|
+
- Responsive layout
|
|
706
|
+
- Dark mode support
|
|
707
|
+
- Full tag cloud
|
|
708
|
+
- Reading time estimates
|
|
709
|
+
|
|
710
|
+
### minimal
|
|
711
|
+
|
|
712
|
+
A simple, content-focused theme with:
|
|
713
|
+
- Minimal styling
|
|
714
|
+
- Fast loading
|
|
715
|
+
- Clean typography
|
|
716
|
+
- Dark mode support
|
|
717
|
+
|
|
718
|
+
### developer
|
|
719
|
+
|
|
720
|
+
A theme with sidebar navigation:
|
|
721
|
+
- Sidebar with recent posts and tags
|
|
722
|
+
- Code-focused styling
|
|
723
|
+
- Dark mode optimized for code
|
|
724
|
+
- Technical aesthetic
|
|
725
|
+
|
|
726
|
+
### magazine
|
|
727
|
+
|
|
728
|
+
A publication-style theme with:
|
|
729
|
+
- Grid-based layout
|
|
730
|
+
- Featured post support
|
|
731
|
+
- Image-forward design
|
|
732
|
+
- Dark mode support
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## Best Practices
|
|
737
|
+
|
|
738
|
+
### Accessibility
|
|
739
|
+
|
|
740
|
+
- Use semantic HTML (`<article>`, `<nav>`, `<main>`, etc.)
|
|
741
|
+
- Include ARIA labels for interactive elements
|
|
742
|
+
- Ensure sufficient color contrast
|
|
743
|
+
- Support keyboard navigation
|
|
744
|
+
|
|
745
|
+
### Performance
|
|
746
|
+
|
|
747
|
+
- Minimize CSS file size
|
|
748
|
+
- Use system fonts when possible
|
|
749
|
+
- Lazy load images where appropriate
|
|
750
|
+
- Test on slow connections
|
|
751
|
+
|
|
752
|
+
### SEO
|
|
753
|
+
|
|
754
|
+
- Always include `meta.njk` for proper meta tags
|
|
755
|
+
- Use proper heading hierarchy (h1 → h2 → h3)
|
|
756
|
+
- Include alt text for images
|
|
757
|
+
- Use canonical URLs
|
|
758
|
+
|
|
759
|
+
### Responsive Design
|
|
760
|
+
|
|
761
|
+
- Test on multiple screen sizes
|
|
762
|
+
- Use relative units (rem, em, %)
|
|
763
|
+
- Make navigation mobile-friendly
|
|
764
|
+
- Consider touch targets on mobile
|
|
765
|
+
|
|
766
|
+
### Template Tips
|
|
767
|
+
|
|
768
|
+
1. **Always use the `url` filter** for paths to support basePath hosting
|
|
769
|
+
2. **Use `safe` filter** for HTML content
|
|
770
|
+
3. **Check for empty collections** before iterating
|
|
771
|
+
4. **Handle missing data** gracefully with conditionals
|
|
772
|
+
5. **Keep templates DRY** with includes and macros
|