@janga/norna 0.7.6 → 0.7.8

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 (139) hide show
  1. package/README.md +8 -7
  2. package/astro.config.mjs +76 -0
  3. package/bin/norna-cli.mjs +2 -2
  4. package/docs/README.md +3 -3
  5. package/docs/commands.md +7 -6
  6. package/docs/configuration.md +3 -2
  7. package/docs/content.md +202 -91
  8. package/docs/design/site-examples-structure.md +1 -1
  9. package/docs/engine-development.md +3 -3
  10. package/docs/getting-started.md +50 -8
  11. package/docs/images-and-metadata.md +117 -20
  12. package/docs/routes.md +20 -12
  13. package/docs/site-structure.md +9 -5
  14. package/docs/theme.md +12 -8
  15. package/docs/typography.md +9 -10
  16. package/examples/dog-gallery/site/content.md +38 -77
  17. package/examples/dog-gallery/site/routes/010-dog-care/route-content.md +0 -10
  18. package/fixtures/basic/site/content.md +2 -4
  19. package/fixtures/content-model-v2/.astro/collections/site.schema.json +776 -0
  20. package/fixtures/content-model-v2/.astro/collections/theme.schema.json +668 -0
  21. package/fixtures/content-model-v2/.astro/content-assets.mjs +4 -0
  22. package/fixtures/content-model-v2/.astro/content-modules.mjs +1 -0
  23. package/fixtures/content-model-v2/.astro/content.d.ts +175 -0
  24. package/fixtures/content-model-v2/.astro/data-store.json +1 -0
  25. package/fixtures/content-model-v2/.astro/dev-local.json +8 -0
  26. package/fixtures/content-model-v2/.astro/dev.json +13 -0
  27. package/fixtures/content-model-v2/.astro/dev.log +231 -0
  28. package/fixtures/content-model-v2/.astro/settings.json +5 -0
  29. package/fixtures/content-model-v2/.astro/types.d.ts +2 -0
  30. package/fixtures/content-model-v2/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
  31. package/fixtures/content-model-v2/dist/_astro/SitePage.BbfZub4g.css +1 -0
  32. package/fixtures/content-model-v2/dist/guide/index.html +3 -0
  33. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-480.webp +0 -0
  34. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-768.webp +0 -0
  35. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-971.webp +0 -0
  36. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-480.webp +0 -0
  37. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-768.webp +0 -0
  38. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-971.webp +0 -0
  39. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-480.webp +0 -0
  40. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-768.webp +0 -0
  41. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-971.webp +0 -0
  42. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1080.webp +0 -0
  43. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1440.webp +0 -0
  44. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1600.webp +0 -0
  45. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-480.webp +0 -0
  46. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-768.webp +0 -0
  47. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-480.webp +0 -0
  48. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-768.webp +0 -0
  49. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-960.webp +0 -0
  50. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1080.webp +0 -0
  51. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1440.webp +0 -0
  52. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1600.webp +0 -0
  53. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-480.webp +0 -0
  54. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-768.webp +0 -0
  55. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1080.webp +0 -0
  56. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1440.webp +0 -0
  57. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1600.webp +0 -0
  58. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-480.webp +0 -0
  59. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-768.webp +0 -0
  60. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1080.webp +0 -0
  61. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1440.webp +0 -0
  62. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1600.webp +0 -0
  63. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-480.webp +0 -0
  64. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-768.webp +0 -0
  65. package/fixtures/content-model-v2/dist/index.html +8 -0
  66. package/fixtures/content-model-v2/node_modules/.vite/deps/_metadata.json +80 -0
  67. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  68. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  69. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  70. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  71. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  72. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  73. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  74. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  75. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  76. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  77. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  78. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  79. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js +1389 -0
  80. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js.map +1 -0
  81. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  82. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  83. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  84. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  85. package/fixtures/content-model-v2/node_modules/.vite/deps/package.json +3 -0
  86. package/fixtures/content-model-v2/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  87. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  88. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  89. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  90. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  91. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  92. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  93. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  94. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  95. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  96. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  97. package/fixtures/content-model-v2/site/.norna/generated-images.json +210 -0
  98. package/fixtures/content-model-v2/site/config.mjs +11 -0
  99. package/fixtures/content-model-v2/site/content.md +84 -0
  100. package/fixtures/content-model-v2/site/images/card-section/adopt.svg +9 -0
  101. package/fixtures/content-model-v2/site/images/card-section/foster.svg +12 -0
  102. package/fixtures/content-model-v2/site/images/intro/detail.jpg +0 -0
  103. package/fixtures/content-model-v2/site/images/intro/duplicate.jpg +0 -0
  104. package/fixtures/content-model-v2/site/images/intro/hero.jpg +0 -0
  105. package/fixtures/content-model-v2/site/images/plain/image.jpg +0 -0
  106. package/fixtures/content-model-v2/site/images/timed/slide-one.jpg +0 -0
  107. package/fixtures/content-model-v2/site/images/timed/slide-three.jpg +0 -0
  108. package/fixtures/content-model-v2/site/images/timed/slide-two.jpg +0 -0
  109. package/fixtures/content-model-v2/site/routes/010-guide/images/intro/duplicate.jpg +0 -0
  110. package/fixtures/content-model-v2/site/routes/010-guide/route-content.md +17 -0
  111. package/fixtures/content-model-v2/site/theme.md +9 -0
  112. package/package.json +3 -2
  113. package/scripts/deploy-site.mjs +11 -4
  114. package/scripts/dev-local.mjs +6 -0
  115. package/scripts/generate-images.mjs +132 -56
  116. package/scripts/lib/image-dimensions.mjs +26 -0
  117. package/scripts/lib/norna-markdown-blocks.mjs +643 -0
  118. package/scripts/lib/site-content.mjs +31 -29
  119. package/scripts/show-typography.mjs +17 -9
  120. package/scripts/sync-content-sections.mjs +414 -412
  121. package/scripts/test-content-check.mjs +59 -45
  122. package/scripts/test-content-model-v2.mjs +1230 -0
  123. package/scripts/test-engine-commands.mjs +1 -1
  124. package/scripts/test-package-check.mjs +36 -8
  125. package/scripts/test-temporary-visibility.mjs +3 -6
  126. package/src/components/CardList.astro +77 -0
  127. package/src/components/GalleryGrid.astro +22 -12
  128. package/src/components/SiteNavigation.astro +5 -4
  129. package/src/components/SitePage.astro +9 -2
  130. package/src/components/SiteSection.astro +35 -13
  131. package/src/content.config.ts +4 -17
  132. package/src/lib/generatedImages.ts +50 -10
  133. package/src/lib/sectionContent.ts +130 -35
  134. package/src/styles/global.css +187 -0
  135. package/starters/basic/README.md +4 -2
  136. package/starters/basic/site/content.md +9 -11
  137. package/starters/project/README.md +7 -3
  138. package/starters/project/site/content.md +0 -9
  139. package/starters/project/site/routes/010-guide/route-content.md +0 -5
