nibula 1.2.3 → 1.2.5

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 (106) hide show
  1. package/.eleventy.js +81 -81
  2. package/.eleventyignore +3 -3
  3. package/.github/workflows/publish.yml +37 -0
  4. package/CHANGELOG.md +166 -135
  5. package/LICENSE +169 -169
  6. package/NOTICE +4 -4
  7. package/README.md +120 -123
  8. package/bin/create.js +507 -507
  9. package/bin/nibula.js +316 -307
  10. package/docs/Assistant CLI.md +65 -65
  11. package/docs/Backend.md +295 -295
  12. package/docs/Components.md +84 -84
  13. package/docs/Creating pages.md +64 -64
  14. package/docs/Deploy.md +189 -189
  15. package/docs/Head and SEO.md +138 -138
  16. package/docs/Javascript.md +50 -50
  17. package/docs/Styling with SCSS.md +141 -141
  18. package/nginx.conf +75 -75
  19. package/package.json +1 -1
  20. package/src/backend/.htaccess +6 -6
  21. package/src/backend/_core/composer.json +5 -5
  22. package/src/backend/_core/composer.lock +492 -492
  23. package/src/backend/_core/index.js +267 -267
  24. package/src/backend/_core/index.php +147 -147
  25. package/src/backend/_core/init.js +52 -52
  26. package/src/backend/_core/init.php +33 -33
  27. package/src/backend/_core/modules/RateLimiter.js +58 -58
  28. package/src/backend/_core/modules/RateLimiter.php +30 -30
  29. package/src/backend/_core/modules/Response.js +59 -59
  30. package/src/backend/_core/modules/Response.php +48 -48
  31. package/src/backend/api/protected/example-protected.js +23 -23
  32. package/src/backend/api/protected/example-protected.php +16 -16
  33. package/src/backend/api/public/example-public.js +24 -24
  34. package/src/backend/api/public/example-public.php +16 -16
  35. package/src/backend/backend-node.service.example +30 -30
  36. package/src/backend/database/Database.js +46 -46
  37. package/src/backend/database/Database.php +23 -23
  38. package/src/backend/example.config.js +37 -37
  39. package/src/backend/example.config.php +27 -27
  40. package/src/backend/package.json +18 -18
  41. package/src/backend/web.config +16 -16
  42. package/src/frontend/.htaccess +51 -51
  43. package/src/frontend/404.njk +17 -17
  44. package/src/frontend/assets/brand/favicon.svg +54 -54
  45. package/src/frontend/assets/brand/logo.svg +54 -54
  46. package/src/frontend/components/global/footer.njk +25 -25
  47. package/src/frontend/components/global/header.njk +6 -6
  48. package/src/frontend/components/welcome.njk +114 -114
  49. package/src/frontend/data/site.json +48 -48
  50. package/src/frontend/index.njk +8 -8
  51. package/src/frontend/js/modules/exampleModule.js +2 -2
  52. package/src/frontend/js/pages/404.js +6 -6
  53. package/src/frontend/js/pages/homepage.js +6 -6
  54. package/src/frontend/layouts/base.njk +132 -132
  55. package/src/frontend/layouts/page-components.njk +13 -13
  56. package/src/frontend/llms.njk +17 -17
  57. package/src/frontend/robots.njk +7 -7
  58. package/src/frontend/scss/modules/_animations.scss +24 -24
  59. package/src/frontend/scss/modules/_footer.scss +27 -27
  60. package/src/frontend/scss/modules/_global.scss +47 -47
  61. package/src/frontend/scss/modules/_header.scss +27 -27
  62. package/src/frontend/scss/modules/_mobile.scss +29 -29
  63. package/src/frontend/scss/modules/_root.scss +34 -34
  64. package/src/frontend/scss/modules/_typography.scss +14 -14
  65. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
  66. package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
  67. package/src/frontend/scss/modules/frameworks/_foundation.scss +138 -139
  68. package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
  69. package/src/frontend/scss/pages/404.scss +27 -27
  70. package/src/frontend/scss/pages/homepage.scss +22 -22
  71. package/src/frontend/sitemap.njk +17 -17
  72. package/src/frontend/ts/modules/exampleModule.ts +2 -2
  73. package/src/frontend/ts/pages/404.ts +6 -6
  74. package/src/frontend/ts/pages/homepage.ts +6 -6
  75. package/src/frontend/web.config +55 -55
  76. package/tools/assistant.js +122 -151
  77. package/tools/buildJs.js +39 -37
  78. package/tools/cleanOutput.js +23 -25
  79. package/tools/cli/prompt.js +40 -0
  80. package/tools/cli/ui.js +74 -0
  81. package/tools/config/messages.json +78 -0
  82. package/tools/config/settings.json +135 -0
  83. package/tools/lib/colors.js +17 -0
  84. package/tools/lib/files.js +65 -0
  85. package/tools/lib/logger.js +22 -0
  86. package/tools/lib/outputPath.js +141 -0
  87. package/tools/lib/pageActions.js +138 -0
  88. package/tools/lib/pageArtifacts.js +46 -0
  89. package/tools/lib/pageComponents.js +88 -0
  90. package/tools/lib/paths.js +61 -0
  91. package/tools/lib/project.js +54 -0
  92. package/tools/lib/siteData.js +93 -0
  93. package/tools/lib/text.js +47 -0
  94. package/tools/lib/validation.js +39 -0
  95. package/tools/res/templates/template.js +7 -5
  96. package/tools/res/templates/template.njk +8 -8
  97. package/tools/res/templates/template.scss +22 -22
  98. package/tools/res/templates/template.ts +7 -5
  99. package/tsconfig.json +24 -24
  100. package/tools/modules/constants.js +0 -66
  101. package/tools/modules/pageComponents.js +0 -77
  102. package/tools/modules/updateData.js +0 -90
  103. package/tools/modules/updateOutputPath.js +0 -133
  104. package/tools/modules/updatePage.js +0 -162
  105. package/tools/modules/utils.js +0 -27
  106. package/tools/modules/validation.js +0 -30
