nibula 1.5.3 → 1.6.1

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 (46) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +2 -0
  3. package/bin/create.js +1 -1
  4. package/bin/delegate.js +58 -0
  5. package/bin/nibula.js +4 -19
  6. package/docs/Assistant CLI.md +3 -3
  7. package/docs/Components.md +68 -12
  8. package/docs/Creating pages.md +18 -13
  9. package/docs/Head and SEO.md +7 -6
  10. package/docs/Javascript.md +11 -9
  11. package/docs/Styling with SCSS.md +9 -11
  12. package/package.json +17 -22
  13. package/src/frontend/components/welcome.njk +1 -0
  14. package/src/frontend/data/pages.json +27 -0
  15. package/src/frontend/data/site.json +1 -28
  16. package/src/frontend/js/global.js +11 -0
  17. package/src/frontend/js/pages/404.js +2 -1
  18. package/src/frontend/js/pages/homepage.js +2 -1
  19. package/src/frontend/layouts/base.njk +23 -19
  20. package/src/frontend/scss/{modules/_global.scss → _global.scss} +9 -10
  21. package/src/frontend/scss/_root.scss +14 -0
  22. package/src/frontend/scss/modules/_animations.scss +1 -1
  23. package/src/frontend/scss/modules/_footer.scss +2 -1
  24. package/src/frontend/scss/modules/_header.scss +1 -2
  25. package/src/frontend/scss/modules/_mobile.scss +1 -1
  26. package/src/frontend/scss/modules/_typography.scss +5 -1
  27. package/src/frontend/scss/pages/404.scss +2 -2
  28. package/src/frontend/scss/pages/homepage.scss +2 -2
  29. package/src/frontend/sitemap.njk +1 -1
  30. package/src/frontend/ts/global.ts +12 -0
  31. package/src/frontend/ts/pages/404.ts +2 -1
  32. package/src/frontend/ts/pages/homepage.ts +2 -1
  33. package/tools/cli/ui.js +17 -3
  34. package/tools/config/messages.json +10 -8
  35. package/tools/config/settings.json +2 -6
  36. package/tools/lib/pageActions.js +8 -5
  37. package/tools/lib/pagesData.js +88 -0
  38. package/tools/lib/paths.js +8 -5
  39. package/tools/lib/validation.js +1 -0
  40. package/tools/res/templates/template.js +1 -1
  41. package/tools/res/templates/template.scss +3 -3
  42. package/tools/res/templates/template.ts +2 -1
  43. package/src/frontend/js/modules/global.js +0 -10
  44. package/src/frontend/scss/modules/_root.scss +0 -35
  45. package/src/frontend/ts/modules/global.ts +0 -10
  46. package/tools/lib/siteData.js +0 -89