@@ -16,10 +16,13 @@ Install Playwright Chromium only when you plan to run navigation diagnostics:
16
16
  npx playwright install chromium
17
17
  ```
18
18
 
19
- ## Create A Site Repository
19
+ ## Create A Site
20
20
 
21
- Create the site from the starter first, then install dependencies inside the
22
- new site directory:
21
+ ### Standalone Site
22
+
23
+ Use a standalone setup when the website is its own project. Create the site
24
+ from the starter first, then install dependencies inside the new site
25
+ directory:
23
26
 
24
27
  ```sh
25
28
  cd path/to/your/projects
@@ -55,11 +58,33 @@ The starter contains:
55
58
  - `site/config.mjs`
56
59
  - `site/theme.md`
57
60
  - `site/content.md`
58
- - `site/images/work/.gitkeep`
59
61
  - `site/public/robots.txt`
60
62
 
61
63
  Commit the generated `package-lock.json` after the first install.
62
64
 
65
+ ### Embedded Site
66
+
67
+ Use an embedded setup when a Norna site should live inside an existing Node or
68
+ GitHub project:
69
+
70
+ ```sh
71
+ cd existing-project
72
+ npx @janga/norna@latest init . --type embedded --site-dir presentation
73
+ npm install
74
+ npm run norna:dev
75
+ ```
76
+
77
+ The command looks different because the target is different. `.` means "add
78
+ Norna to the current project" instead of creating a new directory.
79
+ `--site-dir presentation` tells Norna to manage `presentation/` as the site
80
+ directory instead of the default `site/`.
81
+
82
+ Embedded setup keeps the surrounding project structure. It adds namespaced
83
+ `norna:*` scripts so Norna does not take over the project's normal `build`,
84
+ `test`, or deploy scripts.
85
+
86
+ Relevant documentation: [Commands](commands.md), [Site Structure](site-structure.md).
87
+
63
88
  ## First Edits
