@janga/norna 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/docs/README.md +3 -3
  2. package/docs/commands.md +2 -2
  3. package/docs/configuration.md +2 -1
  4. package/docs/design/command-organization.md +4 -3
  5. package/docs/design/site-examples-structure.md +4 -3
  6. package/docs/getting-started.md +8 -7
  7. package/docs/theme.md +27 -43
  8. package/docs/typography.md +47 -31
  9. package/examples/dog-gallery/site/content.md +1 -1
  10. package/examples/dog-gallery/site/theme.md +12 -14
  11. package/package.json +1 -1
  12. package/scripts/check-config.mjs +1 -0
  13. package/scripts/lib/project-config.mjs +87 -32
  14. package/scripts/lib/site-content.mjs +4 -4
  15. package/scripts/lib/typography.mjs +79 -39
  16. package/scripts/show-typography.mjs +75 -11
  17. package/scripts/test-engine-commands.mjs +18 -0
  18. package/scripts/test-package-check.mjs +19 -3
  19. package/src/components/SiteSection.astro +82 -20
  20. package/src/content.config.ts +11 -9
  21. package/src/layouts/BaseLayout.astro +0 -6
  22. package/src/styles/global.css +44 -17
  23. package/starters/basic/README.md +4 -3
  24. package/starters/basic/site/theme.md +19 -46
  25. package/starters/project/README.md +7 -7
  26. package/starters/project/package-lock.json +4 -4
  27. package/starters/project/package.json +1 -1
  28. package/starters/project/site/content.md +86 -46
  29. package/starters/project/site/routes/guide/route-content.md +52 -25
  30. package/starters/project/site/theme.md +29 -1
  31. package/examples/dog-gallery/.astro/collections/site.schema.json +0 -802
  32. package/examples/dog-gallery/.astro/collections/theme.schema.json +0 -714
  33. package/examples/dog-gallery/.astro/content-assets.mjs +0 -4
  34. package/examples/dog-gallery/.astro/content-modules.mjs +0 -1
  35. package/examples/dog-gallery/.astro/content.d.ts +0 -175
  36. package/examples/dog-gallery/.astro/data-store.json +0 -1
  37. package/examples/dog-gallery/.astro/dev-local.json +0 -8
  38. package/examples/dog-gallery/.astro/dev.json +0 -13
  39. package/examples/dog-gallery/.astro/dev.log +0 -49
  40. package/examples/dog-gallery/.astro/settings.json +0 -5
  41. package/examples/dog-gallery/.astro/types.d.ts +0 -2
  42. package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +0 -1
  43. package/examples/dog-gallery/dist/_astro/SitePage.BFHZm5UG.css +0 -1
  44. package/examples/dog-gallery/dist/dog-care/index.html +0 -15
  45. package/examples/dog-gallery/dist/favicon.svg +0 -7
  46. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
  47. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
  48. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
  49. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
  50. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
  51. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
  52. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
  53. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
  54. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
  55. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
  56. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
  57. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
  58. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
  59. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
  60. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
  61. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
  62. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
  63. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
  64. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
  65. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
  66. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
  67. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
  68. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
  69. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
  70. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
  71. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
  72. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
  73. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
  74. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
  75. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
  76. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
  77. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
  78. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
  79. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
  80. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
  81. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
  82. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
  83. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
  84. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
  85. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
  86. package/examples/dog-gallery/dist/index.html +0 -43
  87. package/examples/dog-gallery/dist/robots.txt +0 -2
package/docs/README.md CHANGED
@@ -41,9 +41,9 @@ For a quick reference:
41
41
  - [Content](content.md) describes page frontmatter, section frontmatter,
42
42
  image rows, carousels, temporary sections, Markdown section matching, and
43
43
  content validation.
44
- - [Theme](theme.md) describes site-wide layout, spacing, image sizing, font,
45
- typography, presentation colors, frame colors, and inline styles.
46
- - [Typography](typography.md) describes presets, roles, overrides,
44
+ - [Theme](theme.md) describes site-wide layout, layout density, image sizing,
45
+ font, typography rhythm, presentation colors, frame colors, and inline styles.
46
+ - [Typography](typography.md) describes presets, rhythms, roles, overrides,
47
47
  inheritance, and inspection commands.
48
48
  - [Routes](routes.md) describes route files, slugs, route navigation, and
49
49
  route image directories.
package/docs/commands.md CHANGED
@@ -105,8 +105,8 @@ should normally mean the repository's complete publishable artifact, while
105
105
  runs `astro sync`.
