@edtools/cli 0.6.1 → 0.7.0

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.
Files changed (39) hide show
  1. package/dist/adapters/html/index.d.ts +2 -2
  2. package/dist/adapters/html/index.d.ts.map +1 -1
  3. package/dist/adapters/html/index.js +19 -2
  4. package/dist/adapters/html/index.js.map +1 -1
  5. package/dist/adapters/html/templates/base-layout.ejs +173 -0
  6. package/dist/adapters/html/templates/blog-index.html.ejs +170 -101
  7. package/dist/adapters/html/templates/blog-post-enhanced.ejs +655 -0
  8. package/dist/adapters/html/templates/blog-post-new.ejs +612 -0
  9. package/dist/adapters/html/templates/components/footer.ejs +330 -0
  10. package/dist/adapters/html/templates/components/header.ejs +208 -0
  11. package/dist/adapters/html/templates/components/social-share.ejs +202 -0
  12. package/dist/chunk-5N3D47CJ.js +823 -0
  13. package/dist/chunk-TROAGFSZ.js +824 -0
  14. package/dist/chunk-U77FH5BI.js +823 -0
  15. package/dist/cli/commands/config.d.ts +17 -0
  16. package/dist/cli/commands/config.d.ts.map +1 -0
  17. package/dist/cli/commands/config.js +140 -0
  18. package/dist/cli/commands/config.js.map +1 -0
  19. package/dist/cli/commands/generate.d.ts +1 -1
  20. package/dist/cli/commands/generate.d.ts.map +1 -1
  21. package/dist/cli/commands/generate.js +74 -17
  22. package/dist/cli/commands/generate.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +114 -0
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/index.js +567 -265
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/core/generator.d.ts +4 -1
  29. package/dist/core/generator.d.ts.map +1 -1
  30. package/dist/core/generator.js +114 -3
  31. package/dist/core/generator.js.map +1 -1
  32. package/dist/index.d.ts +87 -3
  33. package/dist/index.js +1 -1
  34. package/dist/types/adapter.d.ts +17 -2
  35. package/dist/types/adapter.d.ts.map +1 -1
  36. package/dist/types/adapter.js.map +1 -1
  37. package/dist/types/content.d.ts +66 -0
  38. package/dist/types/content.d.ts.map +1 -1
  39. package/package.json +2 -2
