@janga/norna 0.7.5 → 0.7.7

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 (142) hide show
  1. package/README.md +5 -5
  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 +6 -6
  6. package/docs/configuration.md +3 -2
  7. package/docs/content.md +154 -95
  8. package/docs/design/site-examples-structure.md +1 -1
  9. package/docs/engine-development.md +4 -4
  10. package/docs/getting-started.md +6 -4
  11. package/docs/images-and-metadata.md +112 -20
  12. package/docs/routes.md +52 -33
  13. package/docs/site-structure.md +7 -6
  14. package/docs/theme.md +8 -7
  15. package/docs/typography.md +5 -5
  16. package/examples/dog-gallery/site/content.md +41 -46
  17. package/examples/dog-gallery/site/routes/{dog-care → 010-dog-care}/route-content.md +1 -4
  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 +4 -2
  113. package/scripts/deploy-site.mjs +12 -5
  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/route-model.mjs +17 -0
  119. package/scripts/lib/site-content.mjs +45 -37
  120. package/scripts/show-typography.mjs +18 -10
  121. package/scripts/sync-content-sections.mjs +280 -423
  122. package/scripts/test-content-check.mjs +59 -45
  123. package/scripts/test-content-model-v2.mjs +998 -0
  124. package/scripts/test-engine-commands.mjs +1 -1
  125. package/scripts/test-package-check.mjs +38 -11
  126. package/scripts/test-route-model.mjs +32 -0
  127. package/scripts/test-temporary-visibility.mjs +3 -6
  128. package/src/components/CardList.astro +77 -0
  129. package/src/components/GalleryGrid.astro +22 -12
  130. package/src/components/SiteNavigation.astro +5 -4
  131. package/src/components/SitePage.astro +12 -6
  132. package/src/components/SiteSection.astro +35 -13
  133. package/src/content.config.ts +8 -23
  134. package/src/lib/generatedImages.ts +50 -10
  135. package/src/lib/sectionContent.ts +130 -35
  136. package/src/lib/sitePages.ts +36 -15
  137. package/src/pages/[slug].astro +3 -3
  138. package/src/styles/global.css +187 -0
  139. package/starters/basic/site/content.md +10 -6
  140. package/starters/project/README.md +2 -2
  141. package/starters/project/site/content.md +0 -10
  142. package/starters/project/site/routes/{guide → 010-guide}/route-content.md +0 -6
@@ -1,26 +1,78 @@
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` or `norna-image-carousel` blocks. See
10
+ [Content](content.md#image-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
+ Image 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
+ ````
16
40
 
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.
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
+ ```
20
54
 
21
- ## Generated Variants
55
+ Filenames do not have to be globally unique for the site to be valid.
56
+ Automatic sync only moves files when the filename identifies exactly one source
57
+ candidate within the current page or route.
22
58
 
23
- `npm run norna:images` and `npm run norna:build` generate WebP files in:
59
+ ## Markdown Images
60
+
61
+ Use ordinary Markdown images for external images or static public assets:
62
+
63
+ ```md
64
+ ![External image](https://example.com/image.jpg)
65
+ ![Public asset](/favicon.svg)
66
+ ```
67
+
68
+ Relative local Markdown images such as `![Portrait](portrait.jpg)` are not
69
+ managed by Norna. `content:check` warns about them because Norna cannot
70
+ validate, process, or sync those files through the image pipeline.
71
+
72
+ ## Generated Variants And Static SVG
73
+
74
+ For raster images, `npm run norna:images` and `npm run norna:build` generate
75
+ WebP files in:
24
76
 
25
77
  ```text
26
78
  site/.norna/public/images/generated/
@@ -46,6 +98,19 @@ example-work-1a2b3c4d-1440.webp
46
98
  When a source image changes, the generated URL changes too. This avoids stale
47
99
  browser, CDN, and GitHub Actions cache entries at the old URL.
48
100
 
101
+ SVG files are managed by the same Markdown image blocks, validation and sync
102
+ model, but they are not rasterized and do not get WebP variants. Norna copies
103
+ the SVG source to:
104
+
105
+ ```text
106
+ site/.norna/public/images/original/
107
+ ```
108
+
109
+ The copied SVG filename includes the source hash. If the SVG has a `viewBox` or
110
+ numeric `width` and `height`, Norna stores that ratio in the image manifest so
111
+ the existing image layout can size it like other images. If no intrinsic aspect
112
+ ratio can be read, the SVG is still rendered directly.
113
+
49
114
  ## Manifest
50
115
 
51
116
  The generated image manifest is:
@@ -54,13 +119,15 @@ The generated image manifest is:
54
119
  site/.norna/generated-images.json
55
120
  ```