106
106
  - `content:sync` / `norna:sync`: rewrites Markdown section order and moves misplaced referenced
107
107
  image files after confirmation.
108
- - `typography presets`: prints the exact built-in values for every typography
109
- preset.
108
+ - `typography presets`: prints the exact built-in values for typography
109
+ presets and rhythms.
110
110
  - `typography show`: prints the selected site's resolved typography for the
111
111
  theme, every page route, and every section. Each value includes its source,
112
112
  and inherited page or section values are marked with `inherited: true`.
@@ -66,11 +66,12 @@ Use [Theme](theme.md) for:
66
66
 
67
67
  - `layout.pageWidth`
68
68
  - `layout.gutter`
69
+ - `layout.density`
69
70
  - `gallery.width`
70
71
  - `gallery.maxAvailableWidthPercent`
71
72
  - `gallery.maxAvailableHeightPercent`
72
73
  - `typography.fontFamily`
73
- - typography presets and overrides
74
+ - typography presets, rhythm, and overrides
74
75
  - presentation colors, inline styles, and frame colors
75
76
 
76
77
  ## Navigation
@@ -275,9 +275,10 @@ npm run norna:typography:presets
275
275
  npm run norna:typography:show
276
276
  ```
277
277
 
278
- `norna:typography:presets` shows the built-in typography presets from the
279
- installed engine. `norna:typography:show` shows the effective typography for
280
- the selected site after presets and overrides have been applied.
278
+ `norna:typography:presets` shows the built-in typography presets and rhythms
279
+ from the installed engine. `norna:typography:show` shows the effective
280
+ typography for the selected site after presets, rhythms, and overrides have
281
+ been applied.
281
282
 
282
283
  ### Correct Content And Configuration
283
284
 
@@ -26,8 +26,8 @@ site repository. It should not be a showcase for every feature.
26
26
  An example site is a runnable site that demonstrates one or more features.
27
27
 
28
28
  Examples can be richer than the starter. They may show routes, navigation,
29
- typography presets, galleries, image carousels, inline styles, and site-specific
30
- configuration choices.
29
+ typography presets, typography rhythms, galleries, image carousels, inline
30
+ styles, and site-specific configuration choices.
31
31
 
32
32
  ### Documentation Site
33
33
 
@@ -72,7 +72,8 @@ they demonstrate a distinct feature or workflow.
72
72
  navigation diagnostic target.
73
73
  - `routes-demo/`: focused route/navigation example if the dog example becomes
74
74
  too broad.
75
- - `typography-demo/`: focused typography preset and override example if needed.
75
+ - `typography-demo/`: focused typography preset, rhythm, and override example
76
+ if needed.
76
77
 
77
78
  The repository-local `site/` directory is reserved for the documentation site.
78
79
  `docs/` should remain for reference documentation. It should link to the
@@ -64,9 +64,9 @@ Commit the generated `package-lock.json` after the first install.
64
64
 
65
65
  1. Edit `site/config.mjs` for the site's URL, language labels, GitHub
66
66
  repository, footer, and deploy settings.
67
- 2. Edit `site/theme.md` for site-wide layout, vertical spacing, image sizing,
68
- font, typography preset, colors, inline styles, and frame colors, or omit it
69
- to use engine defaults.
67
+ 2. Edit `site/theme.md` for site-wide layout, layout density, typography
68
+ rhythm, image sizing, font, typography preset, colors, inline styles, and
69
+ frame colors, or omit it to use engine defaults.
70
70
  3. Edit `site/content.md` for homepage title, description, sections, text,
71
71
  image rows, page/section presentation overrides, alt text, and captions.
72
72
  4. Put source images under `site/images/<section-id>/`.
@@ -114,12 +114,13 @@ Read [Site Structure](site-structure.md), [Content](content.md),
114
114
  [Theme](theme.md), [Typography](typography.md), [Routes](routes.md), and
115
115
  [Configuration](configuration.md) before publishing a real site.
116
116
 
117
- To change the site's maximum page width, side gutters, vertical spacing, image
118
- width, image height limits, font, colors, or site-wide typography preset, edit
119
- `site/theme.md`. See [Theme](theme.md) for the accepted shape.
117
+ To change the site's maximum page width, side gutters, layout density,
118
+ typography rhythm, image width, image height limits, font, colors, or
119
+ site-wide typography preset, edit `site/theme.md`. See [Theme](theme.md) for
120
+ the accepted shape.
120
121
 
121
122
  To change one page or section, add a focused `presentation.typography`
122
123
  override in `site/content.md`. See [Typography](typography.md). Use
123
- `norna typography presets` to inspect the installed preset values and
124
+ `norna typography presets` to inspect the installed preset and rhythm values and
124
125
  `norna typography show` to inspect the resolved typography for the selected
125
126
  site.
package/docs/theme.md CHANGED
@@ -15,35 +15,11 @@ on top of the resolved page presentation.
15
15
  navigation:
16
16
  brand: Example Site
17
17
  layout:
18
+ density: normal
18
19
  pageWidth: 1180px
19
20
  gutter:
20
21
  desktop: clamp(1.25rem, 4vw, 3rem)
21
22
  mobile: 1rem
22
- spacing:
23
- firstSectionTop:
24
- desktop: clamp(1.875rem, 3vw, 2.75rem)
25
- mobile: 1.375rem
26
- sectionGap:
27
- desktop: clamp(2.55rem, 4.2vw, 3.9rem)
28
- mobile: 2.25rem
29
- finalSectionBottom:
30
- desktop: clamp(2.55rem, 4.2vw, 3.9rem)
31
- mobile: 2.25rem
32
- sectionHeadingToBody:
33
- desktop: clamp(0.5625rem, 1.25vw, 0.75rem)
34
- mobile: 0.4375rem
35
- bodyToImages:
36
- desktop: clamp(1.25rem, 2.5vw, 2rem)
37
- mobile: 1.25rem
38
- imageGap:
39
- desktop: clamp(1.5rem, 3.5vw, 2.75rem)
40
- mobile: 2.75rem
41
- subheadingTop:
42
- desktop: 2rem
43
- mobile: 1.6rem
44
- subheadingRuleTop:
45
- desktop: 1rem
46
- mobile: 0.8rem
47
23
  gallery:
48
24
  width: 900px
49
25
  maxAvailableWidthPercent:
@@ -55,6 +31,7 @@ gallery:
55
31
  typography:
56
32
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
57
33
  preset: quiet-gallery
34
+ rhythm: normal
58
35
  presentation:
59
36
  backgroundColor: "#000000"
60
37
  textColor: "#f7f4ee"
@@ -94,14 +71,17 @@ navigation:
94
71
  - `gutter`: side margin removed from the viewport before available content
95
72
  width is calculated. Use either one CSS length for all viewports or
96
73
  `desktop` / `mobile` values.
97
- - `spacing`: vertical rhythm for sections, text, image rows, and Markdown
98
- subheadings. Every spacing value accepts either one CSS length for all
99
- viewports or `desktop` / `mobile` values.
74
+ - `density`: default structural spacing profile. Allowed values are `compact`,
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.
100
79
 
101
80
  Example:
102
81
 
103
82
  ```yaml
