@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.
@@ -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>&copy; {{ '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