56
121
 
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.
122
+ It is versioned site state. For raster images, it stores source hashes,
123
+ original dimensions, output version, and generated variant paths. For static
124
+ SVG images, it stores the source hash, copied public path, output version, and
125
+ intrinsic dimensions when they can be read. The image pipeline reuses generated
126
+ or copied files only when the manifest entry matches the current source hash
127
+ and output version, and all expected output files exist.
61
128
 
62
- Generated files under `site/.norna/public/` are build-preparation output
63
- and should not be versioned.
129
+ Generated files under `site/.norna/public/` are build-preparation output and
130
+ should not be versioned.
64
131
 
65
132
  ## Metadata Behavior
66
133
 
@@ -70,10 +137,35 @@ copyright metadata.
70
137
  Generated WebP files are created with ImageMagick using `-strip`, so embedded
71
138
  metadata is not a publication mechanism for generated variants. Keep licensing,
72
139
  credits, copyright notices, alt text, and captions in site-owned files such as
73
- `site/content.md`, `COPYRIGHT.md`, or other site documentation.
140
+ `site/content.md`, route content files, `COPYRIGHT.md`, or other site
141
+ documentation.
74
142
 
75
143
  If a site wants embedded metadata in original source files, that process is
76
- outside the current `norna` command surface.
144
+ outside the current Norna command surface.
145
+
146
+ ## AI-Generated Images
147
+
148
+ For AI-generated images, keep provenance and the generation prompt in a
149
+ Markdown comment near the image block. This makes the image maintainable as
150
+ content: future editors can understand where it came from and regenerate or
151
+ revise it without reverse-engineering the asset.
152
+
153
+ ````md
154
+ <!--
155
+ AI image prompt for workflow.png:
156
+ A clean editorial illustration of ordinary project files becoming a small
157
+ static website, restrained colors, readable composition, no text in the image.
158
+ -->
159
+
160
+ ```norna-image-stack
161
+ - image: workflow.png
162
+ alt: Abstract illustration of project files becoming a website.
163
+ caption: Generated illustration.
164
+ ```
165
+ ````
166
+
167
+ This is a maintenance rule for real editorial images. It is not necessary for
168
+ throwaway test fixtures or examples where the prompt has no practical value.
77
169
 
78
170
  ## GitHub Actions Cache
79
171
 
@@ -83,6 +175,6 @@ The starter workflow caches:
83
175
  site/.norna/public/images/generated
84
176
  ```
85
177
 
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.
178
+ The cache key should include `site/.norna/generated-images.json` so unchanged
179
+ generated variants can be restored during deploy. With a cache miss or a
180
+ changed source hash, variants are rebuilt from source images.
package/docs/routes.md CHANGED
@@ -1,19 +1,56 @@
1
1
  # Routes
2
2
 
3
3
  `site/content.md` is the homepage and builds to `/`. Optional route pages live
4
- under `site/routes/<route-folder>/route-content.md` and build to first-level
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 image
8
+ blocks, and Markdown section model as the homepage. See [Content](content.md)
9
+ for the page and section model.
10
+
11
+ ## Route Directory Format
12
+
13
+ Route directories must use:
14
+
15
+ ```text
16
+ NNN-route-id
17
+ ```
18
+
19
+ `NNN` is a three-digit presentation order from `001` through `999`.
20
+ `route-id` becomes the route identity and URL segment.
21
+
22
+ Valid examples:
23
+
24
+ ```text
25
+ 010-getting-started
26
+ 020-concepts
27
+ 120-api-reference
28
+ ```
29
+
30
+ Invalid examples:
31
+
32
+ ```text
33
+ 10-about
34
+ 000-home
35
+ 010_About
36
+ 010-About
37
+ 010-about-
38
+ 010-about--team
39
+ ```
40
+
41
+ The route id may contain only lowercase `a-z`, numbers, and single hyphens
42
+ between alphanumeric groups. The numeric prefix is not part of the route id or
43
+ URL.
44
+
45
+ Renaming `030-contact/` to `015-contact/` changes navigation order but keeps
46
+ the route id `contact` and URL `/contact/`.
10
47
 
11
48
  ## Route File
12
49
 
13
50
  Add a first-level route by creating:
14
51
 
15
52
  ```text