@@ -1,115 +1,115 @@
1
- <div class="layout-container layout-my-3">
2
- <h1>Welcome to <span class="boilerplate-name">Nibula</span></h1>
3
- <div class="slogan">The boilerplate you need, simplified</div>
4
-
5
- <div id="content-welcome" class="tab-content active">
6
- <div style="display: flex; gap: 1rem; flex-wrap: wrap;">
7
- <a href="https://github.com/rhaastrake/nibula" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
8
- <svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
9
- <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
10
- </svg>
11
- <h3>GitHub repository</h3>
12
- <p>Explore the project and suggest improvements</p>
13
- <span class="card-link">Open the repository
14
- <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
15
- <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
16
- </svg>
17
- </span>
18
- </a>
19
- <a href="https://github.com/rhaastrake/nibula/tree/main/docs" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
20
- <svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
21
- <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
22
- <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
23
- </svg>
24
- <h3>Documentation</h3>
25
- <p>Everything you need to get started</p>
26
- <span class="card-link">Go to documentation
27
- <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
28
- <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
29
- </svg>
30
- </span>
31
- </a>
32
- </div>
33
- </div>
34
- <style>
35
- .layout-container {
36
- width: 100%;
37
- max-width: 1140px;
38
- margin-right: auto;
39
- margin-left: auto;
40
- padding-right: 15px;
41
- padding-left: 15px;
42
- }
43
- .layout-my-3 {
44
- margin-top: 1rem;
45
- margin-bottom: 1rem;
46
- }
47
-
48
- h1 {
49
- text-align: center;
50
- font-weight: 400;
51
- .boilerplate-name {
52
- color: #42b883;
53
- font-weight: 600;
54
- }
55
- }
56
-
57
- .slogan {
58
- text-align: center;
59
- color: #4b71a0;
60
- font-size: 1.5rem;
61
- margin-bottom: 1rem;
62
- }
63
-
64
- .card {
65
- background: #11474b;
66
- border: 0.8px solid #1e2d4a;
67
- border-radius: 10px;
68
- padding: 18px;
69
- display: flex;
70
- flex-direction: column;
71
- gap: 0.75rem;
72
- margin-top: 1rem;
73
- margin-bottom: 1rem;
74
- transition: border-color 0.2s, transform 0.15s;
75
- text-decoration: none;
76
- h3 {
77
- font-size: 1.5rem;
78
- font-weight: 600;
79
- color: #cbd5e1;
80
- letter-spacing: 0.01em;
81
- margin: 0;
82
- }
83
- p {
84
- font-size: 1rem;
85
- color: #5d8dc7;
86
- line-height: 1.6;
87
- margin: 0;
88
- }
89
- }
90
-
91
- .card:hover {
92
- border-color: #42b883;
93
- transform: translateY(-2px);
94
- .card-link {
95
- opacity: 1;
96
- }
97
- }
98
-
99
- .card-icon {
100
- width: 2rem;
101
- height: 2rem;
102
- color: #42b883;
103
- }
104
-
105
- .card-link {
106
- display: flex;
107
- align-items: center;
108
- gap: 4px;
109
- font-size: 1rem;
110
- color: #42b883;
111
- opacity: 0.8;
112
- transition: opacity 0.15s;
113
- margin-top: auto;
114
- }
1
+ <div class="layout-container layout-my-3">
2
+ <h1>Welcome to <span class="boilerplate-name">Nibula</span></h1>
3
+ <div class="slogan">The boilerplate you need, simplified</div>
4
+
5
+ <div id="content-welcome" class="tab-content active">
6
+ <div style="display: flex; gap: 1rem; flex-wrap: wrap;">
7
+ <a href="https://github.com/rhaastrake/nibula" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
8
+ <svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
9
+ <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
10
+ </svg>
11
+ <h3>GitHub repository</h3>
12
+ <p>Explore the project and suggest improvements</p>
13
+ <span class="card-link">Open the repository
14
+ <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
15
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
16
+ </svg>
17
+ </span>
18
+ </a>
19
+ <a href="https://github.com/rhaastrake/nibula/tree/main/docs" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
20
+ <svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
21
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
22
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
23
+ </svg>
24
+ <h3>Documentation</h3>
25
+ <p>Everything you need to get started</p>
26
+ <span class="card-link">Go to documentation
27
+ <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
28
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
29
+ </svg>
30
+ </span>
31
+ </a>
32
+ </div>
33
+ </div>
34
+ <style>
35
+ .layout-container {
36
+ width: 100%;
37
+ max-width: 1140px;
38
+ margin-right: auto;
39
+ margin-left: auto;
40
+ padding-right: 15px;
41
+ padding-left: 15px;
42
+ }
43
+ .layout-my-3 {
44
+ margin-top: 1rem;
45
+ margin-bottom: 1rem;
46
+ }
47
+
48
+ h1 {
49
+ text-align: center;
50
+ font-weight: 400;
51
+ .boilerplate-name {
52
+ color: #42b883;
53
+ font-weight: 600;
54
+ }
55
+ }
56
+
57
+ .slogan {
58
+ text-align: center;
59
+ color: #4b71a0;
60
+ font-size: 1.5rem;
61
+ margin-bottom: 1rem;
62
+ }
63
+
64
+ .card {
65
+ background: #11474b;
66
+ border: 0.8px solid #1e2d4a;
67
+ border-radius: 10px;
68
+ padding: 18px;
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: 0.75rem;
72
+ margin-top: 1rem;
73
+ margin-bottom: 1rem;
74
+ transition: border-color 0.2s, transform 0.15s;
75
+ text-decoration: none;
76
+ h3 {
77
+ font-size: 1.5rem;
78
+ font-weight: 600;
79
+ color: #cbd5e1;
80
+ letter-spacing: 0.01em;
81
+ margin: 0;
82
+ }
83
+ p {
84
+ font-size: 1rem;
85
+ color: #5d8dc7;
86
+ line-height: 1.6;
87
+ margin: 0;
88
+ }
89
+ }
90
+
91
+ .card:hover {
92
+ border-color: #42b883;
93
+ transform: translateY(-2px);
94
+ .card-link {
95
+ opacity: 1;
96
+ }
97
+ }
98
+
99
+ .card-icon {
100
+ width: 2rem;
101
+ height: 2rem;
102
+ color: #42b883;
103
+ }
104
+
105
+ .card-link {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 4px;
109
+ font-size: 1rem;
110
+ color: #42b883;
111
+ opacity: 0.8;
112
+ transition: opacity 0.15s;
113
+ margin-top: auto;
114
+ }
115
115
  </style>