104
83
  layout:
84
+ density: normal
105
85
  pageWidth: 1180px
106
86
  gutter:
107
87
  desktop: clamp(1.25rem, 4vw, 3rem)
@@ -111,28 +91,25 @@ layout:
111
91
  desktop: clamp(1.875rem, 3vw, 2.75rem)
112
92
  mobile: 1.375rem
113
93
  sectionGap:
114
- desktop: clamp(2.55rem, 4.2vw, 3.9rem)
115
- mobile: 2.25rem
116
- subheadingTop:
117
- desktop: 2rem
118
- mobile: 1.6rem
94
+ desktop: clamp(1.4rem, 3vw, 2.75rem)
95
+ mobile: 1.5rem
119
96
  ```
120
97
 
121
98
  If omitted, Norna uses `1180px` for `pageWidth`, desktop
122
- `clamp(1.25rem, 4vw, 3rem)` for `gutter`, mobile `1rem`, and the spacing
123
- defaults shown in the minimal theme example.
99
+ `clamp(1.25rem, 4vw, 3rem)` for `gutter`, mobile `1rem`, and `normal` layout
100
+ density.
124
101
 
125
102
  Spacing keys:
126
103
 
127
104
  - `firstSectionTop`: space above the first section heading.
128
105
  - `sectionGap`: space above each following section.
129
106
  - `finalSectionBottom`: space below the final section.
130
- - `sectionHeadingToBody`: space from a section heading to its body text.
131
107
  - `bodyToImages`: space from section body text to image rows.
132
108
  - `imageGap`: space between image rows.
133
- - `subheadingTop`: space above Markdown `###` subheadings inside body text.
134
- - `subheadingRuleTop`: space between the rule above a Markdown `###`
135
- subheading and the subheading text.
109
+
110
+ Text-near spacing, such as spacing after headings, spacing before Markdown
111
+ subheadings, paragraph spacing, and caption spacing, belongs to
112
+ `typography.rhythm` and typography overrides.
136
113
 