package/CHANGELOG.md CHANGED
@@ -5,6 +5,44 @@ All notable changes to Nibula are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.1] - 2026-08-06
9
+
10
+ ### Fixed
11
+ - `src/frontend/scss/pages/prova.scss` was left in the template and shipped with 1.6.0, so every new project started with a stylesheet for a page that doesn't exist — no route, no script, no entry in `pages.json`.
12
+ - Open Graph and Twitter Card tags read `seo.title` and `seo.description` directly, with none of the fallbacks the `<title>` and meta description already had. A page that left those fields empty in `pages.json` published an empty social preview — exactly the case the fallbacks exist for. They now follow the same chain.
13
+ - `data-theme` was missing from the `<html>` tag. It is documented as the hook to target in SCSS for theme variables, so a selector written against it never matched.
14
+
15
+ ## [1.6.0] - 2026-08-06
16
+
17
+ ### Breaking
18
+ - Per-page settings must move out of `site.json`. Take the block under the `pages`
19
+ key and make it the root object of a new `src/frontend/data/pages.json`, then
20
+ update `base.njk`, `sitemap.njk` and `llms.njk` to read `pages[title]` instead of
21
+ `site.pages[title]`. Without `pages.json` the assistant reports the missing file
22
+ and stops.
23
+ - SCSS and JS import paths change with the `_root`, `_global` and `global` moves.
24
+ Update the `@use` and `@import` lines in your page entry points accordingly.
25
+ - Existing projects keep working until you run `npm install nibula@latest` inside
26
+ them: commands now delegate to the project's own copy, so an old project keeps
27
+ running the version it was built with.
28
+
29
+ ### Changed
30
+ - **Per-page settings moved from `site.json` to a new `pages.json`.** They were nested under a `pages` key in the same file the CLI writes on every page operation; splitting them means a syntax error in one page's record no longer makes the whole site configuration unreadable. Access changes from `{{ site.pages[title] }}` to `{{ pages[title] }}`, and the CLI now reads and writes `pages.json` only.
31
+ - Commands run inside a project now execute the project's own Nibula, taken from its `node_modules`, instead of the globally installed one. `nib cli` typed in a terminal used to run the global copy against local files, so a project scaffolded with an older version could be operated on by a newer CLI expecting a different structure. When the two versions differ, the one being used is reported before the command runs, and the assistant's title shows it as well.
32
+ - `_root.scss` and `_global.scss` moved out of `scss/modules/`. They are not modules — every page imports them and nothing else does — so they now sit at the root of `scss/`.
33
+ - `global.js` and `global.ts` moved out of their `modules/` folders, for the same reason.
34
+ - Import paths updated across existing modules, pages and scaffold templates to match both moves.
35
+ - `base.njk` applies the `url` filter to per-page CSS and JS, to the favicons and to the framework scripts. Without it these paths ignore `pathPrefix`, so every asset 404s on a site published under a subpath — GitHub Pages being the common case.
36
+ - Documentation updated throughout for the new structure and the `site.json` / `pages.json` split.
37
+
38
+ ### Fixed
39
+ - TypeScript page entry points imported modules with a `.js` extension.
40
+ - `logo` was nested inside `legal` in `site.json`, so `{{ site.logo }}` resolved to nothing in the header, the footer, the Open Graph image and the JSON-LD publisher block. It now sits at the root, where the templates already looked for it.
41
+
42
+ ### Removed
43
+ - `legal.cookieControls` from `site.json`.
44
+ - Preconnect hint to `cdn.jsdelivr.net` in `base.njk`. The project loads its framework from `node_modules` through passthrough copy, so the hint opened a TCP and TLS connection to a host that was never used.
45
+
8
46
  ## [1.5.3] - 2026-08-05
9
47
 
10
48
  ### Fixed
package/README.md CHANGED
@@ -106,3 +106,5 @@ Thanks for giving Nibula a try.
106
106
  It's a one-person project, so every bit of feedback counts more than you'd think: if something breaks, if the documentation isn't clear, or if a feature is missing, open an [issue](https://github.com/Rhaastrake/Nibula/issues) — even just to tell me what you built with it.
107
107
 
108
108
  If Nibula was useful to you, leaving a ⭐ on the repository is the easiest way to help other people find it.