16
- site/routes/about/route-content.md
53
+ site/routes/010-about/route-content.md
17
54
  ```
18
55
 
19
56
  Minimal route page:
@@ -24,9 +61,6 @@ title: About
24
61
  description: About this site.
25
62
  navigation:
26
63
  label: About
27
- order: 20
28
- sections:
29
- - id: intro
30
64
  ---
31
65
 
32
66
  ## Intro {#intro}
@@ -36,28 +70,14 @@ Text...
36
70
 
37
71
  The example above builds to `/about/`.
38
72
 
39
- ## Slug
40
-
41
- If `slug` is omitted, the route folder name is used. If `slug` is set, it must
42
- use lowercase letters, numbers, and hyphens.
43
-
44
- ```yaml
45
- slug: about-the-work
46
- ```
47
-
48
- Keep the route folder and `slug` aligned unless you intentionally need a
49
- different URL.
50
-
51
- The homepage ignores `slug`.
52
-
53
73
  ## Navigation
54
74
 
55
75
  `navigation` may contain:
56
76
 
57
77
  - `include`: optional boolean. Defaults to `true`.
58
78
  - `label`: optional string. Defaults to `title`.
59
- - `order`: optional integer. Defaults to `0` for the homepage and `100` for
60
- route pages.
79
+
80
+ Route order comes from the directory prefix. The homepage is always first.
61
81
 
62
82
  Current route navigation is intended for small sites. This guidance may change
63
83
  as route support matures, but the present model is:
@@ -71,20 +91,19 @@ as route support matures, but the present model is:
71
91
 
72
92
  ## Route Images
73
93
 
74
- Route images live under the route:
94
+ Route images live under the physical route directory:
75
95
 
76
96
  ```text
77
- site/routes/about/images/intro/image.jpg
97
+ site/routes/010-about/images/intro/image.jpg
78
98
  ```
79
99
 
80
- Image references in route frontmatter still use only the filename:
100
+ Image references in route content still use only the filename:
81
101
 
82
- ```yaml
83
- sections:
84
- - id: intro
85
- gallery:
86
- - image: image.jpg
87
- alt: "Intro image."
102
+ ````md
103
+ ```norna-image-stack
104
+ - image: image.jpg
105
+ alt: Intro image.
88
106
  ```
107
+ ````
89
108
 
90
109
  The image directory segment after `images/` should match the section id.
@@ -15,7 +15,7 @@ site/
15
15
  |-- images/
16
16
  | `-- <section-id>/
17
17
  |-- routes/
18
- | `-- <route-folder>/
18
+ | `-- <NNN-route-id>/
19
19
  | |-- route-content.md
20
20
  | `-- images/
21
21
  | `-- <section-id>/
@@ -41,16 +41,17 @@ 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, image blocks, alt text, and captions.
46
+ See [Content](content.md).
46
47
  - `site/images/<section-id>/`: original source images.
47
- - `site/routes/<route-folder>/route-content.md`: optional route page files.
48
+ - `site/routes/<NNN-route-id>/route-content.md`: optional route page files.
48
49
  See [Routes](routes.md).
49
- - `site/routes/<route-folder>/images/<section-id>/`: original source images
50
+ - `site/routes/<NNN-route-id>/images/<section-id>/`: original source images
50
51
  for that route page.
51
52
  - `site/public/`: site-specific static public files.
52
53
  - `site/.norna/generated-images.json`: generated image manifest used to
53
- decide whether WebP variants can be reused.
54
+ decide whether WebP variants and copied managed SVG files can be reused.
54
55
  - `.github/workflows/deploy.yml`: site-owned GitHub Pages workflow.
55
56
  - `package.json` and `package-lock.json`: scripts and pinned engine dependency.
56
57
 
package/docs/theme.md CHANGED
@@ -73,9 +73,9 @@ navigation:
73
73
  `desktop` / `mobile` values.
74
74
  - `density`: default structural spacing profile. Allowed values are `compact`,
75
75
  `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.
76
+ - `spacing`: optional structural spacing overrides for sections and Norna
77
+ image blocks. Every spacing value accepts either one CSS length for all
78
+ viewports or `desktop` / `mobile` values.
79
79
 
80
80
  Example:
81
81
 
@@ -104,8 +104,8 @@ Spacing keys:
104
104
  - `firstSectionTop`: space above the first section heading.
105
105
  - `sectionGap`: space above each following section.
106
106
  - `finalSectionBottom`: space below the final section.