137
114
  ## Image Sizing
138
115
 
@@ -166,6 +143,8 @@ Top-level `typography` is the site-wide typographic base. It supports:
166
143
 
167
144
  - `fontFamily`: global CSS font-family stack.
168
145
  - `preset`: built-in typography preset.
146
+ - `rhythm`: text-near spacing profile. Allowed values are `compact`,
147
+ `normal`, and `airy`.
169
148
  - `overrides`: focused changes to preset values.
170
149
 
171
150
  Example:
@@ -174,19 +153,23 @@ Example:
174
153
  typography:
175
154
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
176
155
  preset: quiet-gallery
156
+ rhythm: normal
177
157
  overrides:
178
158
  headings:
179
159
  h2:
180
160
  size: medium
161
+ spacingAfter: 0.55em
181
162
  h3:
182
- size: small
163
+ size: medium
164
+ spacingBefore: 1.5em
165
+ spacingAfter: 0.5em
183
166
  body:
184
167
  lineHeight: 1.55
185
168
  ```
186
169
 
187
- Use `norna typography presets` to inspect built-in preset values and
188
- `norna typography show` to inspect the resolved typography for the selected
189
- site. See [Typography](typography.md).
170
+ Use `norna typography presets` to inspect built-in preset and rhythm values,
171
+ and `norna typography show` to inspect the resolved typography for the
172
+ selected site. See [Typography](typography.md).
190
173
 
191
174
  ## Presentation
192
175
 
@@ -218,6 +201,7 @@ presentation:
218
201
  headings:
219
202
  h2:
220
203
  size: large
204
+ spacingAfter: 0.6em
221
205
  body:
222
206
  paragraphSpacing: 1em
223
207
  ```
@@ -1,10 +1,10 @@
1
1
  # Typography
2
2
 
3
3
  `norna` typography is configured in `site/theme.md` through a site-wide font,
4
- presets, and optional overrides.
4
+ presets, text rhythm, and optional overrides.
5
5
 
6
- The normal place to choose a site-wide typography preset is the top-level
7
- `typography` block in `site/theme.md`.
6
+ The normal place to choose a site-wide typography preset and rhythm is the
7
+ top-level `typography` block in `site/theme.md`.
8
8
  Page and section files may override the theme when a page or section needs a
9
9
  different presentation.
10
10
 
@@ -18,25 +18,31 @@ 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`: stronger type for introductions, first sections, and short
22
- programmatic statements. Use it sparingly, usually as a section override.
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.
23
24
 
24
25
  If theme typography is omitted, `quiet-gallery` is used.
25
26
 
26
- Presets define `headings.h1` through `headings.h4`, `body`, and `caption`.
27
- They differ primarily through heading scale, alignment, line height, paragraph
28
- spacing, caption treatment, and intended use. Choose larger or smaller type
29
- with focused overrides when a specific page or section needs it.
27
+ Presets define alignment, size, and line height for `headings.h1` through
28
+ `headings.h4`, `body`, and `caption`. Built-in presets use `medium` as the
29
+ default size for every text role. Visual heading hierarchy comes from the
30
+ Markdown heading level, so `h1` is larger than `h2`, `h2` is larger than `h3`,
31
+ and so on.
30
32
 
31
- Use this command to inspect the exact preset values shipped with the installed
32
- engine:
33
+ `rhythm` defines text-near spacing for headings, paragraphs, and captions.
34
+ Available rhythms are `compact`, `normal`, and `airy`. Built-in rhythm values
35
+ use `em` so spacing follows the rendered text size.
36
+
37
+ Use this command to inspect the exact preset and rhythm values shipped with
38
+ the installed engine:
33
39
 
34
40
  ```sh
35
41
  norna typography presets
36
42
  ```
37
43
 
38
44
  Use this command to inspect the effective values for the selected site after
39
- presets and overrides have been applied:
45
+ presets, rhythms, and overrides have been applied:
40
46
 
41
47
  ```sh
42
48
  norna typography show
@@ -52,14 +58,20 @@ also marked with `inherited: true`.
52
58
  typography:
53
59
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
54
60
  preset: quiet-gallery