109
+
110
+ You can support the project by [buying me a coffee](https://paypal.me/rhaastrake) ☕🫡
package/bin/create.js CHANGED
@@ -78,7 +78,7 @@ const CREATE_DIRS = [];
78
78
 
79
79
  // Backend files that belong to exactly one backend, matched by basename.
80
80
  // Everything else (migrations, .htaccess, web.config, README, ...) is shared.
81
- const NODE_ONLY_FILES = new Set(['package.json', 'backend-node.service.example']);
81
+ const NODE_ONLY_FILES = new Set(['package.json']);
82
82
  const PHP_ONLY_FILES = new Set(['composer.json', 'composer.lock']);
83
83
  const PHP_ONLY_DIRS = new Set(['vendor']);
84
84
  // Runtime artifacts that must never be copied from the template, either way.
@@ -0,0 +1,58 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawnSync } = require('child_process');
4
+ const { color } = require('../tools/lib/colors');
5
+ const { message } = require('../tools/lib/logger');
6
+
7
+ const PACKAGE_NAME = 'nibula';
8
+ const NODE_MODULES = 'node_modules';
9
+ const ENTRY_FILE = path.join('bin', 'nibula.js');
10
+ const PACKAGE_FILE = 'package.json';
11
+ const LOCAL_FLAG = 'NIBULA_LOCAL';
12
+ const LOCAL_FLAG_VALUE = '1';
13
+
14
+ const globalVersion = require(`../${PACKAGE_FILE}`).version;
15
+
16
+ function realPath(target) {
17
+ try {
18
+ return fs.realpathSync(target);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ function readVersion(packageRoot) {
25
+ try {
26
+ return JSON.parse(fs.readFileSync(path.join(packageRoot, PACKAGE_FILE), 'utf8')).version;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ function delegateToLocal(projectRoot) {
33
+ if (process.env[LOCAL_FLAG] === LOCAL_FLAG_VALUE) return;
34
+
35
+ const localRoot = realPath(path.join(projectRoot, NODE_MODULES, PACKAGE_NAME));
36
+ const selfRoot = realPath(path.join(__dirname, '..'));
37
+
38
+ if (!localRoot || localRoot === selfRoot) return;
39
+
40
+ const localEntry = path.join(localRoot, ENTRY_FILE);
41
+ if (!fs.existsSync(localEntry)) return;
42
+
43
+ const localVersion = readVersion(localRoot);
44
+
45
+ if (localVersion && localVersion !== globalVersion) {
46
+ console.log(`${color.dim}${message('cli.usingLocalVersion', { local: localVersion, global: globalVersion })}${color.reset}`);
47
+ }
48
+
49
+ const result = spawnSync(process.execPath, [localEntry, ...process.argv.slice(2)], {
50
+ stdio: 'inherit',
51
+ cwd: process.cwd(),
52
+ env: { ...process.env, [LOCAL_FLAG]: LOCAL_FLAG_VALUE },
53
+ });
54
+
55
+ process.exit(result.status ?? 0);
56
+ }
57
+
58
+ module.exports = { delegateToLocal };
package/bin/nibula.js CHANGED
@@ -8,6 +8,7 @@ const { spawnSync } = require('child_process');
8
8
  const { findProjectRoot, NOT_INSIDE_PROJECT_MESSAGE } = require('../tools/lib/paths');
9
9
  const { color } = require('../tools/lib/colors');
10
10
  const { message } = require('../tools/lib/logger');
11
+ const { delegateToLocal } = require('./delegate');
11
12
 
12
13
  const pkg = require('../package.json');
13
14
 
@@ -48,27 +49,9 @@ function requireOutsideProject() {
48
49
  process.exit(1);
49
50
  }
50
51
 
51
- function maybeDelegateToLocal(root) {
52
- const local = path.join(root, 'node_modules', 'nibula', 'bin', 'nibula.js');
53
- if (!fs.existsSync(local)) return;
54
-
55
- let localReal;
56
- let selfReal;
57
- try { localReal = fs.realpathSync(local); } catch { return; }
58
- try { selfReal = fs.realpathSync(__filename); } catch { selfReal = __filename; }
59
-
60
- if (localReal !== selfReal) {
61
- const res = spawnSync('node', [localReal, ...process.argv.slice(2)], {
62
- stdio: 'inherit',
63
- cwd: process.cwd(),
64
- });
65
- process.exit(res.status ?? 0);
66
- }
67
- }
68
-
69
52
  function enterProject() {
70
53
  const root = requireProjectRoot();
71
- maybeDelegateToLocal(root);
54
+ delegateToLocal(root);
72
55
  return root;
73
56
  }
74
57
 
@@ -198,6 +181,7 @@ function findExistingProject(baseDir, projectName) {
198
181
 
199
182
  function usage(currentVersion) {
200
183
  console.log(`
184
+
201
185
  ${color.bold}${color.cyan}Nibula (${currentVersion})${color.reset} ${color.bold}by Michele Garofalo${color.reset}
202
186
 
203
187
  ${color.yellow}nib new <project-name>${color.reset} Create your new project
@@ -206,6 +190,7 @@ ${color.yellow}nib run${color.reset} Start the dev server and b
206
190
  ${color.yellow}nib build${color.reset} Build the site out folder to publish
207
191
  ${color.yellow}nib clean${color.reset} Remove the output directory
208
192
  ${color.yellow}nib update${color.reset} Update to the latest version
193
+
209
194
  `);
210
195
  }
211
196
 
@@ -33,7 +33,7 @@ For a page named `my-page`, the following files are created:
33
33
  | `src/frontend/js/pages/myPage.js` | JS entry point |
34
34
  | `src/frontend/routes/my-page.njk` | Nunjucks template |
35
35
 
36
- It also adds an `elif` block in `page-components.njk` and a stub entry in `site.json`:
36
+ It also adds an `elif` block in `page-components.njk` and a stub entry in `pages.json`:
37
37
 
38
38
  ```json
39
39
  "myPage": {
@@ -53,11 +53,11 @@ It also adds an `elif` block in `page-components.njk` and a stub entry in `site.
53
53
 
54
54
  ## Remove page
55
55
 
56
- Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `site.json`.
56
+ Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `pages.json`.
57
57
 
58
58
  ## Rename page
59
59
 
60
- Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `site.json` while preserving all existing fields.
60
+ Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `pages.json`. Nothing inside the record is touched — your SEO title, description and CDN links stay exactly as you wrote them.
61
61
 
62
62
  ## Configure output path
63
63
 
@@ -28,8 +28,8 @@ To render a component inside a page, navigate to `src/frontend/layouts/` and edi
28
28
  {% include "welcome.njk" %}
29
29
 
30
30
  {% elif title == "examplePage" %}
31
- {% include "exampleComponent1.njk" %}
32
- {% include "subfolder/exampleComponent2.njk" %}
31
+ {% include "example-component-1.njk" %}
32
+ {% include "subfolder/example-component-2.njk" %}
33
33
 
34
34
  {% else %}
35
35
  {% include "404/_404.njk" %}
@@ -50,8 +50,8 @@ A component can include other components. This is useful for breaking complex se
50
50
  ### exampleComponent.njk
51
51
  ```js
52
52
  <section class="hero">
53
- {% include "ui/heroTitle.njk" %}
54
- {% include "ui/heroButton.njk" %}
53
+ {% include "hero/hero-title.njk" %}
54
+ {% include "hero/hero-button.njk" %}
55
55
  </section>
56
56
  ```
57
57
 
@@ -67,14 +67,23 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
67
67
 
68
68
  ### site.json <small>(`src/frontend/data/`)</small>
69
69
  ```json
70
- {
71
- "title": "My Site",
72
- "theme": "dark",
73
- "logo": "/assets/brand/logo.svg",
74
- "legal": {
75
- "privacy": "/privacy"
70
+ "site_name": "Site name",
71
+ "title": "Site title",
72
+ "description": "Site description",
73
+ "keywords": "keyword1, keyword2, keyword3",
74
+ "domain": "yoursite.com",
75
+ "url": "https://yoursite.com",
76
+ "lang": "en",
77
+ "author": "Name and surname",
78
+ "logo": "/assets/brand/logo.svg",
79
+ "legal": {
80
+ "privacy": "", // This is supposed to contain a link
81
+ "cookie": "", // This is supposed to contain a link
82
+ "terms": "", // This is supposed to contain a link
83
+ "copyright": {
84
+ "year": "2026",
85
+ "text": "All rights reserved."
76
86
  }
77
- }
78
87
  ```
79
88
 
80
89
  ### Usage in any `.njk` file
@@ -82,4 +91,51 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
82
91
  <p>{{ site.title }}</p>
83
92
  <a href="{{ site.legal.privacy }}">Privacy Policy</a>
84
93
  <img src="{{ site.logo }}" alt="{{ site.title }}">
85
- ```
94
+ ```
95
+
96
+ ## Your own data files
97
+
98
+ `site.json` isn't special: **any** `.json` file you drop in `src/frontend/data/` becomes a global variable, and the variable takes the name of the file.
99
+
100
+ Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
101
+
102
+ ### test.json <small>(`src/frontend/data/`)</small>
103
+ ```json
104
+ {
105
+ "testMessage": "This is a test"
106
+ }
107
+ ```
108
+
109
+ ### Usage in any `.njk` file
110
+ ```js
111
+ <p>{{ test.testMessage }}</p>
112
+ ```
113
+
114
+ Renders as:
115
+ ```html
116
+ <p>This is a test</p>
117
+ ```
118
+
119
+ That's the whole rule: **file name = variable name, keys inside = what you write after the dot.** `site.json` gives you `site.title` for exactly the same reason.
120
+
121
+ Values can be anything JSON allows, so a list lets you loop instead of copying markup:
122
+
123
+ ```json
124
+ {
125
+ "testMessage": "This is a test",
126
+ "links": [
127
+ { "label": "Home", "url": "/" },
128
+ { "label": "Contact", "url": "/contact/" }
129
+ ]
130
+ }
131
+ ```
132
+
133
+ ```js
134
+ {% for link in test.links %}
135
+ <a href="{{ link.url }}">{{ link.label }}</a>
136
+ {% endfor %}
137
+ ```
138
+
139
+ Subfolders create nested names: `data/shop/products.json` becomes `{{ shop.products }}`.
140
+
141
+ > ⚠️ Don't reuse a name that already exists. Another file named `site.json` in a subfolder overwrites the original one silently, with no error to point you at the cause.
@@ -11,16 +11,16 @@ For a page named `my-page`:
11
11
  |---|---|
12
12
  | `src/frontend/routes/my-page.njk` | Template with front matter |
13
13
  | `src/frontend/scss/pages/myPage.scss` | Imports framework + modules |
14
- | `src/frontend/js/pages/myPage.js` | Imports JS modules |
14
+ | `src/frontend/js/pages/myPage.js` | Imports JS/TS modules |
15
15
 
16
16
  ## Adding content
17
17
 
18
- 1. Create a component in `src/frontend/components/` (e.g. `_myPage.njk`)
18
+ 1. Create a component in `src/frontend/components/` (e.g. `my-component.njk`)
19
19
  2. Include it in `src/frontend/layouts/page-components.njk` inside the generated `elif` block:
20
20
 
21
21
  ```njk
22
22
  {% elif title == "myPage" %}
23
- {% include "_myPage.njk" %}
23
+ {% include "my-component.njk" ignore missing %}
24
24
  ```
25
25
 
26
26
  See **Components** DOC file for more info
@@ -51,15 +51,20 @@ The parent path (`about`) does **not** need to exist as a real page — it's jus
51
51
 
52
52
  ## SEO
53
53
 
54
- The CLI creates a stub entry in `src/frontend/data/site.json`. Fill it in:
54
+ The CLI creates a stub entry in `src/frontend/data/pages.json`. Fill it in:
55
55
 
56
56
  ```json
57
- "myPage": {
58
- "seo": {
59
- "title": "My Page | Site Name",
60
- "description": "Page description"
61
- }
62
- }
63
- ```
64
-
65
- See **Head & SEO** for all available options.
57
+ "myPage": {
58
+ "seo": {
59
+ "title": "My Page",
60
+ "description": "Description",
61
+ "keywords": "",
62
+ "noindex": false,
63
+ "canonical": ""
64
+ },
65
+ "cdn": {
66
+ "css": [],
67
+ "js": []
68
+ }
69
+ }
70
+ ```
@@ -1,6 +1,6 @@
1
1
  # Head & SEO
2
2
 
3
- Global settings live in `src/frontend/data/site.json` and are available everywhere via `{{ site.* }}`.
3
+ Global settings live in `src/frontend/data/pages.json` and are available everywhere via `{{ pages.* }}`.
4
4
 
5
5
  ## Global fields
6
6
 
@@ -46,10 +46,10 @@ Global settings live in `src/frontend/data/site.json` and are available everywhe
46
46
 
47
47
  ## Per-page SEO and CDN
48
48
 
49
- Each page is keyed by its camelCase `title` from the front matter:
49
+ Per-page settings live in `src/frontend/data/pages.json`, available via `{{ pages.* }}`. Each page is keyed by its camelCase `title` from the front matter, at the root of the file:
50
50
 
51
51
  ```json
52
- "pages": {
52
+ {
53
53
  "examplePage": {
54
54
  "seo": {
55
55
  "title": "Example Page",
@@ -113,14 +113,15 @@ See the SCSS docs for how to hook your variables to `data-theme`.
113
113
  The three favicon tags are hardcoded in `base.njk`. To change the icons, replace the files in `src/frontend/assets/brand/` keeping the same names:
114
114
 
115
115
  ```html
116
- <link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
117
- <link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
118
- <link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
116
+ <link rel="icon" type="image/svg+xml" href="{{ '/assets/brand/favicon.svg' | url }}">
117
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ '/assets/brand/favicon-32.png' | url }}">
118
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/brand/apple-touch-icon.png' | url }}">
119
119
  ```
120
120
 
121
121
  | File | Purpose |
122
122
  |---|---|
123
123
  | `favicon.svg` | Modern browsers, scales to any size |
124
+ | `favicon-48.png` | Fallback for older browsers and some crawlers |
124
125
  | `favicon-32.png` | Fallback for older browsers and some crawlers |
125
126
  | `apple-touch-icon.png` | iOS home screen icon — 180×180, must be opaque (iOS renders transparency as black) |
126
127
 
@@ -11,7 +11,8 @@ Import only what the page needs.
11
11
  ### examplePage.js <small>(`src/frontend/js/pages/`)</small>
12
12
 
13
13
  ```js
14
- import '../modules/global.js';
14
+ import '../global.js';
15
+
15
16
  // import { initExampleModule } from '../modules/exampleModule.js';
16
17
 
17
18
  document.addEventListener("DOMContentLoaded", () => {
@@ -25,9 +26,9 @@ document.addEventListener("DOMContentLoaded", () => {
25
26
 
26
27
  Some code has to run on every page: a header menu, a theme toggle, a cookie banner. Repeating it in every page entry point means you'll eventually forget it on one of them, and the bug only shows up on that single page.
27
28
 
28
- That's what `global.js` is for. It lives in `src/frontend/js/modules/`, every page entry point imports it, and it takes care of running the shared modules.
29
+ That's what `global.js` is for. It lives in `src/frontend/js/`, every page entry point imports it, and it takes care of running the shared modules.
29
30
 
30
- ### global.js <small>(`src/frontend/js/modules/`)</small>
31
+ ### global.js <small>(`src/frontend/js/`)</small>
31
32
 
32
33
  ```js
33
34
  // import { initExampleModule } from './exampleModule.js';
@@ -38,14 +39,15 @@ function initGlobal() {
38
39
 
39
40
  // Global logic here
40
41
 
41
- // Do not touch this line
42
+ // Do not touch
43
+ // This instruction starts global.js itself
42
44
  document.addEventListener('DOMContentLoaded', initGlobal);
43
45
  ```
44
46
 
45
47
  Pages import it without curly braces, since there is nothing to export — the module runs on its own as soon as it's part of the bundle:
46
48
 
47
49
  ```js
48
- import '../modules/global.js';
50
+ import '../global.js';
49
51
  ```
50
52
 
51
53
  Note that the import path inside `global.js` is `'./exampleModule.js'` and not `'../modules/exampleModule.js'`: `global.js` already sits in the modules folder, next to the modules it imports.
@@ -56,9 +58,8 @@ A responsive header has a burger button that opens the navigation on small scree
56
58
 
57
59
  Write the behaviour as a normal module:
58
60
 
61
+ ### header.js <small>(`src/frontend/js/modules/`)</small>
59
62
  ```js
60
- // src/frontend/js/modules/header.js
61
-
62
63
  export function initHeader() {
63
64
  const toggle = document.querySelector('.menu-toggle');
64
65
  const menu = document.querySelector('.menu');
@@ -74,13 +75,14 @@ export function initHeader() {
74
75
  Then wire it up once, in `global.js`:
75
76
 
76
77
  ```js
77
- import { initHeader } from './header.js';
78
+ import { initHeader } from './modules/header.js';
78
79
 
79
80
  function initGlobal() {
80
81
  initHeader();
81
82
  }
82
83
 
83
- // Do not touch this line
84
+ // Do not touch
85
+ // This instruction starts global.js itself
84
86
  document.addEventListener('DOMContentLoaded', initGlobal);
85
87
  ```
86
88
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  Each page has its own SCSS entry point in `src/frontend/scss/pages/`
6
6
 
7
- It must contain `_root.scss` + other modules like `_global.scss` or any other one that you need and its own specific css rules
7
+ It must contain `_root.scss` + any other module that you need and its own specific css rules
8
8
 
9
9
  `_root.scss` uses `@use` to enable namespaced access (`root.$var`); other modules use `@import` as they don't expose variables.
10
10
 
@@ -14,9 +14,9 @@ It must contain `_root.scss` + other modules like `_global.scss` or any other on
14
14
  // CSS MODULES IMPORTS
15
15
  //==========================
16
16
 
17
- @use "../modules/root" as root;
17
+ @use "../root" as root;
18
18
 
19
- @import "../modules/global";
19
+ @import "../global";
20
20
 
21
21
  //==========================
22
22
  // PAGE CUSTOM CSS RULES
@@ -49,7 +49,7 @@ You can create subfolders if you want to refactor the structure, but be sure to
49
49
 
50
50
  ### _yourModule.scss <small>(`src/frontend/scss/modules/subfolder/`)</small>
51
51
  ```scss
52
- @use '../root' as root;
52
+ @use '../../root' as root;
53
53
 
54
54
  body {
55
55
  background-color: root.$primary;
@@ -71,8 +71,6 @@ body {
71
71
 
72
72
  | File | Purpose |
73
73
  |---|---|
74
- | `_root.scss` | Global variables (colors, spacing) |
75
- | `_global.scss` | Site-wide base rules and frameworks |
76
74
  | `_typography.scss` | Font rules |
77
75
  | `_header.scss` | Header styles |
78
76
  | `_footer.scss` | Footer styles |
@@ -87,12 +85,12 @@ You can choose one or none of them (more than 1 works, but you may get in variou
87
85
 
88
86
  To enable/disable them you have to modify 3 files around the project by just commenting them
89
87
 
90
- ### 1. _global.scss <small>(`src/frontend/scss/modules/`)</small>
88
+ ### 1. _global.scss <small>(`src/frontend/scss/`)</small>
91
89
  ```scss
92
- @import "../modules/frameworks/bootstrap";
93
- // @import "../modules/frameworks/bulma";
94
- // @import "../modules/frameworks/foundation";
95
- // @import "../modules/frameworks/uikit";
90
+ @import "modules/frameworks/bootstrap";
91
+ // @import "modules/frameworks/bulma";
92
+ // @import "modules/frameworks/foundation";
93
+ // @import "modules/frameworks/uikit";
96
94
  ```
97
95
 
98
96
  ### 2. base.njk <small>(`src/frontend/layouts/`)</small>
package/package.json CHANGED
@@ -1,34 +1,28 @@
1
1
  {
2
2
  "name": "nibula",
3
- "version": "1.5.3",
3
+ "version": "1.6.1",
4
4
  "description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS — with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
5
5
  "keywords": [
6
- "static-site-generator",
7
- "ssg",
8
- "eleventy",
9
6
  "11ty",
7
+ "beginner-friendly",
10
8
  "boilerplate",
11
- "starter",
12
- "scaffolding",
9
+ "bootstrap",
10
+ "bulma",
13
11
  "cli",
14
- "vanilla",
15
- "vanilla-js",
16
- "html",
17
- "css",
18
- "scss",
19
- "sass",
12
+ "deploy-ready",
13
+ "eleventy-template",
14
+ "nginx",
20
15
  "njk",
21
- "nunjucks",
22
- "components",
23
- "backend",
16
+ "nodejs",
24
17
  "php",
25
- "node",
26
- "python",
27
- "beginner-friendly",
28
- "seo",
29
- "sitemap",
30
- "website-template",
31
- "frontend"
18
+ "rest-api",
19
+ "sass",
20
+ "scaffolder",
21
+ "seo-optimization",
22
+ "ssg",
23
+ "static-site-generator",
24
+ "typescript",
25
+ "uikit"
32
26
  ],
33
27
  "author": "Michele Garofalo",
34
28
  "license": "Apache-2.0",
@@ -40,6 +34,7 @@
40
34
  "bugs": {
41
35
  "url": "https://github.com/Rhaastrake/Nibula/issues"
42
36
  },
37
+ "funding": "https://paypal.me/rhaastrake",
43
38
  "outputDir": "out",
44
39
  "bin": {
45
40
  "nib": "bin/nibula.js",
@@ -31,6 +31,7 @@
31
31
  </a>
32
32
  </div>
33
33
  </div>
34
+ <div class="prova">{{ test.prova }}</div>
34
35
  <style>
35
36
  .layout-container {
36
37
  width: 100%;
@@ -0,0 +1,27 @@
1
+ {
2
+ "404": {
3
+ "seo": {
4
+ "title": "404 - Not found",
5
+ "keywords": "",
6
+ "noindex": true,
7
+ "canonical": ""
8
+ },
9
+ "cdn": {
10
+ "css": [],
11
+ "js": []
12
+ }
13
+ },
14
+ "homepage": {
15
+ "seo": {
16
+ "title": "Homepage",
17
+ "description": "Description",
18
+ "keywords": "",
19
+ "noindex": false,
20
+ "canonical": ""
21
+ },
22
+ "cdn": {
23
+ "css": [],
24
+ "js": []
25
+ }
26
+ }
27
+ }
@@ -8,41 +8,14 @@
8
8
  "lang": "en",
9
9
  "author": "Name and surname",
10
10
  "theme": "dark",
11
+ "logo": "/assets/brand/logo.svg",
11
12
  "legal": {
12
13
  "privacy": "",
13
14
  "cookie": "",
14
- "cookieControls": "",
15
15
  "terms": "",
16
16
  "copyright": {
17
17
  "year": "2026",
18
18
  "text": "All rights reserved."
19
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
20
  }
48
21
  }