@@ -1,48 +1,48 @@
1
- {
2
- "site_name": "Site name",
3
- "title": "Site title",
4
- "description": "Site description",
5
- "keywords": "keyword1, keyword2, keyword3",
6
- "domain": "yoursite.com",
7
- "url": "https://yoursite.com",
8
- "lang": "en",
9
- "author": "Name and surname",
10
- "theme": "dark",
11
- "legal": {
12
- "privacy": "",
13
- "cookie": "",
14
- "cookieControls": "",
15
- "terms": "",
16
- "copyright": {
17
- "year": "2026",
18
- "text": "All rights reserved."
19
- }
20
- },
21
- "pages": {
22
- "404": {
23
- "seo": {
24
- "title": "404 - Not found",
25
- "keywords": "",
26
- "noindex": true,
27
- "canonical": ""
28
- },
29
- "cdn": {
30
- "css": [],
31
- "js": []
32
- }
33
- },
34
- "homepage": {
35
- "seo": {
36
- "title": "Homepage",
37
- "description": "Description",
38
- "keywords": "",
39
- "noindex": false,
40
- "canonical": ""
41
- },
42
- "cdn": {
43
- "css": [],
44
- "js": []
45
- }
46
- }
47
- }
48
- }
1
+ {
2
+ "site_name": "Site name",
3
+ "title": "Site title",
4
+ "description": "Site description",
5
+ "keywords": "keyword1, keyword2, keyword3",
6
+ "domain": "yoursite.com",
7
+ "url": "https://yoursite.com",
8
+ "lang": "en",
9
+ "author": "Name and surname",
10
+ "theme": "dark",
11
+ "legal": {
12
+ "privacy": "",
13
+ "cookie": "",
14
+ "cookieControls": "",
15
+ "terms": "",
16
+ "copyright": {
17
+ "year": "2026",
18
+ "text": "All rights reserved."
19
+ }
20
+ },
21
+ "pages": {
22
+ "404": {
23
+ "seo": {
24
+ "title": "404 - Not found",
25
+ "keywords": "",
26
+ "noindex": true,
27
+ "canonical": ""
28
+ },
29
+ "cdn": {
30
+ "css": [],
31
+ "js": []
32
+ }
33
+ },
34
+ "homepage": {
35
+ "seo": {
36
+ "title": "Homepage",
37
+ "description": "Description",
38
+ "keywords": "",
39
+ "noindex": false,
40
+ "canonical": ""
41
+ },
42
+ "cdn": {
43
+ "css": [],
44
+ "js": []
45
+ }
46
+ }
47
+ }
48
+ }
@@ -1,9 +1,9 @@
1
- ---
2
- title: homepage
3
- permalink: /
4
- layout: page-components.njk
5
- ---
6
-
7
- <!-- !IMPORTANT -->
8
- <!-- DO NOT ADD ANYTHING HERE -->
1
+ ---
2
+ title: homepage
3
+ permalink: /
4
+ layout: page-components.njk
5
+ ---
6
+
7
+ <!-- !IMPORTANT -->
8
+ <!-- DO NOT ADD ANYTHING HERE -->
9
9
  <!-- You should create a new component.njk into src/components and include that in layouts/page-components.njk -->