107
- - `bodyToImages`: space from section body text to image rows.
108
- - `imageGap`: space between image rows.
107
+ - `bodyToImages`: space from section body text to Norna image blocks.
108
+ - `imageGap`: space between stacked images or carousel blocks.
109
109
 
110
110
  Text-near spacing, such as spacing after headings, spacing before Markdown
111
111
  subheadings, paragraph spacing, and caption spacing, belongs to
@@ -206,11 +206,12 @@ presentation:
206
206
  paragraphSpacing: 1em
207
207
  ```
208
208
 
209
- Section-specific presentation belongs under `sections[].presentation`:
209
+ Section-specific presentation belongs under
210
+ `sections.<section-id>.presentation`:
210
211
 
211
212
  ```yaml
212
213
  sections:
213
- - id: intro
214
+ intro:
214
215
  presentation:
215
216
  backgroundColor: "#161616"
216
217
  textColor: "#ffffff"
@@ -132,10 +132,10 @@ typography:
132
132
  ```
133
133
 
134
134
  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.
135
+ for that page. A section-level
136
+ `sections.<section-id>.presentation.typography.preset` changes the typographic
137
+ character for that section. `rhythm` is inherited separately, so changing
138
+ preset does not change spacing unless `rhythm` is also set.
139
139
 
140
140
  If a section sets `typography.rhythm`, that section changes text-near spacing.
141
141
  If a section only sets `typography.overrides`, it keeps the resolved page
@@ -145,7 +145,7 @@ Example section override:
145
145
 
146
146
  ```yaml
147
147
  sections:
148
- - id: intro
148
+ intro:
149
149
  presentation:
150
150
  typography:
151
151
  preset: statement
@@ -3,9 +3,8 @@ title: Local Dog Example
3
3
  description: A small norna example with free dog images.
4
4
  navigation:
5
5
  label: Dog site!
6
- order: 10
7
6
  sections:
8
- - id: black-dogs
7
+ black-dogs:
9
8
  presentation:
10
9
  typography:
11
10
  preset: statement
@@ -13,16 +12,7 @@ sections:
13
12
  headings:
14
13
  h2:
15
14
  size: xlarge
16
- gallery:
17
- - image: photo-of-a-black-dog.jpg
18
- alt: "A black dog sitting outdoors and looking at the camera."
19
- caption:
20
- "Photo of a dog. ContaDeletada2906, CC0 1.0, via Wikimedia Commons."
21
- - image: black-puppy-meadow.png
22
- alt:
23
- "A black puppy standing in a grassy meadow and looking at the camera."
24
- caption: "AI-generated image."
25
- - id: summer-dogs
15
+ summer-dogs:
26
16
  visible:
27
17
  from: "2026-08-01"
28
18
  until: "2026-09-01"
@@ -30,21 +20,11 @@ sections:
30
20
  backgroundColor: "#181818"
31
21
  typography:
32
22
  preset: compact-gallery
33
- gallery: []
34
- - id: brown-dogs
23
+ brown-dogs:
35
24
  presentation:
36
25
  typography:
37
26
  preset: text-forward
38
- gallery:
39
- - image: brown-dog.jpg
40
- alt: "A brown dog looking directly at the camera."
41
- caption: "DogBrown. Frank-3, CC0 1.0, via Wikimedia Commons."
42
- - image: dog-accompanies-master.jpg
43
- alt: "A man walks outdoors with a dog at his side."
44
- caption:
45
- "Dog accompanies his master. Steve Hillebrand, U.S. Fish and Wildlife
46
- Service, public domain."
47
- - id: golden-dogs
27
+ golden-dogs:
48
28
  presentation:
49
29
  typography:
50
30
  overrides:
@@ -56,33 +36,12 @@ sections:
56
36
  body:
57
37
  lineHeight: 1.58
58
38
  paragraphSpacing: 1em
59
- gallery:
60
- - image: golden-retriever.jpg
61
- alt: "A golden retriever standing outdoors."
62
- caption: "Golden Retriever. Ribo, public domain, via Wikimedia Commons."
63
- - image: toller-puppy.jpg
64
- alt:
65
- "A young Nova Scotia Duck Tolling Retriever puppy sitting on grass."
66
- caption:
67
- "Nova Scotia Duck Tolling Retriever puppy. RM, public domain, via
68
- Wikimedia Commons."
69
- - id: white-dogs
39
+ white-dogs:
70
40
  presentation:
71
41
  typography:
72
42
  overrides:
73
43
  caption:
74
44
  spacingBefore: 0.35em
75
- gallery:
76
- - carousel:
77
- - image: white-cute-dog.jpg
78
- alt: "A white dog looking toward the camera."
79
- caption:
80
- "White cute dog. Neal Ziring, public domain, via Wikimedia
81
- Commons."
82
- - image: white-puppy-garden.png
83
- alt: "A fluffy white puppy standing on grass in a garden."
84
- caption: "AI-generated image."
85
- - id: sources
86
45
  ---
87
46
 
88
47
  ## Black Dogs {#black-dogs}
@@ -105,6 +64,15 @@ This `####` heading should follow `typography.headings.h4`.
105
64
  Curabitur blandit tempus porttitor. Donec id elit non mi porta gravida at eget
106
65
  metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
107
66
 
67
+ ```norna-image-stack
68
+ - image: photo-of-a-black-dog.jpg
69
+ alt: A black dog sitting outdoors and looking at the camera.
70
+ caption: Photo of a dog. ContaDeletada2906, CC0 1.0, via Wikimedia Commons.
71
+ - image: black-puppy-meadow.png
72
+ alt: A black puppy standing in a grassy meadow and looking at the camera.
73
+ caption: AI-generated image.
74
+ ```
75
+
108
76
  ## Summer Dogs {#summer-dogs}
109
77
 
110
78
  This temporary section is visible during August 2026. It is here to demonstrate
@@ -127,6 +95,15 @@ Maecenas sed diam eget risus varius blandit sit amet non magna. Morbi leo risus,
127
95
  porta ac consectetur ac, vestibulum at eros. Sed posuere consectetur est at
128
96
  lobortis.
129
97
 
98
+ ```norna-image-stack
99
+ - image: brown-dog.jpg
100
+ alt: A brown dog looking directly at the camera.
101
+ caption: DogBrown. Frank-3, CC0 1.0, via Wikimedia Commons.
102
+ - image: dog-accompanies-master.jpg
103
+ alt: A man walks outdoors with a dog at his side.
104
+ caption: Dog accompanies his master. Steve Hillebrand, U.S. Fish and Wildlife Service, public domain.
105
+ ```
106
+
130
107
  ## Golden Dogs {#golden-dogs}
131
108
 
132
109
  Golden-coated retrievers add a third image section and more scrolling depth.
@@ -142,6 +119,15 @@ Nulla vitae elit libero, a pharetra augue. Integer posuere erat a ante venenatis
142
119
  dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem
143
120
  lacinia quam venenatis vestibulum.
144
121
 
122
+ ```norna-image-stack
123
+ - image: golden-retriever.jpg
124
+ alt: A golden retriever standing outdoors.
125
+ caption: Golden Retriever. Ribo, public domain, via Wikimedia Commons.
126
+ - image: toller-puppy.jpg
127
+ alt: A young Nova Scotia Duck Tolling Retriever puppy sitting on grass.
128
+ caption: Nova Scotia Duck Tolling Retriever puppy. RM, public domain, via Wikimedia Commons.
129
+ ```
130
+
145
131
  ## White Dogs {#white-dogs}
146
132
 
147
133
  A white dog completes the colour-based image sequence.
@@ -153,6 +139,15 @@ Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum
153
139
  dolor sit amet, consectetur adipiscing elit. Donec sed odio dui. Vestibulum id
154
140
  ligula porta felis euismod semper.
155
141
 
142
+ ```norna-image-stack
143
+ - image: white-cute-dog.jpg
144
+ alt: A white dog looking toward the camera.
145
+ caption: White cute dog. Neal Ziring, public domain, via Wikimedia Commons.
146
+ - image: white-puppy-garden.png
147
+ alt: A fluffy white puppy standing on grass in a garden.
148
+ caption: AI-generated image.
149
+ ```
150
+
156
151
  ## Sources {#sources}
157
152
 
158
153
  Most images are sourced from Wikimedia Commons pages that list CC0 or
@@ -3,17 +3,14 @@ title: Dog care
3
3
  description: A small route for testing multi-page norna navigation.
4
4
  navigation:
5
5
  label: Dog care
6
- order: 20
7
6
  sections:
8
- - id: daily-rhythm
7
+ daily-rhythm:
9
8
  presentation:
10
9
  typography:
11
10
  overrides:
12
11
  headings:
13
12
  h2:
14
13
  size: large
15
- - id: calm-training
16
- - id: media-notes
17
14
  ---
18
15
 
19
16
  ## 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}