64
89
 
65
90
  1. Edit `site/config.mjs` for the site's URL, language labels, GitHub
@@ -67,9 +92,11 @@ Commit the generated `package-lock.json` after the first install.
67
92
  2. Edit `site/theme.md` for site-wide layout, layout density, typography
68
93
  rhythm, image sizing, font, typography preset, colors, inline styles, and
69
94
  frame colors, or omit it to use engine defaults.
70
- 3. Edit `site/content.md` for homepage title, description, sections, text,
71
- image rows, page/section presentation overrides, alt text, and captions.
72
- 4. Put source images under `site/images/<section-id>/`.
95
+ 3. Edit `site/content.md` for homepage title, description, Markdown sections,
96
+ text, Norna managed media blocks, page/section presentation overrides, alt
97
+ text, and captions.
98
+ 4. Put source images under `site/images/<section-id>/`, where `<section-id>`
99
+ matches a `## Section {#section-id}` heading in `site/content.md`.
73
100
  5. Add optional route pages under
74
101
  `site/routes/<NNN-route-id>/route-content.md`, for example
75
102
  `site/routes/010-about/route-content.md`.
@@ -78,10 +105,23 @@ Commit the generated `package-lock.json` after the first install.
78
105
  7. Run:
79
106
 
80
107
  ```sh
108
+ npm run norna:config:check
109
+ npm run norna:content:check
110
+ npm run norna:sync
81
111
  npm run norna:check
82
112
  npm run build
83
113
  ```
84
114
 
115
+ `norna:sync` moves misplaced referenced image files when the intended move is
116
+ unambiguous. It can move images between sections and routes, but cross-route
117
+ writes require a clean Git working tree so the operation is easy to roll back.
118
+ Publishing is normally done by committing the site files and pushing them with
119
+ Git; the starter GitHub Pages workflow runs the required checks before
120
+ publishing.
121
+
122
+ Relevant documentation: [Content](content.md), [Theme](theme.md),
123
+ [Typography](typography.md), [Routes](routes.md), [Images And Metadata](images-and-metadata.md).
124
+
85
125
  ## Configure The Public URL
86
126
 
87
127
  For a custom domain or any site published at the web root, use:
@@ -104,13 +144,15 @@ site: {
104
144
  ```
105
145
 
106
146
  After that, content files may still use root-style internal links such as
107
- `/getting-started/` and `/workflow.svg`. Norna applies `site.basePath` during
147
+ `/getting-started/` and `/favicon.svg`. Norna applies `site.basePath` during
108
148
  rendering so the built links work under the GitHub Pages project path.
109
149
 
110
150
  The starter includes a GitHub Pages workflow in `.github/workflows/deploy.yml`.
111
151
  In the GitHub repository settings, configure Pages to build from GitHub
112
152
  Actions.
113
153
 
154
+ Relevant documentation: [Configuration](configuration.md), [Publishing](publishing.md).
155
+
114
156
  Read [Site Structure](site-structure.md), [Content](content.md),
115
157
  [Theme](theme.md), [Typography](typography.md), [Routes](routes.md), and
116
158
  [Configuration](configuration.md) before publishing a real site.
@@ -1,26 +1,80 @@
1
1
  # Images And Metadata
2
2
 
3
- This document describes the generic image pipeline. Site repositories own their
3
+ This document describes Norna's image pipeline. Site repositories own their
4
4
  source images and any copyright or licensing policy for those images.
5
5
 
6
- ## Source Images
6
+ ## Managed Source Images
7
7
 
8
- Source images live under `site/images/<section-id>/` and are referenced from
9
- `site/content.md` by filename only.
8
+ Norna-managed local images are referenced from Markdown with
9
+ `norna-image-stack`, `norna-image-carousel`, or `norna-card-list` blocks. See
10
+ [Content](content.md#managed-media-blocks) for the block syntax.
11
+
12
+ Homepage source images live under:
13
+
14
+ ```text
15
+ site/images/<section-id>/
16
+ ```
17
+
18
+ Route source images live under:
19
+
20
+ ```text
21
+ site/routes/<NNN-route-id>/images/<section-id>/
22
+ ```
10
23
 
11
24
  Supported source extensions:
12
25
 
13
26
  - `.jpg`
14
27
  - `.jpeg`
15
28
  - `.png`
29
+ - `.svg`
30
+
31
+ Managed media block references use only the filename:
32
+
33
+ ````md
34
+ ```norna-image-stack
35
+ - image: portrait.jpg
36
+ alt: Optional alt text.
37
+ caption: Optional caption.
38
+ ```
39
+ ````
40
+
41
+ `alt` and `caption` are optional. If `alt` is omitted, Norna renders an empty
42
+ alt attribute.
43
+
44
+ For generated or edited images, keep provenance near the image in a source-only
45
+ comment. Norna removes these comments from rendered HTML:
46
+
47
+ ```md
48
+ <!-- norna-image-provenance:
49
+ image: portrait.jpg
50
+ source: generated
51
+ prompt: Short prompt or editing note.
52
+ -->
53
+ ```
54
+
55
+ Filenames do not have to be globally unique for the site to be valid.
56
+ Automatic sync only moves misplaced files when the filename identifies exactly
57
+ one source candidate across the site's page and route image roots, and when the
58
+ move will not break another reference. If the move crosses page or route image
59
+ roots, `content:sync` requires a clean Git working tree before writing.
16
60
 
17
- Image filenames must be globally unique under `site/images/`. The content and
18
- image scripts reject duplicate filenames because image rows identify images by
19
- filename only.
61
+ ## Markdown Images
20
62
 
21
- ## Generated Variants
63
+ Use ordinary Markdown images for external images or static public assets:
64
+
65
+ ```md
66
+ ![External image](https://example.com/image.jpg)
67
+ ![Public asset](/favicon.svg)
68
+ ```
22
69
 
23
- `npm run norna:images` and `npm run norna:build` generate WebP files in:
70
+ Relative local Markdown images such as `![Portrait](portrait.jpg)` are not
71
+ managed by Norna. `content:check` warns about them because Norna cannot
72
+ validate, process, or sync those files through the image pipeline.
73
+
74
+ ## Generated Variants And Static SVG
75
+
76
+ For raster images, `npm run norna:images` and `npm run norna:build` generate
77
+ WebP files in:
24
78
 
25
79
  ```text
26
80
  site/.norna/public/images/generated/
@@ -46,6 +100,19 @@ example-work-1a2b3c4d-1440.webp
46
100
  When a source image changes, the generated URL changes too. This avoids stale
47
101
  browser, CDN, and GitHub Actions cache entries at the old URL.
48
102
 
103
+ SVG files are managed by the same Markdown media blocks, validation and sync
104
+ model, but they are not rasterized and do not get WebP variants. Norna copies
105
+ the SVG source to:
106
+
107
+ ```text
108
+ site/.norna/public/images/original/
109
+ ```
110
+
111
+ The copied SVG filename includes the source hash. If the SVG has a `viewBox` or
112
+ numeric `width` and `height`, Norna stores that ratio in the image manifest so
113
+ the existing image layout can size it like other images. If no intrinsic aspect
114
+ ratio can be read, the SVG is still rendered directly.
115
+
49
116
  ## Manifest
50
117
 
51
118
  The generated image manifest is:
@@ -54,13 +121,15 @@ The generated image manifest is:
54
121
  site/.norna/generated-images.json
55
122
  ```
56
123
 
57
- It is versioned site state. It stores source hashes, original dimensions,
58
- output version, and generated variant paths. The image pipeline reuses generated
59
- files only when the manifest entry matches the current source hash and output
60
- version, and all expected variant files exist.
124
+ It is versioned site state. For raster images, it stores source hashes,
125
+ original dimensions, output version, and generated variant paths. For static
126
+ SVG images, it stores the source hash, copied public path, output version, and
127
+ intrinsic dimensions when they can be read. The image pipeline reuses generated
128
+ or copied files only when the manifest entry matches the current source hash
129
+ and output version, and all expected output files exist.
61
130
 
62
- Generated files under `site/.norna/public/` are build-preparation output
63
- and should not be versioned.
131
+ Generated files under `site/.norna/public/` are build-preparation output and
132
+ should not be versioned.
64
133
 
65
134
  ## Metadata Behavior
66
135
 
@@ -70,10 +139,38 @@ copyright metadata.
70
139
  Generated WebP files are created with ImageMagick using `-strip`, so embedded
71
140
  metadata is not a publication mechanism for generated variants. Keep licensing,
72
141
  credits, copyright notices, alt text, and captions in site-owned files such as
73
- `site/content.md`, `COPYRIGHT.md`, or other site documentation.
142
+ `site/content.md`, route content files, `COPYRIGHT.md`, or other site
143
+ documentation.
74
144
 
75
145
  If a site wants embedded metadata in original source files, that process is
76
- outside the current `norna` command surface.
146
+ outside the current Norna command surface.
147
+
148
+ ## AI-Generated Images
149
+
150
+ For AI-generated images, keep provenance and the generation prompt in a
151
+ Markdown comment near the image block. This makes the image maintainable as
152
+ content: future editors can understand where it came from and regenerate or
153
+ revise it without reverse-engineering the asset.
154
+
155
+ ````md
156
+ <!--
157
+ AI image prompt for workflow.png:
158
+ A clean editorial illustration of ordinary project files becoming a small
159
+ static website, restrained colors, readable composition, no text in the image.
160
+ -->
161
+
162
+ ```norna-image-stack
163
+ - image: workflow.png
164
+ alt: Abstract illustration of project files becoming a website.
165
+ caption: Generated illustration.
166
+ ```
167
+ ````
168
+
169
+ The same provenance comment style can be used next to a `norna-image-carousel`
170
+ or `norna-card-list` block when generated images are referenced there.
171
+
172
+ This is a maintenance rule for real editorial images. It is not necessary for
173
+ throwaway test fixtures or examples where the prompt has no practical value.
77
174
 
78
175
  ## GitHub Actions Cache
79
176
 
@@ -83,6 +180,6 @@ The starter workflow caches:
83
180
  site/.norna/public/images/generated
84
181
  ```
85
182
 
86
- The cache key should include `site/.norna/generated-images.json` so
87
- unchanged generated variants can be restored during deploy. With a cache miss or
88
- a changed source hash, variants are rebuilt from source images.
183
+ The cache key should include `site/.norna/generated-images.json` so unchanged
184
+ generated variants can be restored during deploy. With a cache miss or a
185
+ changed source hash, variants are rebuilt from source images.
package/docs/routes.md CHANGED
@@ -4,9 +4,14 @@
4
4
  under `site/routes/<NNN-route-id>/route-content.md` and build to first-level
5
5
  URLs.
6
6
 
7
- Routes use the same page frontmatter, section frontmatter, image rows, and
8
- Markdown section model as the homepage. See [Content](content.md) for the page
9
- and section model.
7
+ Routes use the same page frontmatter, optional section metadata, Norna managed
8
+ media blocks, and Markdown section model as the homepage. See
9
+ [Content](content.md) for the page and section model.
10
+
11
+ Route directories can contain route content, route-local images, and
12
+ route-local presentation overrides in `route-content.md`. They cannot contain
13
+ technical site configuration; technical configuration stays at the selected
14
+ site directory's top-level `config.mjs`.
10
15
 
11
16
  ## Route Directory Format
12
17
 
@@ -61,8 +66,6 @@ title: About
61
66
  description: About this site.
62
67
  navigation:
63
68
  label: About
64
- sections:
65
- - id: intro
66
69
  ---
67
70
 
68
71
  ## Intro {#intro}
@@ -99,14 +102,19 @@ Route images live under the physical route directory:
99
102
  site/routes/010-about/images/intro/image.jpg
100
103
  ```
101
104
 
102
- Image references in route frontmatter still use only the filename:
105
+ Image references in route content still use only the filename:
103
106
 
104
- ```yaml
105
- sections:
106
- - id: intro
107
- gallery:
108
- - image: image.jpg
109
- alt: "Intro image."
107
+ ````md
108
+ ```norna-image-stack
109
+ - image: image.jpg
110
+ alt: Intro image.
110
111
  ```
112
+ ````
111
113
 
112
114
  The image directory segment after `images/` should match the section id.
115
+ If the section id or route folder changes, run `norna content:check` to find
116
+ misplaced images and `norna content:sync` to move unambiguous files into the
117
+ expected section folder. `content:sync` can move images between route image
118
+ roots when the filename is unambiguous across the site and the move will not
119
+ break another reference; these cross-route writes require a clean Git working
120
+ tree.
@@ -41,8 +41,10 @@ Version these files in a site repository:
41
41
  image sizing, font, typography defaults, colors, inline styles, and frame
42
42
  colors.
43
43
  See [Theme](theme.md).
44
- - `site/content.md`: homepage page file with editable content, section
45
- definitions, image rows, alt text, and captions. See [Content](content.md).
44
+ - `site/content.md`: homepage page file with editable content, Markdown
45
+ sections, optional section metadata, managed media blocks, alt text, and
46
+ captions.
47
+ See [Content](content.md).
46
48
  - `site/images/<section-id>/`: original source images.
47
49
  - `site/routes/<NNN-route-id>/route-content.md`: optional route page files.
48
50
  See [Routes](routes.md).
@@ -50,7 +52,7 @@ Version these files in a site repository:
50
52
  for that route page.
51
53
  - `site/public/`: site-specific static public files.
52
54
  - `site/.norna/generated-images.json`: generated image manifest used to
53
- decide whether WebP variants can be reused.
55
+ decide whether WebP variants and copied managed SVG files can be reused.
54
56
  - `.github/workflows/deploy.yml`: site-owned GitHub Pages workflow.
55
57
  - `package.json` and `package-lock.json`: scripts and pinned engine dependency.
56
58
 
@@ -81,8 +83,10 @@ In this repository:
81
83
  - `bin/norna-cli.mjs` dispatches public CLI commands.
82
84
  - `scripts/` contains validation, image, local preview, deploy, and test tools.
83
85
  - `src/` contains the Astro renderer, components, styles, and content schema.
84
- - `starters/basic/` is copied by `norna init <target-dir>` to create a
85
- site repository.
86
+ - `starters/basic/` is the default compact starter copied by
87
+ `norna init <target-dir>`.
88
+ - `starters/project/` is a project-site starter used as a maintained example
89
+ for small project and developer-tool sites.
86
90
  - `fixtures/basic/site/` is used by engine regression tests.
87
91
  - `examples/dog-gallery/site/` is the local visual dog example used by demo
88
92
  builds and navigation diagnostics.
package/docs/theme.md CHANGED
@@ -6,7 +6,8 @@ missing, built-in engine defaults are used.
6
6
 
7
7
  Page-level presentation in `site/content.md` and route page files is always an
8
8
  override on top of `site/theme.md`. Section-level presentation is an override
9
- on top of the resolved page presentation.
9
+ on top of the resolved page presentation. Use those overrides for focused
10
+ exceptions; the normal place for shared visual choices is `site/theme.md`.
10
11
 
11
12
  ## Minimal Theme
12
13
 
@@ -73,9 +74,9 @@ navigation:
73
74
  `desktop` / `mobile` values.
74
75
  - `density`: default structural spacing profile. Allowed values are `compact`,
75
76
  `normal`, and `airy`.
76
- - `spacing`: optional structural spacing overrides for sections and image
77
- rows. Every spacing value accepts either one CSS length for all viewports or
78
- `desktop` / `mobile` values.
77
+ - `spacing`: optional structural spacing overrides for sections and Norna
78
+ managed media blocks. Every spacing value accepts either one CSS length for all
79
+ viewports or `desktop` / `mobile` values.
79
80
 
80
81
  Example:
81
82
 
@@ -104,8 +105,8 @@ Spacing keys:
104
105
  - `firstSectionTop`: space above the first section heading.
105
106
  - `sectionGap`: space above each following section.
106
107
  - `finalSectionBottom`: space below the final section.
107
- - `bodyToImages`: space from section body text to image rows.
108
- - `imageGap`: space between image rows.
108
+ - `bodyToImages`: space from section body text to Norna managed media blocks.
109
+ - `imageGap`: space between stacked images or carousel blocks.
109
110
 
110
111
  Text-near spacing, such as spacing after headings, spacing before Markdown
111
112
  subheadings, paragraph spacing, and caption spacing, belongs to
@@ -206,11 +207,14 @@ presentation:
206
207
  paragraphSpacing: 1em
207
208
  ```
208
209
 
209
- Section-specific presentation belongs under `sections[].presentation`:
210
+ Section-specific presentation belongs under
211
+ `sections.<section-id>.presentation`. Use it when one section genuinely needs
212
+ different presentation; do not use `sections` to define section order or normal
213
+ Markdown structure.
210
214
 
211
215
  ```yaml
212
216
  sections:
213
- - id: intro
217
+ intro:
214
218
  presentation:
215
219
  backgroundColor: "#161616"
216
220
  textColor: "#ffffff"
@@ -5,8 +5,8 @@ presets, text rhythm, and optional overrides.
5
5
 
6
6
  The normal place to choose a site-wide typography preset and rhythm is the
7
7
  top-level `typography` block in `site/theme.md`.
8
- Page and section files may override the theme when a page or section needs a
9
- different presentation.
8
+ Page and section files may override the theme when a focused exception is
9
+ needed, but the normal workflow is to keep typography in `theme.md`.
10
10
 
11
11
  ## Presets
12
12
 
@@ -18,9 +18,8 @@ Available presets:
18
18
  short information blocks.
19
19
  - `text-forward`: more generous body text for pages where longer text carries
20
20
  more of the experience.
21
- - `statement`: tighter, more declarative line-height for introductions, first
22
- sections, and short programmatic statements. Use it sparingly, usually as a
23
- section override.
21
+ - `statement`: tighter, more declarative line-height for short programmatic
22
+ statements. Use it sparingly, usually as a page or section exception.
24
23
 
25
24
  If theme typography is omitted, `quiet-gallery` is used.
26
25
 
@@ -132,10 +131,10 @@ typography:
132
131
  ```
133
132
 
134
133
  A page-level `presentation.typography.preset` changes the typographic character
135
- for that page. A section-level `sections[].presentation.typography.preset`
136
- changes the typographic character for that section. `rhythm` is inherited
137
- separately, so changing preset does not change spacing unless `rhythm` is also
138
- set.
134
+ for that page. A section-level
135
+ `sections.<section-id>.presentation.typography.preset` changes the typographic
136
+ character for that section. `rhythm` is inherited separately, so changing
137
+ preset does not change spacing unless `rhythm` is also set.
139
138
 
140
139
  If a section sets `typography.rhythm`, that section changes text-near spacing.
141
140
  If a section only sets `typography.overrides`, it keeps the resolved page
@@ -145,7 +144,7 @@ Example section override:
145
144
 
146
145
  ```yaml
147
146
  sections:
148
- - id: intro
147
+ intro:
149
148
  presentation:
150
149
  typography:
151
150
  preset: statement
@@ -4,84 +4,10 @@ description: A small norna example with free dog images.
4
4
  navigation:
5
5
  label: Dog site!
6
6
  sections:
7
- - id: black-dogs
8
- presentation:
9
- typography:
10
- preset: statement
11
- overrides:
12
- headings:
13
- h2:
14
- size: xlarge
15
- gallery:
16
- - image: photo-of-a-black-dog.jpg
17
- alt: "A black dog sitting outdoors and looking at the camera."
18
- caption:
19
- "Photo of a dog. ContaDeletada2906, CC0 1.0, via Wikimedia Commons."
20
- - image: black-puppy-meadow.png
21
- alt:
22
- "A black puppy standing in a grassy meadow and looking at the camera."
23
- caption: "AI-generated image."
24
- - id: summer-dogs
7
+ summer-dogs:
25
8
  visible:
26
9
  from: "2026-08-01"
27
10
  until: "2026-09-01"
28
- presentation:
29
- backgroundColor: "#181818"
30
- typography:
31
- preset: compact-gallery
32
- gallery: []
33
- - id: brown-dogs
34
- presentation:
35
- typography:
36
- preset: text-forward
37
- gallery:
38
- - image: brown-dog.jpg
39
- alt: "A brown dog looking directly at the camera."
40
- caption: "DogBrown. Frank-3, CC0 1.0, via Wikimedia Commons."
41
- - image: dog-accompanies-master.jpg
42
- alt: "A man walks outdoors with a dog at his side."
43
- caption:
44
- "Dog accompanies his master. Steve Hillebrand, U.S. Fish and Wildlife
45
- Service, public domain."
46
- - id: golden-dogs
47
- presentation:
48
- typography:
49
- overrides:
50
- headings:
51
- h2:
52
- size: large
53
- h3:
54
- size: medium
55
- body:
56
- lineHeight: 1.58
57
- paragraphSpacing: 1em
58
- gallery:
59
- - image: golden-retriever.jpg
60
- alt: "A golden retriever standing outdoors."
61
- caption: "Golden Retriever. Ribo, public domain, via Wikimedia Commons."
62
- - image: toller-puppy.jpg
63
- alt:
64
- "A young Nova Scotia Duck Tolling Retriever puppy sitting on grass."
65
- caption:
66
- "Nova Scotia Duck Tolling Retriever puppy. RM, public domain, via
67
- Wikimedia Commons."
68
- - id: white-dogs
69
- presentation:
70
- typography:
71
- overrides:
72
- caption:
73
- spacingBefore: 0.35em
74
- gallery:
75
- - carousel:
76
- - image: white-cute-dog.jpg
77
- alt: "A white dog looking toward the camera."
78
- caption:
79
- "White cute dog. Neal Ziring, public domain, via Wikimedia
80
- Commons."
81
- - image: white-puppy-garden.png
82
- alt: "A fluffy white puppy standing on grass in a garden."
83
- caption: "AI-generated image."
84
- - id: sources
85
11
  ---
86
12
 
87
13
  ## Black Dogs {#black-dogs}
@@ -104,6 +30,15 @@ This `####` heading should follow `typography.headings.h4`.
104
30
  Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget
105
31
  metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
106
32
 
33
+ ```norna-image-stack
34
+ - image: photo-of-a-black-dog.jpg
35
+ alt: A black dog sitting outdoors and looking at the camera.
36
+ caption: Photo of a dog. ContaDeletada2906, CC0 1.0, via Wikimedia Commons.
37
+ - image: black-puppy-meadow.png
38
+ alt: A black puppy standing in a grassy meadow and looking at the camera.
39
+ caption: AI-generated image.
40
+ ```
41
+
107
42
  ## Summer Dogs {#summer-dogs}
108
43
 
109
44
  This temporary section is visible during August 2026. It is here to demonstrate
@@ -126,6 +61,15 @@ Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus,
126
61
  porta ac consectetur ac, vestibulum at eros. Sed posuere consectetur est at
127
62
  lobortis.
128
63
 
64
+ ```norna-image-stack
65
+ - image: brown-dog.jpg
66
+ alt: A brown dog looking directly at the camera.
67
+ caption: DogBrown. Frank-3, CC0 1.0, via Wikimedia Commons.
68
+ - image: dog-accompanies-master.jpg
69
+ alt: A man walks outdoors with a dog at his side.
70
+ caption: Dog accompanies his master. Steve Hillebrand, U.S. Fish and Wildlife Service, public domain.
71
+ ```
72
+
129
73
  ## Golden Dogs {#golden-dogs}
130
74
 
131
75
  Golden-coated retrievers add a third image section and more scrolling depth.
@@ -134,13 +78,21 @@ Cras justo odio, dapibus ac facilisis in, egestas eget quam.
134
78
 
135
79
  ### Training note
136
80
 
137
- This section overrides `headings.h2` and `headings.h3`, while keeping the
138
- site-wide `headings.h4` style.
81
+ This `###` heading follows the site-wide `headings.h3` style.
139
82
 
140
83
  Nulla vitae elit libero, a pharetra augue. Integer posuere erat a ante venenatis
141
84
  dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem
142
85
  lacinia quam venenatis vestibulum.
143
86
 
87
+ ```norna-image-stack
88
+ - image: golden-retriever.jpg
89
+ alt: A golden retriever standing outdoors.
90
+ caption: Golden Retriever. Ribo, public domain, via Wikimedia Commons.
91
+ - image: toller-puppy.jpg
92
+ alt: A young Nova Scotia Duck Tolling Retriever puppy sitting on grass.
93
+ caption: Nova Scotia Duck Tolling Retriever puppy. RM, public domain, via Wikimedia Commons.
94
+ ```
95
+
144
96
  ## White Dogs {#white-dogs}
145
97
 
146
98
  A white dog completes the colour-based image sequence.
@@ -152,6 +104,15 @@ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum
152
104
  dolor sit amet, consectetur adipiscing elit. Donec sed odio dui. Vestibulum id
153
105
  ligula porta felis euismod semper.
154
106
 
107
+ ```norna-image-stack
108
+ - image: white-cute-dog.jpg
109
+ alt: A white dog looking toward the camera.
110
+ caption: White cute dog. Neal Ziring, public domain, via Wikimedia Commons.
111
+ - image: white-puppy-garden.png
112
+ alt: A fluffy white puppy standing on grass in a garden.
113
+ caption: AI-generated image.
114
+ ```
115
+
155
116
  ## Sources {#sources}
156
117
 
157
118
  Most images are sourced from Wikimedia Commons pages that list CC0 or
@@ -3,16 +3,6 @@ title: Dog care
3
3
  description: A small route for testing multi-page norna navigation.
4
4
  navigation:
5
5
  label: Dog care
6
- sections:
7
- - id: daily-rhythm
8
- presentation:
9
- typography:
10
- overrides:
11
- headings:
12
- h2:
13
- size: large
14
- - id: calm-training
15
- - id: media-notes
16
6
  ---
17
7
 
18
8
  ## Daily rhythm {#daily-rhythm}
@@ -2,10 +2,8 @@
2
2
  title: Fixture Site
3
3
  description: Minimal cli-gallery fixture site.
4
4
  sections:
5
- - id: intro
6
- gallery: []
7
- - id: work
8
- gallery: []
5
+ intro: {}
6
+ work: {}
9
7
 
10
8
  ---
11
9
  ## Intro {#intro}