@@ -1,3 +1,3 @@
1
- export function initExampleModule() {
2
- // Module logic here
1
+ export function initExampleModule() {
2
+ // Module logic here
3
3
  }
@@ -1,7 +1,7 @@
1
- // import { initExampleModule } from '../modules/exampleModule.js';
2
-
3
- document.addEventListener("DOMContentLoaded", () => {
4
- // initExampleModule();
5
- });
6
-
1
+ // import { initExampleModule } from '../modules/exampleModule.js';
2
+
3
+ document.addEventListener("DOMContentLoaded", () => {
4
+ // initExampleModule();
5
+ });
6
+
7
7
  // Page logic here
@@ -1,7 +1,7 @@
1
- // import { initExampleModule } from '../modules/exampleModule.js';
2
-
3
- document.addEventListener("DOMContentLoaded", () => {
4
- // initExampleModule();
5
- });
6
-
1
+ // import { initExampleModule } from '../modules/exampleModule.js';
2
+
3
+ document.addEventListener("DOMContentLoaded", () => {
4
+ // initExampleModule();
5
+ });
6
+
7
7
  // Page logic here
@@ -1,133 +1,133 @@
1
- <!-- This is the base of every page that you will create -->
2
-
3
- <!DOCTYPE html>
4
- <html lang="{{ site.lang or 'en' }}" data-bs-theme="{{ site.theme or 'light' }}">
5
-
6
- <head>
7
- <meta charset="UTF-8">
8
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
- <meta name="color-scheme" content="{{ site.theme or 'light' }}">
10
-
11
- {# Preconnect hints — establish early connections to external origins #}
12
- <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
13
-
14
- {#╔══════════════════╗
15
- ║ PAGE DATA ║
16
- ╚══════════════════╝#}
17
-
18
- {% set pageKey = title %}
19
- {% set pageData = site.pages[pageKey] %}
20
-
21
- {#╔══════════════════╗
22
- ║ SEO ║
23
- ╚══════════════════╝#}
24
-
25
- <link rel="canonical" href="{{ pageData.seo.canonical or (site.url + page.url) }}"/>
26
- <title>{{ pageData.seo.title or title or site.title }}</title>
27
- <meta name="description" content="{{ pageData.seo.description or site.description }}">
28
- <meta name="keywords" content="{{ pageData.seo.keywords or site.keywords }}">
29
- <meta name="author" content="{{ site.author }}">
30
- <meta name="theme-color" content="{{ site.theme_color }}">
31
-
32
- {% if pageData.seo.noindex %}
33
- <meta name="robots" content="noindex, follow">
34
- {% endif %}
35
-
36
- {# Open Graph #}
37
- <meta property="og:title" content="{{ pageData.seo.title }}">
38
- <meta property="og:description" content="{{ pageData.seo.description }}">
39
- <meta property="og:type" content="website">
40
- <meta property="og:url" content="{{ site.url }}{{ page.url }}">
41
- <meta property="og:image" content="{{ site.url }}{{ site.logo }}">
42
- <meta property="og:site_name" content="{{ site.site_name }}">
43
-
44
- {# Twitter Card #}
45
- <meta name="twitter:card" content="summary_large_image">
46
- <meta name="twitter:title" content="{{ pageData.seo.title }}">
47
- <meta name="twitter:description" content="{{ pageData.seo.description }}">
48
- <meta name="twitter:image" content="{{ site.url }}{{ site.logo }}">
49
-
50
- {#╔══════════════════════════════════════════╗
51
- ║ JSON-LD — Structured data for SEO/AI ║
52
- ╚══════════════════════════════════════════╝#}
53
-
54
- <script type="application/ld+json">
55
- {
56
- "@context": "https://schema.org",
57
- "@type": "WebPage",
58
- "name": {{ (pageData.seo.title or title or site.title) | dump }},
59
- "description": {{ (pageData.seo.description or site.description) | dump }},
60
- "url": {{ (site.url + page.url) | dump }},
61
- "author": {
62
- "@type": "Person",
63
- "name": {{ site.author | dump }}
64
- },
65
- "publisher": {
66
- "@type": "Organization",
67
- "name": {{ site.site_name | dump }},
68
- "logo": {
69
- "@type": "ImageObject",
70
- "url": {{ (site.url + site.logo) | dump }}
71
- }
72
- }
73
- }
74
- </script>
75
-
76
- {#╔══════════════════╗
77
- ║ FAVICON ║
78
- ╚══════════════════╝#}
79
-
80
- <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
81
- <link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
82
- <link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
83
-
84
- {#╔══════════════════╗
85
- ║ CSS ║
86
- ╚══════════════════╝#}
87
-
88
- {# Page CSS #}
89
- <link rel="stylesheet" href="/css/pages/{{ title }}.css"/>
90
-
91
- {# Per-page CDN CSS #}
92
- {% if pageData and pageData.cdn and pageData.cdn.css %}
93
- {% for link in pageData.cdn.css %}
94
- <link rel="stylesheet" href="{{ link }}">
95
- {% endfor %}
96
- {% endif %}
97
-
98
- </head>
99
-
100
- <body>
101
-
102
- {% include "global/header.njk" %}
103
-
104
- <main>
105
- {{ content | safe }}
106
- </main>
107
-
108
- {% include "global/footer.njk" %}
109
-
110
- {#╔══════════════════╗
111
- ║ SCRIPTS ║
112
- ╚══════════════════╝#}
113
-
114
- {# Framework — uncomment the one you are using #}
115
- <script src="/js/bootstrap.bundle.min.js" defer></script>
116
- {# <script src="/js/foundation.min.js" defer></script> #}
117
- {# <script src="/js/uikit.min.js" defer></script> #}
118
- {# <script src="/js/uikit-icons.min.js" defer></script> #}
119
- {# Bulma — no JS needed #}
120
-
121
- {# Per-page CDN JS #}
122
- {% if pageData and pageData.cdn and pageData.cdn.js %}
123
- {% for script in pageData.cdn.js %}
124
- <script src="{{ script }}" defer></script>
125
- {% endfor %}
126
- {% endif %}
127
-
128
- {# Page JS #}
129
- <script src="/js/pages/{{ title }}.js"></script>
130
-
131
- </body>
132
-
1
+ <!-- This is the base of every page that you will create -->
2
+
3
+ <!DOCTYPE html>
4
+ <html lang="{{ site.lang or 'en' }}" data-bs-theme="{{ site.theme or 'light' }}">
5
+
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <meta name="color-scheme" content="{{ site.theme or 'light' }}">
10
+
11
+ {# Preconnect hints — establish early connections to external origins #}
12
+ <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
13
+
14
+ {#╔══════════════════╗
15
+ ║ PAGE DATA ║
16
+ ╚══════════════════╝#}
17
+
18
+ {% set pageKey = title %}
19
+ {% set pageData = site.pages[pageKey] %}
20
+
21
+ {#╔══════════════════╗
22
+ ║ SEO ║
23
+ ╚══════════════════╝#}
24
+
25
+ <link rel="canonical" href="{{ pageData.seo.canonical or (site.url + page.url) }}"/>
26
+ <title>{{ pageData.seo.title or title or site.title }}</title>
27
+ <meta name="description" content="{{ pageData.seo.description or site.description }}">
28
+ <meta name="keywords" content="{{ pageData.seo.keywords or site.keywords }}">
29
+ <meta name="author" content="{{ site.author }}">
30
+ <meta name="theme-color" content="{{ site.theme_color }}">
31
+
32
+ {% if pageData.seo.noindex %}
33
+ <meta name="robots" content="noindex, follow">
34
+ {% endif %}
35
+
36
+ {# Open Graph #}
37
+ <meta property="og:title" content="{{ pageData.seo.title }}">
38
+ <meta property="og:description" content="{{ pageData.seo.description }}">
39
+ <meta property="og:type" content="website">
40
+ <meta property="og:url" content="{{ site.url }}{{ page.url }}">
41
+ <meta property="og:image" content="{{ site.url }}{{ site.logo }}">
42
+ <meta property="og:site_name" content="{{ site.site_name }}">
43
+
44
+ {# Twitter Card #}
45
+ <meta name="twitter:card" content="summary_large_image">
46
+ <meta name="twitter:title" content="{{ pageData.seo.title }}">
47
+ <meta name="twitter:description" content="{{ pageData.seo.description }}">
48
+ <meta name="twitter:image" content="{{ site.url }}{{ site.logo }}">
49
+
50
+ {#╔══════════════════════════════════════════╗
51
+ ║ JSON-LD — Structured data for SEO/AI ║
52
+ ╚══════════════════════════════════════════╝#}
53
+
54
+ <script type="application/ld+json">
55
+ {
56
+ "@context": "https://schema.org",
57
+ "@type": "WebPage",
58
+ "name": {{ (pageData.seo.title or title or site.title) | dump }},
59
+ "description": {{ (pageData.seo.description or site.description) | dump }},
60
+ "url": {{ (site.url + page.url) | dump }},
61
+ "author": {
62
+ "@type": "Person",
63
+ "name": {{ site.author | dump }}
64
+ },
65
+ "publisher": {
66
+ "@type": "Organization",
67
+ "name": {{ site.site_name | dump }},
68
+ "logo": {
69
+ "@type": "ImageObject",
70
+ "url": {{ (site.url + site.logo) | dump }}
71
+ }
72
+ }
73
+ }
74
+ </script>
75
+
76
+ {#╔══════════════════╗
77
+ ║ FAVICON ║
78
+ ╚══════════════════╝#}
79
+
80
+ <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
81
+ <link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
82
+ <link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
83
+
84
+ {#╔══════════════════╗
85
+ ║ CSS ║
86
+ ╚══════════════════╝#}
87
+
88
+ {# Page CSS #}
89
+ <link rel="stylesheet" href="/css/pages/{{ title }}.css"/>
90
+
91
+ {# Per-page CDN CSS #}
92
+ {% if pageData and pageData.cdn and pageData.cdn.css %}
93
+ {% for link in pageData.cdn.css %}
94
+ <link rel="stylesheet" href="{{ link }}">
95
+ {% endfor %}
96
+ {% endif %}
97
+
98
+ </head>
99
+
100
+ <body>
101
+
102
+ {% include "global/header.njk" %}
103
+
104
+ <main>
105
+ {{ content | safe }}
106
+ </main>
107
+
108
+ {% include "global/footer.njk" %}
109
+
110
+ {#╔══════════════════╗
111
+ ║ SCRIPTS ║
112
+ ╚══════════════════╝#}
113
+
114
+ {# Framework — uncomment the one you are using #}
115
+ <script src="/js/bootstrap.bundle.min.js" defer></script>
116
+ {# <script src="/js/foundation.min.js" defer></script> #}
117
+ {# <script src="/js/uikit.min.js" defer></script> #}
118
+ {# <script src="/js/uikit-icons.min.js" defer></script> #}
119
+ {# Bulma — no JS needed #}
120
+
121
+ {# Per-page CDN JS #}
122
+ {% if pageData and pageData.cdn and pageData.cdn.js %}
123
+ {% for script in pageData.cdn.js %}
124
+ <script src="{{ script }}" defer></script>
125
+ {% endfor %}
126
+ {% endif %}
127
+
128
+ {# Page JS #}
129
+ <script src="/js/pages/{{ title }}.js"></script>
130
+
131
+ </body>
132
+
133
133
  </html>