61
+ rhythm: normal
55
62
  overrides:
56
63
  headings:
57
64
  h2:
58
65
  size: medium
66
+ spacingAfter: 0.55em
59
67
  h3:
60
- size: small
68
+ size: medium
69
+ spacingBefore: 1.5em
70
+ spacingAfter: 0.5em
61
71
  body:
62
72
  paragraphSpacing: 0.8em
73
+ caption:
74
+ spacingBefore: 0.5em
63
75
  ```
64
76
 
65
77
  The typographic roles are:
@@ -84,27 +96,28 @@ align:
84
96
  Allowed size values are `small`, `medium`, `large`, and `xlarge`. `medium` is
85
97
  the normal reading size. Use `small` for quieter supporting text, and use
86
98
  `large` or `xlarge` only when a page or section needs stronger emphasis.
87
- Headings use their own scale, but follow the same principle: `medium` is the
88
- normal section heading size, not a hero size. Norna may render the first
89
- section heading as an HTML `h1` for document structure, but its visual
90
- typography still follows the Markdown level the user wrote: `##` uses
91
- `headings.h2`.
99
+ Each heading level uses its own scale, but follows the same principle:
100
+ `medium` is the normal size for that level. Norna may render the first section
101
+ heading as an HTML `h1` for document structure, but its visual typography still
102
+ follows the Markdown level the user wrote: `##` uses `headings.h2`.
92
103
 
93
- `lineHeight` is a unitless number from `1` through `3`. `spacing` and
94
- `paragraphSpacing` are CSS lengths such as `0`, `0.8em`, `1rem`, or `12px`.
104
+ `lineHeight` is a unitless number from `1` through `3`. `spacingBefore`,
105
+ `spacingAfter`, and `paragraphSpacing` are CSS lengths such as `0`, `0.8em`,
106
+ `1rem`, or `12px`. Use `em` for spacing that should track the text size.
95
107
 
96
108
  Supported override fields:
97
109
 
98
110
  - `headings.h1.align`, `headings.h1.size`, `headings.h1.lineHeight`,
99
- `headings.h1.spacing`
111
+ `headings.h1.spacingBefore`, `headings.h1.spacingAfter`
100
112
  - `headings.h2.align`, `headings.h2.size`, `headings.h2.lineHeight`,
101
- `headings.h2.spacing`
113
+ `headings.h2.spacingBefore`, `headings.h2.spacingAfter`
102
114
  - `headings.h3.align`, `headings.h3.size`, `headings.h3.lineHeight`,
103
- `headings.h3.spacing`
115
+ `headings.h3.spacingBefore`, `headings.h3.spacingAfter`
104
116
  - `headings.h4.align`, `headings.h4.size`, `headings.h4.lineHeight`,
105
- `headings.h4.spacing`
117
+ `headings.h4.spacingBefore`, `headings.h4.spacingAfter`
106
118
  - `body.align`, `body.size`, `body.lineHeight`, `body.paragraphSpacing`
107
- - `caption.align`, `caption.size`, `caption.lineHeight`, `caption.spacing`
119
+ - `caption.align`, `caption.size`, `caption.lineHeight`,
120
+ `caption.spacingBefore`
108
121
 
109
122
  ## Inheritance
110
123
 
@@ -115,15 +128,18 @@ Theme typography is the site-wide base:
115
128
  typography:
116
129
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
117
130
  preset: quiet-gallery