@@ -0,0 +1,655 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= blogConfig.language || 'es' %>">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+
7
+ <!-- SEO Meta Tags -->
8
+ <title><%= metadata.title %><% if (blogConfig.name && metadata.title !== blogConfig.name) { %> | <%= blogConfig.name %><% } %></title>
9
+ <meta name="description" content="<%= metadata.description %>">
10
+ <meta name="keywords" content="<%= metadata.keywords.join(', ') %>">
11
+ <meta name="author" content="<%= metadata.author %>">
12
+ <meta name="date" content="<%= metadata.datePublished %>">
13
+
14
+ <!-- Canonical URL -->
15
+ <link rel="canonical" href="<%= (blogConfig.websiteUrl || '') + metadata.url %>">
16
+
17
+ <!-- Open Graph / Social Media -->
18
+ <meta property="og:type" content="article">
19
+ <meta property="og:title" content="<%= metadata.title %>">
20
+ <meta property="og:description" content="<%= metadata.description %>">
21
+ <meta property="og:url" content="<%= (blogConfig.websiteUrl || '') + metadata.url %>">
22
+ <% if (blogConfig.websiteUrl) { %>
23
+ <meta property="og:site_name" content="<%= blogConfig.name %>">
24
+ <% } %>
25
+ <meta property="article:published_time" content="<%= metadata.datePublished %>">
26
+ <meta property="article:author" content="<%= metadata.author %>">
27
+ <meta property="article:section" content="<%= metadata.category %>">
28
+ <% metadata.keywords.forEach(keyword => { %>
29
+ <meta property="article:tag" content="<%= keyword %>">
30
+ <% }) %>
31
+
32
+ <!-- Twitter Card -->
33
+ <meta name="twitter:card" content="summary_large_image">
34
+ <meta name="twitter:title" content="<%= metadata.title %>">
35
+ <meta name="twitter:description" content="<%= metadata.description %>">
36
+ <% if (blogConfig.social && blogConfig.social.twitter) { %>
37
+ <meta name="twitter:site" content="<%= blogConfig.social.twitter.replace('https://twitter.com/', '@') %>">
38
+ <% } %>
39
+
40
+ <!-- RSS Feed -->
41
+ <link rel="alternate" type="application/rss+xml" title="<%= blogConfig.name %> RSS Feed" href="/blog/rss.xml">
42
+
43
+ <!-- Schema.org structured data -->
44
+ <script type="application/ld+json">
45
+ <%- schemaOrg %>
46
+ </script>
47
+
48
+ <!-- Breadcrumb Schema -->
49
+ <script type="application/ld+json">
50
+ {
51
+ "@context": "https://schema.org",
52
+ "@type": "BreadcrumbList",
53
+ "itemListElement": [
54
+ {
55
+ "@type": "ListItem",
56
+ "position": 1,
57
+ "name": "Home",
58
+ "item": "<%= blogConfig.websiteUrl || '/' %>"
59
+ },
60
+ {
61
+ "@type": "ListItem",
62
+ "position": 2,
63
+ "name": "Blog",
64
+ "item": "<%= (blogConfig.websiteUrl || '') + '/blog/' %>"
65
+ },
66
+ {
67
+ "@type": "ListItem",
68
+ "position": 3,
69
+ "name": "<%= metadata.title %>",
70
+ "item": "<%= (blogConfig.websiteUrl || '') + metadata.url %>"
71
+ }
72
+ ]
73
+ }
74
+ </script>
75
+
76
+ <style>
77
+ :root {
78
+ --primary-color: <%= blogConfig.primaryColor || '#2563eb' %>;
79
+ --text-color: <%= blogConfig.textColor || '#1f2937' %>;
80
+ --text-light: <%= blogConfig.textLight || '#6b7280' %>;
81
+ --border-color: <%= blogConfig.borderColor || '#e5e7eb' %>;
82
+ --bg-light: <%= blogConfig.bgLight || '#f9fafb' %>;
83
+ }
84
+
85
+ * {
86
+ margin: 0;
87
+ padding: 0;
88
+ box-sizing: border-box;
89
+ }
90
+
91
+ body {
92
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
93
+ line-height: 1.6;
94
+ color: var(--text-color);
95
+ background: white;
96
+ }
97
+
98
+ a {
99
+ color: var(--primary-color);
100
+ text-decoration: none;
101
+ }
102
+
103
+ a:hover {
104
+ text-decoration: underline;
105
+ }
106
+
107
+ .post-container {
108
+ max-width: 800px;
109
+ margin: 0 auto;
110
+ padding: 2rem 1rem;
111
+ min-height: calc(100vh - 200px);
112
+ }
113
+
114
+ .breadcrumbs {
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 0.5rem;
118
+ font-size: 0.875rem;
119
+ color: var(--text-light);
120
+ margin-bottom: 1.5rem;
121
+ }
122
+
123
+ .breadcrumbs a {
124
+ color: var(--primary-color);
125
+ }
126
+
127
+ .breadcrumbs span {
128
+ color: var(--text-light);
129
+ }
130
+
131
+ .post-header {
132
+ margin-bottom: 2rem;
133
+ }
134
+
135
+ h1 {
136
+ font-size: 2.5rem;
137
+ font-weight: 700;
138
+ line-height: 1.2;
139
+ margin-bottom: 1rem;
140
+ color: var(--text-color);
141
+ }
142
+
143
+ .post-meta {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 1rem;
147
+ flex-wrap: wrap;
148
+ font-size: 0.875rem;
149
+ color: var(--text-light);
150
+ margin-top: 1rem;
151
+ }
152
+
153
+ .post-meta-item {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 0.375rem;
157
+ }
158
+
159
+ .post-category {
160
+ display: inline-block;
161
+ padding: 0.25rem 0.75rem;
162
+ background: var(--primary-color);
163
+ color: white;
164
+ border-radius: 9999px;
165
+ font-size: 0.8125rem;
166
+ font-weight: 600;
167
+ text-decoration: none;
168
+ }
169
+
170
+ .post-category:hover {
171
+ opacity: 0.9;
172
+ text-decoration: none;
173
+ }
174
+
175
+ .post-intro {
176
+ font-size: 1.125rem;
177
+ color: var(--text-light);
178
+ margin-bottom: 2rem;
179
+ padding-bottom: 2rem;
180
+ border-bottom: 1px solid var(--border-color);
181
+ }
182
+
183
+ /* Table of Contents */
184
+ .table-of-contents {
185
+ background: var(--bg-light);
186
+ border: 1px solid var(--border-color);
187
+ border-radius: 0.5rem;
188
+ padding: 1.5rem;
189
+ margin-bottom: 2rem;
190
+ }
191
+
192
+ .toc-title {
193
+ font-size: 1rem;
194
+ font-weight: 600;
195
+ color: var(--text-color);
196
+ margin-bottom: 1rem;
197
+ }
198
+
199
+ .toc-list {
200
+ list-style: none;
201
+ padding: 0;
202
+ margin: 0;
203
+ }
204
+
205
+ .toc-item {
206
+ margin-bottom: 0.5rem;
207
+ }
208
+
209
+ .toc-item-level-2 { padding-left: 0; }
210
+ .toc-item-level-3 { padding-left: 1rem; }
211
+ .toc-item-level-4 { padding-left: 2rem; }
212
+
213
+ .toc-link {
214
+ color: var(--text-color);
215
+ text-decoration: none;
216
+ font-size: 0.9375rem;
217
+ transition: color 0.2s;
218
+ }
219
+
220
+ .toc-link:hover {
221
+ color: var(--primary-color);
222
+ text-decoration: none;
223
+ }
224
+
225
+ .post-content h2 {
226
+ font-size: 1.875rem;
227
+ font-weight: 600;
228
+ margin-top: 2.5rem;
229
+ margin-bottom: 1rem;
230
+ color: var(--text-color);
231
+ }
232
+
233
+ .post-content h3 {
234
+ font-size: 1.5rem;
235
+ font-weight: 600;
236
+ margin-top: 2rem;
237
+ margin-bottom: 0.75rem;
238
+ color: var(--text-color);
239
+ }
240
+
241
+ .post-content h4 {
242
+ font-size: 1.25rem;
243
+ font-weight: 600;
244
+ margin-top: 1.5rem;
245
+ margin-bottom: 0.5rem;
246
+ color: var(--text-color);
247
+ }
248
+
249
+ .post-content p {
250
+ margin-bottom: 1rem;
251
+ }
252
+
253
+ .post-content section {
254
+ margin-bottom: 2.5rem;
255
+ }
256
+
257
+ .comparison-table {
258
+ width: 100%;
259
+ border-collapse: collapse;
260
+ margin: 1.5rem 0;
261
+ font-size: 0.9375rem;
262
+ }
263
+
264
+ .comparison-table thead {
265
+ background: var(--bg-light);
266
+ }
267
+
268
+ .comparison-table th,
269
+ .comparison-table td {
270
+ padding: 0.75rem 1rem;
271
+ text-align: left;
272
+ border: 1px solid var(--border-color);
273
+ }
274
+
275
+ .comparison-table th {
276
+ font-weight: 600;
277
+ color: var(--text-color);
278
+ }
279
+
280
+ .comparison-table tbody tr:hover {
281
+ background: var(--bg-light);
282
+ }
283
+
284
+ .content-list {
285
+ margin: 1.5rem 0;
286
+ padding-left: 1.5rem;
287
+ }
288
+
289
+ .content-list li {
290
+ margin-bottom: 1rem;
291
+ }
292
+
293
+ .code-block {
294
+ margin: 1.5rem 0;
295
+ background: var(--bg-light);
296
+ border: 1px solid var(--border-color);
297
+ border-radius: 0.375rem;
298
+ overflow: hidden;
299
+ }
300
+
301
+ .code-caption {
302
+ padding: 0.5rem 1rem;
303
+ background: #e5e7eb;
304
+ font-size: 0.875rem;
305
+ font-weight: 600;
306
+ border-bottom: 1px solid var(--border-color);
307
+ }
308
+
309
+ .code-block pre {
310
+ padding: 1rem;
311
+ overflow-x: auto;
312
+ }
313
+
314
+ .code-block code {
315
+ font-family: 'Monaco', 'Courier New', monospace;
316
+ font-size: 0.875rem;
317
+ line-height: 1.5;
318
+ }
319
+
320
+ .post-conclusion {
321
+ margin-top: 3rem;
322
+ padding-top: 2rem;
323
+ border-top: 1px solid var(--border-color);
324
+ }
325
+
326
+ .post-tags {
327
+ display: flex;
328
+ flex-wrap: wrap;
329
+ gap: 0.5rem;
330
+ margin-top: 2rem;
331
+ }
332
+
333
+ .post-tag {
334
+ display: inline-block;
335
+ padding: 0.375rem 0.75rem;
336
+ background: var(--bg-light);
337
+ color: var(--text-color);
338
+ border: 1px solid var(--border-color);
339
+ border-radius: 0.25rem;
340
+ font-size: 0.875rem;
341
+ text-decoration: none;
342
+ transition: all 0.2s;
343
+ }
344
+
345
+ .post-tag:hover {
346
+ border-color: var(--primary-color);
347
+ color: var(--primary-color);
348
+ text-decoration: none;
349
+ }
350
+
351
+ .post-cta {
352
+ margin-top: 3rem;
353
+ padding: 2rem;
354
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
355
+ border-radius: 0.5rem;
356
+ text-align: center;
357
+ }
358
+
359
+ .post-cta a {
360
+ display: inline-block;
361
+ padding: 1rem 2rem;
362
+ background: white;
363
+ color: var(--primary-color);
364
+ font-weight: 600;
365
+ font-size: 1.125rem;
366
+ border-radius: 0.375rem;
367
+ transition: transform 0.2s;
368
+ text-decoration: none;
369
+ }
370
+
371
+ .post-cta a:hover {
372
+ transform: translateY(-2px);
373
+ text-decoration: none;
374
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
375
+ }
376
+
377
+ .related-posts {
378
+ margin-top: 4rem;
379
+ padding-top: 2rem;
380
+ border-top: 2px solid var(--border-color);
381
+ }
382
+
383
+ .related-posts h3 {
384
+ margin-top: 0;
385
+ margin-bottom: 1.5rem;
386
+ }
387
+
388
+ .related-posts-grid {
389
+ display: grid;
390
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
391
+ gap: 1.5rem;
392
+ }
393
+
394
+ .related-post-card {
395
+ border: 1px solid var(--border-color);
396
+ border-radius: 0.5rem;
397
+ padding: 1.5rem;
398
+ transition: box-shadow 0.2s;
399
+ }
400
+
401
+ .related-post-card:hover {
402
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
403
+ }
404
+
405
+ .related-post-title {
406
+ font-weight: 600;
407
+ color: var(--text-color);
408
+ margin-bottom: 0.5rem;
409
+ }
410
+
411
+ .related-post-title a {
412
+ color: var(--text-color);
413
+ text-decoration: none;
414
+ }
415
+
416
+ .related-post-title a:hover {
417
+ color: var(--primary-color);
418
+ }
419
+
420
+ .related-post-excerpt {
421
+ color: var(--text-light);
422
+ font-size: 0.875rem;
423
+ }
424
+
425
+ .post-navigation {
426
+ display: grid;
427
+ grid-template-columns: 1fr 1fr;
428
+ gap: 1.5rem;
429
+ margin-top: 3rem;
430
+ padding-top: 2rem;
431
+ border-top: 1px solid var(--border-color);
432
+ }
433
+
434
+ .post-nav-link {
435
+ display: block;
436
+ padding: 1.5rem;
437
+ border: 1px solid var(--border-color);
438
+ border-radius: 0.5rem;
439
+ text-decoration: none;
440
+ transition: all 0.2s;
441
+ }
442
+
443
+ .post-nav-link:hover {
444
+ border-color: var(--primary-color);
445
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
446
+ text-decoration: none;
447
+ }
448
+
449
+ .post-nav-link.prev {
450
+ text-align: left;
451
+ }
452
+
453
+ .post-nav-link.next {
454
+ text-align: right;
455
+ }
456
+
457
+ .post-nav-label {
458
+ font-size: 0.8125rem;
459
+ text-transform: uppercase;
460
+ font-weight: 600;
461
+ color: var(--text-light);
462
+ letter-spacing: 0.05em;
463
+ margin-bottom: 0.5rem;
464
+ }
465
+
466
+ .post-nav-title {
467
+ color: var(--text-color);
468
+ font-weight: 600;
469
+ font-size: 1rem;
470
+ }
471
+
472
+ @media (max-width: 768px) {
473
+ h1 {
474
+ font-size: 2rem;
475
+ }
476
+
477
+ .post-navigation {
478
+ grid-template-columns: 1fr;
479
+ }
480
+
481
+ .post-nav-link.next {
482
+ text-align: left;
483
+ }
484
+
485
+ .related-posts-grid {
486
+ grid-template-columns: 1fr;
487
+ }
488
+ }
489
+ </style>
490
+ </head>
491
+ <body>
492
+ <!-- Global Header -->
493
+ <%- include('components/header', { blogConfig }) %>
494
+
495
+ <article class="post-container">
496
+ <!-- Breadcrumbs -->
497
+ <nav class="breadcrumbs" aria-label="Breadcrumb">
498
+ <a href="<%= blogConfig.websiteUrl || '/' %>">Home</a>
499
+ <span>/</span>
500
+ <a href="<%= (blogConfig.websiteUrl || '') + '/blog/' %>">Blog</a>
501
+ <% if (metadata.category) { %>
502
+ <span>/</span>
503
+ <a href="/blog/category/<%= metadata.category.toLowerCase().replace(/\s+/g, '-') %>/"><%= metadata.category %></a>
504
+ <% } %>
505
+ <span>/</span>
506
+ <span><%= metadata.title %></span>
507
+ </nav>
508
+
509
+ <header class="post-header">
510
+ <h1><%= metadata.title %></h1>
511
+
512
+ <div class="post-meta">
513
+ <% if (metadata.category) { %>
514
+ <a href="/blog/category/<%= metadata.category.toLowerCase().replace(/\s+/g, '-') %>/" class="post-category">
515
+ <%= metadata.category %>
516
+ </a>
517
+ <% } %>
518
+ <div class="post-meta-item">
519
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
520
+ <circle cx="12" cy="12" r="10"></circle>
521
+ <polyline points="12 6 12 12 16 14"></polyline>
522
+ </svg>
523
+ <span><%= metadata.readTime %></span>
524
+ </div>
525
+ <div class="post-meta-item">
526
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
527
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
528
+ <line x1="16" y1="2" x2="16" y2="6"></line>
529
+ <line x1="8" y1="2" x2="8" y2="6"></line>
530
+ <line x1="3" y1="10" x2="21" y2="10"></line>
531
+ </svg>
532
+ <time datetime="<%= metadata.datePublished %>"><%= new Date(metadata.datePublished).toLocaleDateString(blogConfig.language || 'en', { year: 'numeric', month: 'long', day: 'numeric' }) %></time>
533
+ </div>
534
+ <% if (metadata.author) { %>
535
+ <div class="post-meta-item">
536
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
537
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
538
+ <circle cx="12" cy="7" r="4"></circle>
539
+ </svg>
540
+ <span><%= metadata.author %></span>
541
+ </div>
542
+ <% } %>
543
+ </div>
544
+ </header>
545
+
546
+ <div class="post-intro">
547
+ <%- intro %>
548
+ </div>
549
+
550
+ <% if (tableOfContents && tableOfContents.length > 0) { %>
551
+ <!-- Table of Contents -->
552
+ <nav class="table-of-contents" aria-label="Table of Contents">
553
+ <div class="toc-title">Table of Contents</div>
554
+ <ul class="toc-list">
555
+ <% tableOfContents.forEach(item => { %>
556
+ <li class="toc-item toc-item-level-<%= item.level %>">
557
+ <a href="#<%= item.id %>" class="toc-link"><%= item.text %></a>
558
+ </li>
559
+ <% }) %>
560
+ </ul>
561
+ </nav>
562
+ <% } %>
563
+
564
+ <div class="post-content">
565
+ <% sections.forEach(section => { %>
566
+ <section id="<%= section.heading.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') %>">
567
+ <h<%= section.level %>><%= section.heading %></h<%= section.level %>>
568
+ <%- section.content %>
569
+ </section>
570
+ <% }) %>
571
+ </div>
572
+
573
+ <div class="post-conclusion">
574
+ <%- conclusion %>
575
+ </div>
576
+
577
+ <!-- Tags -->
578
+ <% if (metadata.tags && metadata.tags.length > 0) { %>
579
+ <div class="post-tags">
580
+ <% metadata.tags.forEach(tag => { %>
581
+ <a href="/blog/tag/<%= tag.toLowerCase().replace(/\s+/g, '-') %>/" class="post-tag">#<%= tag %></a>
582
+ <% }) %>
583
+ </div>
584
+ <% } %>
585
+
586
+ <!-- Social Share -->
587
+ <%
588
+ const shareData = {
589
+ title: metadata.title,
590
+ description: metadata.description,
591
+ url: (blogConfig.websiteUrl || '') + metadata.url,
592
+ via: blogConfig.social && blogConfig.social.twitter ? blogConfig.social.twitter.replace('https://twitter.com/', '').replace('@', '') : null
593
+ };
594
+ %>
595
+ <%- include('components/social-share', { shareData }) %>
596
+
597
+ <div class="post-cta">
598
+ <a href="<%= cta.url %>"><%= cta.text %></a>
599
+ </div>
600
+
601
+ <% if (relatedPosts && relatedPosts.length > 0) { %>
602
+ <!-- Related Posts -->
603
+ <div class="related-posts">
604
+ <h3>Related Articles</h3>
605
+ <div class="related-posts-grid">
606
+ <% relatedPosts.forEach(post => { %>
607
+ <div class="related-post-card">
608
+ <div class="related-post-title">
609
+ <a href="<%= post.url %>"><%= post.title %></a>
610
+ </div>
611
+ <% if (post.excerpt) { %>
612
+ <p class="related-post-excerpt"><%= post.excerpt %></p>
613
+ <% } %>
614
+ </div>
615
+ <% }) %>
616
+ </div>
617
+ </div>
618
+ <% } %>
619
+
620
+ <% if (prevPost || nextPost) { %>
621
+ <!-- Post Navigation -->
622
+ <nav class="post-navigation" aria-label="Post navigation">
623
+ <% if (prevPost) { %>
624
+ <a href="<%= prevPost.url %>" class="post-nav-link prev" rel="prev">
625
+ <div class="post-nav-label">← Previous</div>
626
+ <div class="post-nav-title"><%= prevPost.title %></div>
627
+ </a>
628
+ <% } else { %>
629
+ <div></div>
630
+ <% } %>
631
+
632
+ <% if (nextPost) { %>
633
+ <a href="<%= nextPost.url %>" class="post-nav-link next" rel="next">
634
+ <div class="post-nav-label">Next →</div>
635
+ <div class="post-nav-title"><%= nextPost.title %></div>
636
+ </a>
637
+ <% } %>
638
+ </nav>
639
+ <% } %>
640
+ </article>
641
+
642
+ <!-- Global Footer -->
643
+ <%- include('components/footer', { blogConfig }) %>
644
+
645
+ <!-- AI-friendly metadata in comments for LLM parsing -->
646
+ <!--
647
+ METADATA:
648
+ - Title: <%= metadata.title %>
649
+ - Category: <%= metadata.category %>
650
+ - Keywords: <%= metadata.keywords.join(', ') %>
651
+ - Published: <%= metadata.datePublished %>
652
+ - SEO Score: <%= seoScore %>/100
653
+ -->
654
+ </body>
655
+ </html>