@terrymooreii/sia 2.1.2 → 2.1.4
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/assets.js +17 -20
- package/lib/build.js +2 -2
- package/lib/config.js +1 -1
- package/lib/content.js +24 -21
- package/lib/templates.js +10 -5
- package/package.json +1 -1
- package/themes/_shared/includes/meta.njk +41 -0
- package/themes/_shared/includes/theme-script.njk +8 -0
- package/themes/developer/includes/footer.njk +5 -0
- package/themes/developer/includes/header.njk +46 -0
- package/themes/developer/includes/pagination.njk +39 -0
- package/themes/developer/includes/sidebar.njk +60 -0
- package/themes/developer/includes/tag-list.njk +7 -0
- package/themes/developer/layouts/base.njk +29 -0
- package/themes/developer/layouts/note.njk +21 -0
- package/themes/developer/layouts/page.njk +13 -0
- package/themes/developer/layouts/post.njk +47 -0
- package/themes/developer/pages/blog.njk +41 -0
- package/themes/developer/pages/feed.njk +22 -0
- package/themes/developer/pages/index.njk +65 -0
- package/themes/developer/pages/notes.njk +38 -0
- package/themes/developer/pages/tag.njk +39 -0
- package/themes/developer/pages/tags.njk +21 -0
- package/themes/developer/styles/main.css +874 -0
- package/themes/magazine/includes/footer.njk +32 -0
- package/themes/magazine/includes/header.njk +63 -0
- package/themes/magazine/includes/pagination.njk +29 -0
- package/themes/magazine/includes/tag-list.njk +7 -0
- package/themes/magazine/layouts/base.njk +23 -0
- package/themes/magazine/layouts/note.njk +25 -0
- package/themes/magazine/layouts/page.njk +13 -0
- package/themes/magazine/layouts/post.njk +79 -0
- package/themes/magazine/pages/blog.njk +35 -0
- package/themes/magazine/pages/feed.njk +22 -0
- package/themes/magazine/pages/index.njk +79 -0
- package/themes/magazine/pages/notes.njk +33 -0
- package/themes/magazine/pages/tag.njk +31 -0
- package/themes/magazine/pages/tags.njk +19 -0
- package/themes/magazine/styles/main.css +1169 -0
- package/themes/main/layouts/base.njk +21 -0
- package/themes/minimal/includes/footer.njk +14 -0
- package/themes/minimal/includes/header.njk +71 -0
- package/themes/minimal/includes/pagination.njk +26 -0
- package/themes/minimal/includes/tag-list.njk +11 -0
- package/themes/minimal/layouts/base.njk +21 -0
- package/themes/minimal/layouts/note.njk +25 -0
- package/themes/minimal/layouts/page.njk +14 -0
- package/themes/minimal/layouts/post.njk +46 -0
- package/themes/minimal/pages/blog.njk +37 -0
- package/themes/minimal/pages/feed.njk +28 -0
- package/themes/minimal/pages/index.njk +61 -0
- package/themes/minimal/pages/notes.njk +34 -0
- package/themes/minimal/pages/tag.njk +42 -0
- package/themes/minimal/pages/tags.njk +39 -0
- package/defaults/layouts/base.njk +0 -41
- /package/{defaults → themes/main}/includes/footer.njk +0 -0
- /package/{defaults → themes/main}/includes/header.njk +0 -0
- /package/{defaults → themes/main}/includes/pagination.njk +0 -0
- /package/{defaults → themes/main}/includes/tag-list.njk +0 -0
- /package/{defaults → themes/main}/layouts/note.njk +0 -0
- /package/{defaults → themes/main}/layouts/page.njk +0 -0
- /package/{defaults → themes/main}/layouts/post.njk +0 -0
- /package/{defaults → themes/main}/pages/blog.njk +0 -0
- /package/{defaults → themes/main}/pages/feed.njk +0 -0
- /package/{defaults → themes/main}/pages/index.njk +0 -0
- /package/{defaults → themes/main}/pages/notes.njk +0 -0
- /package/{defaults → themes/main}/pages/tag.njk +0 -0
- /package/{defaults → themes/main}/pages/tags.njk +0 -0
- /package/{defaults → themes/main}/styles/main.css +0 -0
- /package/{defaults/styles/minimal.css → themes/minimal/styles/main.css} +0 -0
package/lib/assets.js
CHANGED
|
@@ -127,8 +127,7 @@ function hasCssFiles(dir) {
|
|
|
127
127
|
/**
|
|
128
128
|
* Copy default styles to output
|
|
129
129
|
*/
|
|
130
|
-
export function copyDefaultStyles(config,
|
|
131
|
-
const defaultStylesDir = join(defaultsDir, 'styles');
|
|
130
|
+
export function copyDefaultStyles(config, themesDir) {
|
|
132
131
|
const outputStylesDir = join(config.outputDir, 'styles');
|
|
133
132
|
|
|
134
133
|
// Check if user has custom styles (must actually have CSS files)
|
|
@@ -141,24 +140,22 @@ export function copyDefaultStyles(config, defaultsDir) {
|
|
|
141
140
|
return copied;
|
|
142
141
|
}
|
|
143
142
|
|
|
144
|
-
// Copy
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return copied;
|
|
161
|
-
}
|
|
143
|
+
// Copy styles from the selected theme
|
|
144
|
+
const themeName = config.theme || 'main';
|
|
145
|
+
const themeStylesDir = join(themesDir, themeName, 'styles');
|
|
146
|
+
|
|
147
|
+
if (existsSync(themeStylesDir)) {
|
|
148
|
+
const copied = copyAssets(themeStylesDir, outputStylesDir);
|
|
149
|
+
console.log(`🎨 Using "${themeName}" theme`);
|
|
150
|
+
return copied;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Fallback to main theme if selected theme not found
|
|
154
|
+
const fallbackStylesDir = join(themesDir, 'main', 'styles');
|
|
155
|
+
if (existsSync(fallbackStylesDir)) {
|
|
156
|
+
console.log(`⚠️ Theme "${themeName}" not found, using "main" theme`);
|
|
157
|
+
const copied = copyAssets(fallbackStylesDir, outputStylesDir);
|
|
158
|
+
return copied;
|
|
162
159
|
}
|
|
163
160
|
|
|
164
161
|
console.log('⚠️ No styles found');
|
package/lib/build.js
CHANGED
|
@@ -8,7 +8,7 @@ import { copyImages, copyDefaultStyles, copyStaticAssets, writeFile, ensureDir }
|
|
|
8
8
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = dirname(__filename);
|
|
11
|
-
const
|
|
11
|
+
const themesDir = join(__dirname, '..', 'themes');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Clean the output directory
|
|
@@ -244,7 +244,7 @@ export async function build(options = {}) {
|
|
|
244
244
|
|
|
245
245
|
// Copy assets
|
|
246
246
|
copyImages(config);
|
|
247
|
-
copyDefaultStyles(config,
|
|
247
|
+
copyDefaultStyles(config, themesDir);
|
|
248
248
|
copyStaticAssets(config);
|
|
249
249
|
|
|
250
250
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
package/lib/config.js
CHANGED
|
@@ -10,7 +10,7 @@ const defaultConfig = {
|
|
|
10
10
|
url: 'http://localhost:3000',
|
|
11
11
|
author: 'Anonymous'
|
|
12
12
|
},
|
|
13
|
-
theme: 'main', // Theme
|
|
13
|
+
theme: 'main', // Theme to use: 'main' or 'minimal'
|
|
14
14
|
input: 'src',
|
|
15
15
|
output: 'dist',
|
|
16
16
|
layouts: '_layouts',
|
package/lib/content.js
CHANGED
|
@@ -221,28 +221,31 @@ function embedGiphyGifs(html) {
|
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// Override the link renderer to handle YouTube URLs
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
224
|
+
// Override the link renderer to handle YouTube and Giphy URLs
|
|
225
|
+
marked.use({
|
|
226
|
+
renderer: {
|
|
227
|
+
link(href, s, text) {
|
|
228
|
+
const videoId = extractYouTubeId(href);
|
|
229
|
+
|
|
230
|
+
if (videoId) {
|
|
231
|
+
// Return responsive YouTube embed instead of a link
|
|
232
|
+
return `<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/${videoId}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const gifId = extractGiphyId(href);
|
|
236
|
+
|
|
237
|
+
if (gifId) {
|
|
238
|
+
// Return responsive Giphy embed instead of a link
|
|
239
|
+
return `<div class="giphy-embed"><iframe src="https://giphy.com/embed/${gifId}" frameBorder="0" class="giphy-embed" allowFullScreen></iframe></div>`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Use default link rendering for non-YouTube/Giphy links
|
|
243
|
+
text = text || href;
|
|
244
|
+
const title = text ? ` title="${text}"` : '';
|
|
245
|
+
return `<a href="${href}"${title}>${text}</a>`;
|
|
246
|
+
}
|
|
241
247
|
}
|
|
242
|
-
|
|
243
|
-
// Use default link rendering for non-YouTube/Giphy links
|
|
244
|
-
return originalLink(token);
|
|
245
|
-
};
|
|
248
|
+
});
|
|
246
249
|
|
|
247
250
|
/**
|
|
248
251
|
* Generate a URL-friendly slug from a string
|
package/lib/templates.js
CHANGED
|
@@ -204,11 +204,16 @@ export function createTemplateEngine(config) {
|
|
|
204
204
|
templatePaths.push(config.includesDir);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Default templates from the
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
templatePaths.push(join(
|
|
211
|
-
templatePaths.push(join(
|
|
207
|
+
// Default templates from the selected theme
|
|
208
|
+
const themeName = config.theme || 'main';
|
|
209
|
+
const themeDir = join(__dirname, '..', 'themes', themeName);
|
|
210
|
+
templatePaths.push(join(themeDir, 'layouts'));
|
|
211
|
+
templatePaths.push(join(themeDir, 'includes'));
|
|
212
|
+
templatePaths.push(join(themeDir, 'pages'));
|
|
213
|
+
|
|
214
|
+
// Shared includes available to all themes
|
|
215
|
+
const sharedIncludesDir = join(__dirname, '..', 'themes', '_shared', 'includes');
|
|
216
|
+
templatePaths.push(sharedIncludesDir);
|
|
212
217
|
|
|
213
218
|
// Create the environment
|
|
214
219
|
const env = nunjucks.configure(templatePaths, {
|
package/package.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{# Core meta tags - include this in your theme's <head> #}
|
|
2
|
+
<meta charset="UTF-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
+
<meta name="description" content="{{ page.excerpt or page.description or site.description }}">
|
|
5
|
+
<meta name="author" content="{{ page.author or site.author }}">
|
|
6
|
+
<title>{% if title %}{{ title }} | {% endif %}{{ site.title }}</title>
|
|
7
|
+
|
|
8
|
+
{# Open Graph #}
|
|
9
|
+
<meta property="og:title" content="{{ title or site.title }}">
|
|
10
|
+
<meta property="og:description" content="{{ page.excerpt or page.description or site.description }}">
|
|
11
|
+
<meta property="og:type" content="{% if page.collection == 'posts' %}article{% else %}website{% endif %}">
|
|
12
|
+
<meta property="og:url" content="{{ site.url }}{{ page.url or ('/' | url) }}">
|
|
13
|
+
<meta property="og:site_name" content="{{ site.title }}">
|
|
14
|
+
{% if page.image %}
|
|
15
|
+
<meta property="og:image" content="{{ site.url }}{{ page.image | url }}">
|
|
16
|
+
{% endif %}
|
|
17
|
+
|
|
18
|
+
{# Twitter Card #}
|
|
19
|
+
<meta name="twitter:card" content="{% if page.image %}summary_large_image{% else %}summary{% endif %}">
|
|
20
|
+
<meta name="twitter:title" content="{{ title or site.title }}">
|
|
21
|
+
<meta name="twitter:description" content="{{ page.excerpt or page.description or site.description }}">
|
|
22
|
+
{% if page.image %}
|
|
23
|
+
<meta name="twitter:image" content="{{ site.url }}{{ page.image | url }}">
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
{# Article specific meta (for blog posts) #}
|
|
27
|
+
{% if page.collection == 'posts' %}
|
|
28
|
+
<meta property="article:published_time" content="{{ page.date | date('iso') }}">
|
|
29
|
+
<meta property="article:author" content="{{ page.author or site.author }}">
|
|
30
|
+
{% if page.tags %}
|
|
31
|
+
{% for tag in page.tags %}
|
|
32
|
+
<meta property="article:tag" content="{{ tag }}">
|
|
33
|
+
{% endfor %}
|
|
34
|
+
{% endif %}
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
{# RSS Feed #}
|
|
38
|
+
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ '/feed.xml' | url }}">
|
|
39
|
+
|
|
40
|
+
{# Canonical URL #}
|
|
41
|
+
<link rel="canonical" href="{{ site.url }}{{ page.url or ('/' | url) }}">
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{# Prevent flash of incorrect theme - include in <head> before stylesheets #}
|
|
2
|
+
<script>
|
|
3
|
+
(function() {
|
|
4
|
+
var saved = localStorage.getItem('theme');
|
|
5
|
+
var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
6
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
7
|
+
})();
|
|
8
|
+
</script>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<header class="top-header">
|
|
2
|
+
<button class="mobile-menu-btn" aria-label="Toggle menu">
|
|
3
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
4
|
+
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
5
|
+
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
6
|
+
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
7
|
+
</svg>
|
|
8
|
+
</button>
|
|
9
|
+
|
|
10
|
+
<div class="header-spacer"></div>
|
|
11
|
+
|
|
12
|
+
<div class="header-actions">
|
|
13
|
+
<button class="theme-toggle" aria-label="Toggle dark mode">
|
|
14
|
+
<svg class="sun-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15
|
+
<circle cx="12" cy="12" r="5"></circle>
|
|
16
|
+
<line x1="12" y1="1" x2="12" y2="3"></line>
|
|
17
|
+
<line x1="12" y1="21" x2="12" y2="23"></line>
|
|
18
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
|
19
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
|
20
|
+
<line x1="1" y1="12" x2="3" y2="12"></line>
|
|
21
|
+
<line x1="21" y1="12" x2="23" y2="12"></line>
|
|
22
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
|
23
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
|
24
|
+
</svg>
|
|
25
|
+
<svg class="moon-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
26
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
|
27
|
+
</svg>
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
|
|
32
|
+
<script>
|
|
33
|
+
// Theme toggle
|
|
34
|
+
document.querySelector('.theme-toggle').addEventListener('click', function() {
|
|
35
|
+
const html = document.documentElement;
|
|
36
|
+
const current = html.getAttribute('data-theme');
|
|
37
|
+
const next = current === 'dark' ? 'light' : 'dark';
|
|
38
|
+
html.setAttribute('data-theme', next);
|
|
39
|
+
localStorage.setItem('theme', next);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Mobile menu
|
|
43
|
+
document.querySelector('.mobile-menu-btn').addEventListener('click', function() {
|
|
44
|
+
document.querySelector('.sidebar').classList.toggle('open');
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% if pagination.totalPages > 1 %}
|
|
2
|
+
<nav class="pagination" aria-label="Pagination">
|
|
3
|
+
{% if pagination.previousUrl %}
|
|
4
|
+
<a href="{{ pagination.previousUrl }}" class="pagination-btn pagination-prev">
|
|
5
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
6
|
+
<polyline points="15 18 9 12 15 6"></polyline>
|
|
7
|
+
</svg>
|
|
8
|
+
<span>Previous</span>
|
|
9
|
+
</a>
|
|
10
|
+
{% else %}
|
|
11
|
+
<span class="pagination-btn pagination-prev disabled">
|
|
12
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
13
|
+
<polyline points="15 18 9 12 15 6"></polyline>
|
|
14
|
+
</svg>
|
|
15
|
+
<span>Previous</span>
|
|
16
|
+
</span>
|
|
17
|
+
{% endif %}
|
|
18
|
+
|
|
19
|
+
<span class="pagination-info">
|
|
20
|
+
Page {{ pagination.currentPage }} of {{ pagination.totalPages }}
|
|
21
|
+
</span>
|
|
22
|
+
|
|
23
|
+
{% if pagination.nextUrl %}
|
|
24
|
+
<a href="{{ pagination.nextUrl }}" class="pagination-btn pagination-next">
|
|
25
|
+
<span>Next</span>
|
|
26
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
27
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
28
|
+
</svg>
|
|
29
|
+
</a>
|
|
30
|
+
{% else %}
|
|
31
|
+
<span class="pagination-btn pagination-next disabled">
|
|
32
|
+
<span>Next</span>
|
|
33
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
34
|
+
<polyline points="9 18 15 12 9 6"></polyline>
|
|
35
|
+
</svg>
|
|
36
|
+
</span>
|
|
37
|
+
{% endif %}
|
|
38
|
+
</nav>
|
|
39
|
+
{% endif %}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<aside class="sidebar">
|
|
2
|
+
<div class="sidebar-header">
|
|
3
|
+
<a href="{{ '/' | url }}" class="sidebar-logo">
|
|
4
|
+
<span class="logo-icon">{{ site.title[0] }}</span>
|
|
5
|
+
<span class="logo-text">{{ site.title }}</span>
|
|
6
|
+
</a>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<nav class="sidebar-nav">
|
|
10
|
+
<a href="{{ '/' | url }}" class="nav-item {% if page.url == '/' %}active{% endif %}">
|
|
11
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
12
|
+
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
13
|
+
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
14
|
+
</svg>
|
|
15
|
+
<span>Home</span>
|
|
16
|
+
</a>
|
|
17
|
+
<a href="{{ '/blog/' | url }}" class="nav-item {% if page.url and '/blog' in page.url %}active{% endif %}">
|
|
18
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
19
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
20
|
+
<polyline points="14 2 14 8 20 8"></polyline>
|
|
21
|
+
<line x1="16" y1="13" x2="8" y2="13"></line>
|
|
22
|
+
<line x1="16" y1="17" x2="8" y2="17"></line>
|
|
23
|
+
</svg>
|
|
24
|
+
<span>Posts</span>
|
|
25
|
+
</a>
|
|
26
|
+
<a href="{{ '/notes/' | url }}" class="nav-item {% if page.url and '/notes' in page.url %}active{% endif %}">
|
|
27
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
28
|
+
<line x1="18" y1="2" x2="22" y2="6"></line>
|
|
29
|
+
<path d="M7.5 20.5L19 9l-4-4L3.5 16.5 2 22z"></path>
|
|
30
|
+
</svg>
|
|
31
|
+
<span>Notes</span>
|
|
32
|
+
</a>
|
|
33
|
+
<a href="{{ '/tags/' | url }}" class="nav-item {% if page.url and '/tags' in page.url %}active{% endif %}">
|
|
34
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
35
|
+
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
|
|
36
|
+
<line x1="7" y1="7" x2="7.01" y2="7"></line>
|
|
37
|
+
</svg>
|
|
38
|
+
<span>Tags</span>
|
|
39
|
+
</a>
|
|
40
|
+
<a href="{{ '/about/' | url }}" class="nav-item {% if page.url and '/about' in page.url %}active{% endif %}">
|
|
41
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
42
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
43
|
+
<line x1="12" y1="16" x2="12" y2="12"></line>
|
|
44
|
+
<line x1="12" y1="8" x2="12.01" y2="8"></line>
|
|
45
|
+
</svg>
|
|
46
|
+
<span>About</span>
|
|
47
|
+
</a>
|
|
48
|
+
</nav>
|
|
49
|
+
|
|
50
|
+
<div class="sidebar-footer">
|
|
51
|
+
<a href="{{ '/feed.xml' | url }}" class="nav-item">
|
|
52
|
+
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
53
|
+
<path d="M4 11a9 9 0 0 1 9 9"></path>
|
|
54
|
+
<path d="M4 4a16 16 0 0 1 16 16"></path>
|
|
55
|
+
<circle cx="5" cy="19" r="1"></circle>
|
|
56
|
+
</svg>
|
|
57
|
+
<span>RSS Feed</span>
|
|
58
|
+
</a>
|
|
59
|
+
</div>
|
|
60
|
+
</aside>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
{% include "meta.njk" %}
|
|
5
|
+
{% include "theme-script.njk" %}
|
|
6
|
+
|
|
7
|
+
<link rel="stylesheet" href="{{ '/styles/main.css' | url }}">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
11
|
+
|
|
12
|
+
{% block head %}{% endblock %}
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div class="app-layout">
|
|
16
|
+
{% include "sidebar.njk" %}
|
|
17
|
+
|
|
18
|
+
<div class="main-wrapper">
|
|
19
|
+
{% include "header.njk" %}
|
|
20
|
+
|
|
21
|
+
<main class="main-content">
|
|
22
|
+
{% block content %}{% endblock %}
|
|
23
|
+
</main>
|
|
24
|
+
|
|
25
|
+
{% include "footer.njk" %}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<article class="note-article">
|
|
5
|
+
<header class="note-header">
|
|
6
|
+
<time class="note-date" datetime="{{ page.date | date('iso') }}">
|
|
7
|
+
{{ page.date | date('full') }}
|
|
8
|
+
</time>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<div class="note-content prose">
|
|
12
|
+
{{ content | safe }}
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
{% if page.tags %}
|
|
16
|
+
<footer class="note-footer">
|
|
17
|
+
{% include "tag-list.njk" %}
|
|
18
|
+
</footer>
|
|
19
|
+
{% endif %}
|
|
20
|
+
</article>
|
|
21
|
+
{% endblock %}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<article class="page-article">
|
|
5
|
+
<header class="page-header">
|
|
6
|
+
<h1 class="page-title">{{ page.title }}</h1>
|
|
7
|
+
</header>
|
|
8
|
+
|
|
9
|
+
<div class="page-content prose">
|
|
10
|
+
{{ content | safe }}
|
|
11
|
+
</div>
|
|
12
|
+
</article>
|
|
13
|
+
{% endblock %}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<article class="post-article">
|
|
5
|
+
<header class="post-header">
|
|
6
|
+
{% if page.tags %}
|
|
7
|
+
<div class="post-tags-top">
|
|
8
|
+
{% for tag in page.tags | limit(2) %}
|
|
9
|
+
<a href="{{ ('/tags/' + (tag | slug) + '/') | url }}" class="tag-badge">{{ tag }}</a>
|
|
10
|
+
{% endfor %}
|
|
11
|
+
</div>
|
|
12
|
+
{% endif %}
|
|
13
|
+
|
|
14
|
+
<h1 class="post-title">{{ page.title }}</h1>
|
|
15
|
+
|
|
16
|
+
<div class="post-meta">
|
|
17
|
+
<div class="author-info">
|
|
18
|
+
<div class="author-avatar">{{ site.author[0] }}</div>
|
|
19
|
+
<div class="author-details">
|
|
20
|
+
<span class="author-name">{{ page.author or site.author }}</span>
|
|
21
|
+
<span class="post-date">{{ page.date | date('long') }} · {{ content | readingTime }}</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
{% if page.image %}
|
|
28
|
+
<figure class="post-hero">
|
|
29
|
+
<img src="{{ page.image | url }}" alt="{{ page.title }}">
|
|
30
|
+
</figure>
|
|
31
|
+
{% endif %}
|
|
32
|
+
|
|
33
|
+
<div class="post-content prose">
|
|
34
|
+
{{ content | safe }}
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{% if page.tags %}
|
|
38
|
+
<footer class="post-footer">
|
|
39
|
+
<div class="post-tags">
|
|
40
|
+
{% for tag in page.tags %}
|
|
41
|
+
<a href="{{ ('/tags/' + (tag | slug) + '/') | url }}" class="tag">#{{ tag }}</a>
|
|
42
|
+
{% endfor %}
|
|
43
|
+
</div>
|
|
44
|
+
</footer>
|
|
45
|
+
{% endif %}
|
|
46
|
+
</article>
|
|
47
|
+
{% endblock %}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="blog-page">
|
|
5
|
+
<header class="page-header">
|
|
6
|
+
<h1 class="page-title">Blog</h1>
|
|
7
|
+
<p class="page-description">Thoughts, tutorials, and insights</p>
|
|
8
|
+
</header>
|
|
9
|
+
|
|
10
|
+
<div class="post-cards">
|
|
11
|
+
{% for post in posts %}
|
|
12
|
+
<article class="post-card">
|
|
13
|
+
<div class="card-content">
|
|
14
|
+
{% if post.tags %}
|
|
15
|
+
<div class="card-tags">
|
|
16
|
+
{% for tag in post.tags | limit(2) %}
|
|
17
|
+
<span class="tag-small">#{{ tag }}</span>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</div>
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
<h2 class="card-title">
|
|
23
|
+
<a href="{{ post.url | url }}">{{ post.title }}</a>
|
|
24
|
+
</h2>
|
|
25
|
+
|
|
26
|
+
<p class="card-excerpt">{{ post.excerpt | excerpt(150) }}</p>
|
|
27
|
+
|
|
28
|
+
<div class="card-meta">
|
|
29
|
+
<span class="card-date">{{ post.date | date('short') }}</span>
|
|
30
|
+
<span class="card-reading-time">{{ post.content | readingTime }}</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</article>
|
|
34
|
+
{% else %}
|
|
35
|
+
<p class="no-posts">No posts yet.</p>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
{% include "pagination.njk" %}
|
|
40
|
+
</div>
|
|
41
|
+
{% endblock %}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
3
|
+
<channel>
|
|
4
|
+
<title>{{ site.title }}</title>
|
|
5
|
+
<description>{{ site.description }}</description>
|
|
6
|
+
<link>{{ site.url }}</link>
|
|
7
|
+
<atom:link href="{{ site.url }}/feed.xml" rel="self" type="application/rss+xml"/>
|
|
8
|
+
<lastBuildDate>{{ buildDate }}</lastBuildDate>
|
|
9
|
+
{% for post in posts | limit(20) %}
|
|
10
|
+
<item>
|
|
11
|
+
<title>{{ post.title }}</title>
|
|
12
|
+
<link>{{ site.url }}{{ post.url }}</link>
|
|
13
|
+
<guid isPermaLink="true">{{ site.url }}{{ post.url }}</guid>
|
|
14
|
+
<pubDate>{{ post.date | date('rss') }}</pubDate>
|
|
15
|
+
<description><![CDATA[{{ post.excerpt | excerpt(300) }}]]></description>
|
|
16
|
+
{% for tag in post.tags %}
|
|
17
|
+
<category>{{ tag }}</category>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</item>
|
|
20
|
+
{% endfor %}
|
|
21
|
+
</channel>
|
|
22
|
+
</rss>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{% extends "base.njk" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
<div class="home-page">
|
|
5
|
+
<section class="hero-section">
|
|
6
|
+
<h1 class="hero-title">{{ site.title }}</h1>
|
|
7
|
+
<p class="hero-description">{{ site.description }}</p>
|
|
8
|
+
</section>
|
|
9
|
+
|
|
10
|
+
<section class="posts-section">
|
|
11
|
+
<div class="section-header">
|
|
12
|
+
<h2 class="section-title">Latest Posts</h2>
|
|
13
|
+
<a href="{{ '/blog/' | url }}" class="view-all-link">View all posts →</a>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="post-cards">
|
|
17
|
+
{% for post in collections.posts | limit(6) %}
|
|
18
|
+
<article class="post-card">
|
|
19
|
+
<div class="card-content">
|
|
20
|
+
{% if post.tags %}
|
|
21
|
+
<div class="card-tags">
|
|
22
|
+
{% for tag in post.tags | limit(2) %}
|
|
23
|
+
<span class="tag-small">#{{ tag }}</span>
|
|
24
|
+
{% endfor %}
|
|
25
|
+
</div>
|
|
26
|
+
{% endif %}
|
|
27
|
+
|
|
28
|
+
<h3 class="card-title">
|
|
29
|
+
<a href="{{ post.url | url }}">{{ post.title }}</a>
|
|
30
|
+
</h3>
|
|
31
|
+
|
|
32
|
+
<p class="card-excerpt">{{ post.excerpt | excerpt(120) }}</p>
|
|
33
|
+
|
|
34
|
+
<div class="card-meta">
|
|
35
|
+
<span class="card-date">{{ post.date | date('short') }}</span>
|
|
36
|
+
<span class="card-reading-time">{{ post.content | readingTime }}</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</article>
|
|
40
|
+
{% else %}
|
|
41
|
+
<p class="no-posts">No posts yet. Create your first post!</p>
|
|
42
|
+
{% endfor %}
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
{% if collections.notes.length %}
|
|
47
|
+
<section class="notes-section">
|
|
48
|
+
<div class="section-header">
|
|
49
|
+
<h2 class="section-title">Recent Notes</h2>
|
|
50
|
+
<a href="{{ '/notes/' | url }}" class="view-all-link">View all notes →</a>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="notes-list">
|
|
54
|
+
{% for note in collections.notes | limit(3) %}
|
|
55
|
+
<article class="note-card">
|
|
56
|
+
<time class="note-time">{{ note.date | date('short') }}</time>
|
|
57
|
+
<div class="note-preview">{{ note.excerpt | excerpt(150) }}</div>
|
|
58
|
+
<a href="{{ note.url | url }}" class="note-link">Read more →</a>
|
|
59
|
+
</article>
|
|
60
|
+
{% endfor %}
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
{% endif %}
|
|
64
|
+
</div>
|
|
65
|
+
{% endblock %}
|