131
+ rhythm: normal
118
132
  ```
119
133
 
120
- A page-level `presentation.typography.preset` changes the typographic base for
121
- that page. A section-level `sections[].presentation.typography.preset` changes
122
- the typographic base for that section.
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.
123
139
 
124
- If a section sets `typography.preset`, that section starts from that preset. If
125
- a section only sets `typography.overrides`, it keeps the resolved page preset
126
- and changes only the specified values.
140
+ If a section sets `typography.rhythm`, that section changes text-near spacing.
141
+ If a section only sets `typography.overrides`, it keeps the resolved page
142
+ preset and rhythm and changes only the specified values.
127
143
 
128
144
  Example section override:
129
145
 
@@ -71,7 +71,7 @@ sections:
71
71
  typography:
72
72
  overrides:
73
73
  caption:
74
- spacing: 0.35em
74
+ spacingBefore: 0.35em
75
75
  gallery:
76
76
  - carousel:
77
77
  - image: white-cute-dog.jpg
@@ -7,6 +7,7 @@
7
7
  # navigation:
8
8
  # brand: site-wide brand/home text; defaults to the homepage title
9
9
  # layout:
10
+ # density: compact | normal | airy
10
11
  # pageWidth: CSS length, e.g. 2048px
11
12
  # gutter:
12
13
  # desktop: CSS length or clamp(), e.g. clamp(1.25rem, 4vw, 3rem)
@@ -18,9 +19,6 @@
18
19
  # sectionGap:
19
20
  # desktop: CSS length or clamp(), e.g. clamp(1.8rem, 3vw, 2.75rem)
20
21
  # mobile: CSS length, e.g. 1.65rem
21
- # subheadingTop:
22
- # desktop: CSS length, e.g. 1.35rem
23
- # mobile: CSS length, e.g. 1.1rem
24
22
  # gallery:
25
23
  # width: CSS length, e.g. 55vw
26
24
  # maxAvailableWidthPercent:
@@ -32,14 +30,19 @@
32
30
  # typography:
33
31
  # fontFamily: CSS font-family stack
34
32
  # preset: quiet-gallery | compact-gallery | text-forward | statement
33
+ # rhythm: compact | normal | airy
35
34
  # overrides:
36
35
  # headings:
37
36
  # h2:
38
37
  # size: small | medium | large | xlarge
39
38
  # h3:
40
39
  # size: small | medium | large | xlarge
40
+ # spacingBefore: CSS length, e.g. 1.2em
41
+ # spacingAfter: CSS length, e.g. 0.45em
41
42
  # h4:
42
43
  # size: small | medium | large | xlarge
44
+ # spacingBefore: CSS length, e.g. 0.9em
45
+ # spacingAfter: CSS length, e.g. 0.35em
43
46
  # presentation:
44
47
  # backgroundColor: quoted hex color, e.g. "#000000"
45
48
  # textColor: quoted hex color, e.g. "#f7f4ee"
@@ -53,6 +56,7 @@
53
56
  # textColor: quoted hex color
54
57
  # norna:end theme-help
55
58
  layout:
59
+ density: compact
56
60
  pageWidth: 2048px
57
61
  gutter:
58
62
  desktop: clamp(1.25rem, 4vw, 3rem)
@@ -67,21 +71,12 @@ layout:
67
71
  finalSectionBottom:
68
72
  desktop: clamp(1.8rem, 3vw, 2.75rem)
69
73
  mobile: 1.65rem
70
- sectionHeadingToBody:
71
- desktop: 0.55rem
72
- mobile: 0.45rem
73
74
  bodyToImages:
74
75
  desktop: 1.25rem
75
76
  mobile: 1rem
76
77
  imageGap:
77
78
  desktop: clamp(1.25rem, 2.8vw, 2rem)
78
79
  mobile: 1.8rem
79
- subheadingTop:
80
- desktop: 1.35rem
81
- mobile: 1.1rem
82
- subheadingRuleTop:
83
- desktop: 0.65rem
84
- mobile: 0.55rem
85
80
  gallery:
86
81
  width: 55vw
87
82
  maxAvailableWidthPercent:
@@ -93,14 +88,17 @@ gallery:
93
88
  typography:
94
89
  fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
95
90
  preset: quiet-gallery
91
+ rhythm: compact
96
92
  overrides:
97
93
  headings:
98
94
  h3:
99
95
  size: medium
100
- spacing: 0.7rem
96
+ spacingBefore: 1.2em
97
+ spacingAfter: 0.45em
101
98
  h4:
102
99
  size: small
103
- spacing: 0.55rem
100
+ spacingBefore: 0.9em
101
+ spacingAfter: 0.35em
104
102
  presentation:
105
103
  backgroundColor: "#550000"
106
104
  textColor: "#ffffff"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@janga/norna",
3
3
  "description": "A command-line publishing workflow for simple static sites.",
4
4
  "type": "module",
5
- "version": "0.7.3",
5
+ "version": "0.7.5",
6
6
  "license": "GPL-3.0-only",
7
7
  "repository": {
8
8
  "type": "git",
@@ -23,6 +23,7 @@ try {
23
23
  console.log(`Base path: ${projectConfig.site.basePath}`);
24
24
  console.log(`Page width: ${projectConfig.layout.pageWidth}`);
25
25
  console.log(`Gutter: desktop ${projectConfig.layout.gutter.desktop}, mobile ${projectConfig.layout.gutter.mobile}`);
26
+ console.log(`Layout density: ${projectConfig.layout.density}`);
26
27
  console.log(`Image area width: ${projectConfig.gallery.width}`);
27
28
  console.log(`Image max available width: desktop ${projectConfig.gallery.maxAvailableWidthPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableWidthPercent.mobile}%`);
28
29
  console.log(`Image max available height: desktop ${projectConfig.gallery.maxAvailableHeightPercent.desktop}%, mobile ${projectConfig.gallery.maxAvailableHeightPercent.mobile}%`);
@@ -17,6 +17,16 @@ const assertObject = (value, path, sourceLabel = siteConfigLabel) => {
17
17
  return value;
18
18
  };
19
19
 
20
+ const readEnum = (object, key, path, allowedValues, fallback, sourceLabel = siteConfigLabel) => {
21
+ const value = object[key] ?? fallback;
22
+
23
+ if (!allowedValues.includes(value)) {
24
+ throw new Error(`${path}.${key} must be one of ${allowedValues.join(', ')} in ${sourceLabel}.`);
25
+ }
26
+
27
+ return value;
28
+ };
29
+
20
30
  const readString = (object, key, path, sourceLabel = siteConfigLabel) => {
21
31
  const value = object[key];
22
32
 
@@ -304,6 +314,77 @@ const rawDeploy = assertObject(rawConfig.deploy ?? {}, 'deploy');
304
314
  const rawDeployWatch = assertObject(rawDeploy.watch ?? {}, 'deploy.watch');
305
315
 
306
316
  const defaultFontFamily = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
317
+ const layoutDensityNames = ['compact', 'normal', 'airy'];
318
+ const layoutDensityProfiles = Object.freeze({
319
+ compact: Object.freeze({
320
+ bodyToImages: Object.freeze({
321
+ desktop: '1rem',
322
+ mobile: '0.9rem',
323
+ }),
324
+ finalSectionBottom: Object.freeze({
325
+ desktop: 'clamp(1.2rem, 2.4vw, 2.25rem)',
326
+ mobile: '1.25rem',
327
+ }),
328
+ firstSectionTop: Object.freeze({
329
+ desktop: 'clamp(1rem, 2vw, 1.75rem)',
330
+ mobile: '1rem',
331
+ }),
332
+ imageGap: Object.freeze({
333
+ desktop: 'clamp(1rem, 2vw, 1.5rem)',
334
+ mobile: '1.25rem',
335
+ }),
336
+ sectionGap: Object.freeze({
337
+ desktop: 'clamp(1.2rem, 2.4vw, 2.25rem)',
338
+ mobile: '1.25rem',
339
+ }),
340
+ }),
341
+ normal: Object.freeze({
342
+ bodyToImages: Object.freeze({
343
+ desktop: '1.25rem',
344
+ mobile: '1rem',
345
+ }),
346
+ finalSectionBottom: Object.freeze({
347
+ desktop: 'clamp(1.4rem, 3vw, 2.75rem)',
348
+ mobile: '1.5rem',
349
+ }),
350
+ firstSectionTop: Object.freeze({
351
+ desktop: 'clamp(1.25rem, 3vw, 2.5rem)',
352
+ mobile: '1.25rem',
353
+ }),
354
+ imageGap: Object.freeze({
355
+ desktop: 'clamp(1.25rem, 2.8vw, 2rem)',
356
+ mobile: '1.5rem',
357
+ }),
358
+ sectionGap: Object.freeze({
359
+ desktop: 'clamp(1.4rem, 3vw, 2.75rem)',
360
+ mobile: '1.5rem',
361
+ }),
362
+ }),
363
+ airy: Object.freeze({
364
+ bodyToImages: Object.freeze({
365
+ desktop: '1.5rem',
366
+ mobile: '1.25rem',
367
+ }),
368
+ finalSectionBottom: Object.freeze({
369
+ desktop: 'clamp(2.25rem, 5vw, 4.5rem)',
370
+ mobile: '2rem',
371
+ }),
372
+ firstSectionTop: Object.freeze({
373
+ desktop: 'clamp(2rem, 5vw, 4rem)',
374
+ mobile: '1.5rem',
375
+ }),
376
+ imageGap: Object.freeze({
377
+ desktop: 'clamp(1.5rem, 3.5vw, 2.75rem)',
378
+ mobile: '2rem',
379
+ }),
380
+ sectionGap: Object.freeze({
381
+ desktop: 'clamp(2.25rem, 5vw, 4.5rem)',
382
+ mobile: '2rem',
383
+ }),
384
+ }),
385
+ });
386
+ const layoutDensity = readEnum(rawLayout, 'density', 'layout', layoutDensityNames, 'normal', siteThemeLabel);
387
+ const layoutSpacingDefaults = layoutDensityProfiles[layoutDensity];
307
388
 
308
389
  export const projectConfig = Object.freeze({
309
390
  site: Object.freeze({
@@ -311,44 +392,18 @@ export const projectConfig = Object.freeze({
311
392
  url: readUrl(rawSite, 'url', 'site'),
312
393
  }),
313
394
  layout: Object.freeze({
395
+ density: layoutDensity,
314
396
  gutter: readResponsiveCssLength(rawLayout, 'gutter', 'layout', Object.freeze({
315
397
  desktop: 'clamp(1.25rem, 4vw, 3rem)',
316
398
  mobile: '1rem',
317
399
  }), siteThemeLabel),
318
400
  pageWidth: readCssLength(rawLayout, 'pageWidth', 'layout', '1180px', siteThemeLabel),
319
401
  spacing: Object.freeze({
320
- bodyToImages: readResponsiveCssLength(rawLayoutSpacing, 'bodyToImages', 'layout.spacing', Object.freeze({
321
- desktop: 'clamp(1.25rem, 2.5vw, 2rem)',
322
- mobile: '1.25rem',
323
- }), siteThemeLabel),
324
- finalSectionBottom: readResponsiveCssLength(rawLayoutSpacing, 'finalSectionBottom', 'layout.spacing', Object.freeze({
325
- desktop: 'clamp(2.55rem, 4.2vw, 3.9rem)',
326
- mobile: '2.25rem',
327
- }), siteThemeLabel),
328
- firstSectionTop: readResponsiveCssLength(rawLayoutSpacing, 'firstSectionTop', 'layout.spacing', Object.freeze({
329
- desktop: 'clamp(1.875rem, 3vw, 2.75rem)',
330
- mobile: '1.375rem',
331
- }), siteThemeLabel),
332
- imageGap: readResponsiveCssLength(rawLayoutSpacing, 'imageGap', 'layout.spacing', Object.freeze({
333
- desktop: 'clamp(1.5rem, 3.5vw, 2.75rem)',
334
- mobile: '2.75rem',
335
- }), siteThemeLabel),
336
- sectionGap: readResponsiveCssLength(rawLayoutSpacing, 'sectionGap', 'layout.spacing', Object.freeze({
337
- desktop: 'clamp(2.55rem, 4.2vw, 3.9rem)',
338
- mobile: '2.25rem',
339
- }), siteThemeLabel),
340
- sectionHeadingToBody: readResponsiveCssLength(rawLayoutSpacing, 'sectionHeadingToBody', 'layout.spacing', Object.freeze({
341
- desktop: 'clamp(0.5625rem, 1.25vw, 0.75rem)',
342
- mobile: '0.4375rem',
343
- }), siteThemeLabel),
344
- subheadingRuleTop: readResponsiveCssLength(rawLayoutSpacing, 'subheadingRuleTop', 'layout.spacing', Object.freeze({
345
- desktop: '1rem',
346
- mobile: '0.8rem',
347
- }), siteThemeLabel),
348
- subheadingTop: readResponsiveCssLength(rawLayoutSpacing, 'subheadingTop', 'layout.spacing', Object.freeze({
349
- desktop: '2rem',
350
- mobile: '1.6rem',
351
- }), siteThemeLabel),
402
+ bodyToImages: readResponsiveCssLength(rawLayoutSpacing, 'bodyToImages', 'layout.spacing', layoutSpacingDefaults.bodyToImages, siteThemeLabel),
403
+ finalSectionBottom: readResponsiveCssLength(rawLayoutSpacing, 'finalSectionBottom', 'layout.spacing', layoutSpacingDefaults.finalSectionBottom, siteThemeLabel),
404
+ firstSectionTop: readResponsiveCssLength(rawLayoutSpacing, 'firstSectionTop', 'layout.spacing', layoutSpacingDefaults.firstSectionTop, siteThemeLabel),
405
+ imageGap: readResponsiveCssLength(rawLayoutSpacing, 'imageGap', 'layout.spacing', layoutSpacingDefaults.imageGap, siteThemeLabel),
406
+ sectionGap: readResponsiveCssLength(rawLayoutSpacing, 'sectionGap', 'layout.spacing', layoutSpacingDefaults.sectionGap, siteThemeLabel),
352
407
  }),
353
408
  }),
354
409
  gallery: Object.freeze({