@janga/norna 0.7.2 → 0.7.3

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 (145) hide show
  1. package/README.md +16 -16
  2. package/bin/norna-cli.mjs +12 -7
  3. package/docs/README.md +10 -11
  4. package/docs/commands.md +8 -8
  5. package/docs/configuration.md +13 -145
  6. package/docs/content.md +17 -15
  7. package/docs/design/command-organization.md +55 -55
  8. package/docs/design/site-examples-structure.md +2 -2
  9. package/docs/engine-development.md +5 -5
  10. package/docs/getting-started.md +19 -26
  11. package/docs/images-and-metadata.md +1 -1
  12. package/docs/local-development.md +2 -2
  13. package/docs/publishing.md +1 -1
  14. package/docs/routes.md +2 -2
  15. package/docs/site-structure.md +8 -7
  16. package/docs/theme.md +149 -7
  17. package/docs/typography.md +47 -28
  18. package/examples/dog-gallery/.astro/collections/site.schema.json +802 -0
  19. package/examples/dog-gallery/.astro/collections/theme.schema.json +714 -0
  20. package/examples/dog-gallery/.astro/content-assets.mjs +4 -0
  21. package/examples/dog-gallery/.astro/content-modules.mjs +1 -0
  22. package/examples/dog-gallery/.astro/content.d.ts +175 -0
  23. package/examples/dog-gallery/.astro/data-store.json +1 -0
  24. package/examples/dog-gallery/.astro/dev-local.json +8 -0
  25. package/examples/dog-gallery/.astro/dev.json +13 -0
  26. package/examples/dog-gallery/.astro/dev.log +49 -0
  27. package/examples/dog-gallery/.astro/settings.json +5 -0
  28. package/examples/dog-gallery/.astro/types.d.ts +2 -0
  29. package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
  30. package/examples/dog-gallery/dist/_astro/SitePage.BFHZm5UG.css +1 -0
  31. package/examples/dog-gallery/dist/dog-care/index.html +15 -0
  32. package/examples/dog-gallery/dist/favicon.svg +7 -0
  33. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
  34. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
  35. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
  36. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
  37. package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
  38. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
  39. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
  40. package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
  41. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
  42. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
  43. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
  44. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
  45. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
  46. package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
  47. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
  48. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
  49. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
  50. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
  51. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
  52. package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
  53. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
  54. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
  55. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
  56. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
  57. package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
  58. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
  59. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
  60. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
  61. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
  62. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
  63. package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
  64. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
  65. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
  66. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
  67. package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
  68. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
  69. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
  70. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
  71. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
  72. package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
  73. package/examples/dog-gallery/dist/index.html +43 -0
  74. package/examples/dog-gallery/dist/robots.txt +2 -0
  75. package/examples/dog-gallery/node_modules/.vite/deps/_metadata.json +74 -0
  76. package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  77. package/examples/dog-gallery/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  78. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  79. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  80. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  81. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  82. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  83. package/examples/dog-gallery/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  84. package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  85. package/examples/dog-gallery/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  86. package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  87. package/examples/dog-gallery/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  88. package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  89. package/examples/dog-gallery/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  90. package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  91. package/examples/dog-gallery/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  92. package/examples/dog-gallery/node_modules/.vite/deps/package.json +3 -0
  93. package/examples/dog-gallery/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  94. package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  95. package/examples/dog-gallery/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  96. package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  97. package/examples/dog-gallery/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  98. package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  99. package/examples/dog-gallery/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  100. package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  101. package/examples/dog-gallery/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  102. package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  103. package/examples/dog-gallery/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  104. package/examples/dog-gallery/site/config.mjs +12 -42
  105. package/examples/dog-gallery/site/content.md +27 -5
  106. package/examples/dog-gallery/site/routes/dog-care/route-content.md +17 -3
  107. package/examples/dog-gallery/site/theme.md +85 -26
  108. package/fixtures/basic/site/config.mjs +2 -22
  109. package/fixtures/basic/site/content.md +1 -1
  110. package/fixtures/basic/site/theme.md +7 -3
  111. package/package.json +3 -3
  112. package/scripts/check-config.mjs +3 -3
  113. package/scripts/deploy-site.mjs +3 -3
  114. package/scripts/init-site.mjs +10 -8
  115. package/scripts/lib/frontmatter-yaml.mjs +85 -0
  116. package/scripts/lib/presentation.mjs +10 -11
  117. package/scripts/lib/project-config.mjs +91 -45
  118. package/scripts/lib/site-content.mjs +20 -2
  119. package/scripts/lib/theme-config.mjs +34 -0
  120. package/scripts/lib/typography.mjs +100 -20
  121. package/scripts/show-typography.mjs +58 -154
  122. package/scripts/sync-content-sections.mjs +6 -6
  123. package/scripts/test-content-check.mjs +3 -4
  124. package/scripts/test-engine-commands.mjs +2 -2
  125. package/scripts/test-package-check.mjs +17 -16
  126. package/scripts/test-temporary-visibility.mjs +2 -3
  127. package/src/components/SitePage.astro +2 -3
  128. package/src/components/SiteSection.astro +39 -5
  129. package/src/content.config.ts +60 -2
  130. package/src/layouts/BaseLayout.astro +17 -0
  131. package/src/styles/global.css +54 -50
  132. package/starters/basic/README.md +10 -15
  133. package/starters/basic/package.json +2 -2
  134. package/starters/basic/site/config.mjs +3 -24
  135. package/starters/basic/site/content.md +40 -7
  136. package/starters/basic/site/theme.md +110 -17
  137. package/starters/project/.github/workflows/deploy.yml +65 -0
  138. package/starters/project/README.md +72 -0
  139. package/starters/project/package-lock.json +4296 -0
  140. package/starters/project/package.json +35 -0
  141. package/starters/project/site/config.mjs +43 -0
  142. package/starters/project/site/content.md +88 -0
  143. package/starters/project/site/public/robots.txt +2 -0
  144. package/starters/project/site/routes/guide/route-content.md +58 -0
  145. package/starters/project/site/theme.md +15 -0
@@ -7,11 +7,31 @@ export const typographyPresetNames = [
7
7
 
8
8
  export const typographyPresets = {
9
9
  'quiet-gallery': {
10
- heading: {
11
- align: { desktop: 'left', mobile: 'left' },
12
- size: 'medium',
13
- lineHeight: 1.08,
14
- spacing: '0.65em',
10
+ headings: {
11
+ h1: {
12
+ align: { desktop: 'left', mobile: 'left' },
13
+ size: 'large',
14
+ lineHeight: 1.04,
15
+ spacing: '0.55em',
16
+ },
17
+ h2: {
18
+ align: { desktop: 'left', mobile: 'left' },
19
+ size: 'medium',
20
+ lineHeight: 1.08,
21
+ spacing: '0.65em',
22
+ },
23
+ h3: {
24
+ align: { desktop: 'left', mobile: 'left' },
25
+ size: 'small',
26
+ lineHeight: 1.35,
27
+ spacing: '0.9rem',
28
+ },
29
+ h4: {
30
+ align: { desktop: 'left', mobile: 'left' },
31
+ size: 'medium',
32
+ lineHeight: 1.5,
33
+ spacing: '0.85rem',
34
+ },
15
35
  },
16
36
  body: {
17
37
  align: { desktop: 'left', mobile: 'left' },
@@ -27,11 +47,31 @@ export const typographyPresets = {
27
47
  },
28
48
  },
29
49
  'compact-gallery': {
30
- heading: {
31
- align: { desktop: 'left', mobile: 'left' },
32
- size: 'medium',
33
- lineHeight: 1.08,
34
- spacing: '0.45em',
50
+ headings: {
51
+ h1: {
52
+ align: { desktop: 'left', mobile: 'left' },
53
+ size: 'large',
54
+ lineHeight: 1.04,
55
+ spacing: '0.45em',
56
+ },
57
+ h2: {
58
+ align: { desktop: 'left', mobile: 'left' },
59
+ size: 'medium',
60
+ lineHeight: 1.08,
61
+ spacing: '0.45em',
62
+ },
63
+ h3: {
64
+ align: { desktop: 'left', mobile: 'left' },
65
+ size: 'small',
66
+ lineHeight: 1.3,
67
+ spacing: '0.75rem',
68
+ },
69
+ h4: {
70
+ align: { desktop: 'left', mobile: 'left' },
71
+ size: 'medium',
72
+ lineHeight: 1.42,
73
+ spacing: '0.6rem',
74
+ },
35
75
  },
36
76
  body: {
37
77
  align: { desktop: 'left', mobile: 'left' },
@@ -47,11 +87,31 @@ export const typographyPresets = {
47
87
  },
48
88
  },
49
89
  'text-forward': {
50
- heading: {
51
- align: { desktop: 'left', mobile: 'left' },
52
- size: 'medium',
53
- lineHeight: 1.12,
54
- spacing: '0.8em',
90
+ headings: {
91
+ h1: {
92
+ align: { desktop: 'left', mobile: 'left' },
93
+ size: 'large',
94
+ lineHeight: 1.06,
95
+ spacing: '0.65em',
96
+ },
97
+ h2: {
98
+ align: { desktop: 'left', mobile: 'left' },
99
+ size: 'medium',
100
+ lineHeight: 1.12,
101
+ spacing: '0.8em',
102
+ },
103
+ h3: {
104
+ align: { desktop: 'left', mobile: 'left' },
105
+ size: 'small',
106
+ lineHeight: 1.35,
107
+ spacing: '1rem',
108
+ },
109
+ h4: {
110
+ align: { desktop: 'left', mobile: 'left' },
111
+ size: 'medium',
112
+ lineHeight: 1.55,
113
+ spacing: '0.85rem',
114
+ },
55
115
  },
56
116
  body: {
57
117
  align: { desktop: 'left', mobile: 'left' },
@@ -67,11 +127,31 @@ export const typographyPresets = {
67
127
  },
68
128
  },
69
129
  statement: {
70
- heading: {
71
- align: { desktop: 'left', mobile: 'left' },
72
- size: 'medium',
73
- lineHeight: 1.04,
74
- spacing: '0.5em',
130
+ headings: {
131
+ h1: {
132
+ align: { desktop: 'left', mobile: 'left' },
133
+ size: 'xlarge',
134
+ lineHeight: 1.02,
135
+ spacing: '0.45em',
136
+ },
137
+ h2: {
138
+ align: { desktop: 'left', mobile: 'left' },
139
+ size: 'large',
140
+ lineHeight: 1.04,
141
+ spacing: '0.5em',
142
+ },
143
+ h3: {
144
+ align: { desktop: 'left', mobile: 'left' },
145
+ size: 'medium',
146
+ lineHeight: 1.18,
147
+ spacing: '0.85rem',
148
+ },
149
+ h4: {
150
+ align: { desktop: 'left', mobile: 'left' },
151
+ size: 'medium',
152
+ lineHeight: 1.42,
153
+ spacing: '0.75rem',
154
+ },
75
155
  },
76
156
  body: {
77
157
  align: { desktop: 'left', mobile: 'left' },
@@ -1,4 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
+ import { findMap } from './lib/frontmatter-yaml.mjs';
2
3
  import {
3
4
  defaultTypography,
4
5
  resolveTypographyOverride,
@@ -23,76 +24,40 @@ import {
23
24
 
24
25
  const mode = process.argv[2] ?? 'show';
25
26
 
26
- const typographyRoles = ['heading', 'body', 'caption'];
27
- const typographyFields = {
28
- heading: ['align', 'size', 'lineHeight', 'spacing'],
29
- body: ['align', 'size', 'lineHeight', 'paragraphSpacing'],
30
- caption: ['align', 'size', 'lineHeight', 'spacing'],
31
- };
32
- const responsiveFields = new Set(['align']);
33
-
27
+ const typographyValuePaths = [
28
+ ['headings', 'h1', 'align', 'desktop'],
29
+ ['headings', 'h1', 'align', 'mobile'],
30
+ ['headings', 'h1', 'size'],
31
+ ['headings', 'h1', 'lineHeight'],
32
+ ['headings', 'h1', 'spacing'],
33
+ ['headings', 'h2', 'align', 'desktop'],
34
+ ['headings', 'h2', 'align', 'mobile'],
35
+ ['headings', 'h2', 'size'],
36
+ ['headings', 'h2', 'lineHeight'],
37
+ ['headings', 'h2', 'spacing'],
38
+ ['headings', 'h3', 'align', 'desktop'],
39
+ ['headings', 'h3', 'align', 'mobile'],
40
+ ['headings', 'h3', 'size'],
41
+ ['headings', 'h3', 'lineHeight'],
42
+ ['headings', 'h3', 'spacing'],
43
+ ['headings', 'h4', 'align', 'desktop'],
44
+ ['headings', 'h4', 'align', 'mobile'],
45
+ ['headings', 'h4', 'size'],
46
+ ['headings', 'h4', 'lineHeight'],
47
+ ['headings', 'h4', 'spacing'],
48
+ ['body', 'align', 'desktop'],
49
+ ['body', 'align', 'mobile'],
50
+ ['body', 'size'],
51
+ ['body', 'lineHeight'],
52
+ ['body', 'paragraphSpacing'],
53
+ ['caption', 'align', 'desktop'],
54
+ ['caption', 'align', 'mobile'],
55
+ ['caption', 'size'],
56
+ ['caption', 'lineHeight'],
57
+ ['caption', 'spacing'],
58
+ ];
34
59
  const countIndent = (line) => line.match(/^\s*/)?.[0].length ?? 0;
35
60
 
36
- const parseScalar = (rawValue) => {
37
- const value = rawValue.trim();
38
-
39
- if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
40
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
41
- return value.slice(1, -1);
42
- }
43
-
44
- return value;
45
- };
46
-
47
- const parseMapping = (lines, startIndex, baseIndent) => {
48
- const value = {};
49
- let index = startIndex;
50
-
51
- while (index < lines.length) {
52
- const line = lines[index];
53
- if (!line.trim() || line.trim().startsWith('#')) {
54
- index += 1;
55
- continue;
56
- }
57
-
58
- const indent = countIndent(line);
59
- if (indent <= baseIndent) break;
60
- if (line.trim().startsWith('- ')) break;
61
-
62
- const match = line.trim().match(/^([a-zA-Z][a-zA-Z0-9-]*):(?:\s+(.*))?$/);
63
- if (!match) break;
64
-
65
- const [, key, rawValue] = match;
66
- if (rawValue === undefined) {
67
- const parsed = parseMapping(lines, index + 1, indent);
68
- value[key] = parsed.value;
69
- index = parsed.nextIndex;
70
- } else {
71
- value[key] = parseScalar(rawValue);
72
- index += 1;
73
- }
74
- }
75
-
76
- return { value, nextIndex: index };
77
- };
78
-
79
- const findMap = (lines, label, parentStart = 0, parentEnd = lines.length, requiredIndent = null) => {
80
- for (let index = parentStart; index < parentEnd; index += 1) {
81
- const line = lines[index];
82
- const match = line.match(/^(\s*)([a-zA-Z][a-zA-Z0-9-]*):\s*$/);
83
- if (!match || match[2] !== label) continue;
84
- if (requiredIndent !== null && match[1].length !== requiredIndent) continue;
85
-
86
- return {
87
- index,
88
- indent: match[1].length,
89
- ...parseMapping(lines, index + 1, match[1].length),
90
- };
91
- }
92
-
93
- return null;
94
- };
95
-
96
61
  const isPlainObject = (value) => (
97
62
  value !== null &&
98
63
  typeof value === 'object' &&
@@ -129,29 +94,13 @@ const setPath = (value, path, entry) => {
129
94
  const annotateResolvedValues = (resolved, sources) => {
130
95
  const annotated = {};
131
96
 
132
- for (const role of typographyRoles) {
133
- for (const field of typographyFields[role]) {
134
- if (responsiveFields.has(field)) {
135
- for (const viewport of ['desktop', 'mobile']) {
136
- const path = [role, field, viewport];
137
- const source = getPath(sources, path);
138
- setPath(annotated, path, {
139
- value: getPath(resolved.values, path),
140
- source: source.source,
141
- ...(source.inherited ? { inherited: true } : {}),
142
- });
143
- }
144
- continue;
145
- }
146
-
147
- const path = [role, field];
148
- const source = getPath(sources, path);
149
- setPath(annotated, path, {
150
- value: getPath(resolved.values, path),
151
- source: source.source,
152
- ...(source.inherited ? { inherited: true } : {}),
153
- });
154
- }
97
+ for (const path of typographyValuePaths) {
98
+ const source = getPath(sources, path);
99
+ setPath(annotated, path, {
100
+ value: getPath(resolved.values, path),
101
+ source: source.source,
102
+ ...(source.inherited ? { inherited: true } : {}),
103
+ });
155
104
  }
156
105
 
157
106
  return annotated;
@@ -160,23 +109,11 @@ const annotateResolvedValues = (resolved, sources) => {
160
109
  const presetSources = (presetName) => {
161
110
  const sources = {};
162
111
 
163
- for (const role of typographyRoles) {
164
- for (const field of typographyFields[role]) {
165
- if (responsiveFields.has(field)) {
166
- for (const viewport of ['desktop', 'mobile']) {
167
- setPath(sources, [role, field, viewport], {
168
- source: `preset:${presetName}`,
169
- inherited: false,
170
- });
171
- }
172
- continue;
173
- }
174
-
175
- setPath(sources, [role, field], {
176
- source: `preset:${presetName}`,
177
- inherited: false,
178
- });
179
- }
112
+ for (const path of typographyValuePaths) {
113
+ setPath(sources, path, {
114
+ source: `preset:${presetName}`,
115
+ inherited: false,
116
+ });
180
117
  }
181
118
 
182
119
  return sources;
@@ -186,28 +123,12 @@ const applyOverrideSources = (sources, typographyConfig, sourceLabel) => {
186
123
  const overrides = typographyConfig?.overrides;
187
124
  if (!overrides) return sources;
188
125
 
189
- for (const role of typographyRoles) {
190
- for (const field of typographyFields[role]) {
191
- if (responsiveFields.has(field)) {
192
- for (const viewport of ['desktop', 'mobile']) {
193
- const path = [role, field, viewport];
194
- if (hasPath(overrides, path)) {
195
- setPath(sources, path, {
196
- source: `${sourceLabel} override`,
197
- inherited: false,
198
- });
199
- }
200
- }
201
- continue;
202
- }
203
-
204
- const path = [role, field];
205
- if (hasPath(overrides, path)) {
206
- setPath(sources, path, {
207
- source: `${sourceLabel} override`,
208
- inherited: false,
209
- });
210
- }
126
+ for (const path of typographyValuePaths) {
127
+ if (hasPath(overrides, path)) {
128
+ setPath(sources, path, {
129
+ source: `${sourceLabel} override`,
130
+ inherited: false,
131
+ });
211
132
  }
212
133
  }
213
134
 
@@ -217,25 +138,11 @@ const applyOverrideSources = (sources, typographyConfig, sourceLabel) => {
217
138
  const inheritedSources = (sources) => {
218
139
  const inherited = structuredClone(sources);
219
140
 
220
- for (const role of typographyRoles) {
221
- for (const field of typographyFields[role]) {
222
- if (responsiveFields.has(field)) {
223
- for (const viewport of ['desktop', 'mobile']) {
224
- const path = [role, field, viewport];
225
- setPath(inherited, path, {
226
- ...getPath(sources, path),
227
- inherited: true,
228
- });
229
- }
230
- continue;
231
- }
232
-
233
- const path = [role, field];
234
- setPath(inherited, path, {
235
- ...getPath(sources, path),
236
- inherited: true,
237
- });
238
- }
141
+ for (const path of typographyValuePaths) {
142
+ setPath(inherited, path, {
143
+ ...getPath(sources, path),
144
+ inherited: true,
145
+ });
239
146
  }
240
147
 
241
148
  return inherited;
@@ -355,10 +262,7 @@ const readThemeTypography = async () => {
355
262
  }
356
263
 
357
264
  const themeLines = themeFrontmatterBody.split(/\r?\n/);
358
- const themePresentation = findMap(themeLines, 'presentation', 0, themeLines.length, 0);
359
- const themeTypographyConfig = themePresentation
360
- ? findMap(themeLines, 'typography', themePresentation.index + 1, themePresentation.nextIndex)?.value
361
- : null;
265
+ const themeTypographyConfig = findMap(themeLines, 'typography', 0, themeLines.length, 0)?.value;
362
266
  return resolveAnnotatedTypographyConfig(themeTypographyConfig ?? defaultTypography, siteThemeLabel);
363
267
  };
364
268
 
@@ -429,5 +333,5 @@ if (mode === 'presets') {
429
333
 
430
334
  console.log(toYamlLines(output).join('\n'));
431
335
  } else {
432
- throw new Error('Usage: norna typography:presets|typography:show');
336
+ throw new Error('Usage: norna typography presets|show');
433
337
  }
@@ -59,7 +59,7 @@ const promptForWrite = async () => {
59
59
  if (!process.stdin.isTTY) return false;
60
60
 
61
61
  const rl = createInterface({ input, output });
62
- const answer = await rl.question(`This will rewrite Markdown sections in ${siteContentLabel} and move gallery image files if needed. Continue? [y/N] `);
62
+ const answer = await rl.question(`This will rewrite Markdown sections in ${siteContentLabel} and move image files if needed. Continue? [y/N] `);
63
63
  rl.close();
64
64
 
65
65
  return answer.trim().toLowerCase() === 'y';
@@ -269,7 +269,7 @@ const validateRouteContentFiles = async () => {
269
269
  if (referencedImages.has(imageName)) {
270
270
  const previousSectionId = referencedImages.get(imageName);
271
271
  const message = `Image "${imageName}" is referenced more than once, in "${previousSectionId}" and "${contentFile.contentLabel}:${section.id}".`;
272
- const fix = 'Each image filename can be referenced by only one gallery row.';
272
+ const fix = 'Each image filename can be referenced by only one image row.';
273
273
 
274
274
  addIssue({ severity: 'error', sectionId: previousSectionId, message, fix });
275
275
  addIssue({
@@ -291,7 +291,7 @@ const validateRouteContentFiles = async () => {
291
291
  sectionId: `${contentFile.contentLabel}:${section.id}`,
292
292
  sectionLabel: `${contentFile.contentLabel} [${section.id}]`,
293
293
  message: `Image "${imageName}" does not exist anywhere under ${contentFile.imagesLabel}/.`,
294
- fix: `Add the source image to ${contentFile.imagesLabel}/${section.id}/ or remove the gallery row.`,
294
+ fix: `Add the source image to ${contentFile.imagesLabel}/${section.id}/ or remove the image row.`,
295
295
  });
296
296
  continue;
297
297
  }
@@ -380,7 +380,7 @@ const getReportLines = (title) => {
380
380
  leftKey.localeCompare(rightKey, 'sv')
381
381
  ));
382
382
 
383
- lines.push('', 'Section and Gallery Issues');
383
+ lines.push('', 'Section And Image Issues');
384
384
 
385
385
  for (const [, group] of orderedSectionGroups) {
386
386
  lines.push('', `[${group.label}]`);
@@ -506,7 +506,7 @@ for (const section of frontmatterSections) {
506
506
  const message = previousSectionId === section.id
507
507
  ? `Image "${imageName}" is referenced more than once in this section.`
508
508
  : `Image "${imageName}" is referenced more than once, in sections "${previousSectionId}" and "${section.id}".`;
509
- const fix = 'Each image filename can be referenced by only one gallery row.';
509
+ const fix = 'Each image filename can be referenced by only one image row.';
510
510
 
511
511
  addSectionIssue(previousSectionId, { severity: 'error', message, fix });
512
512
  if (previousSectionId !== section.id) {
@@ -522,7 +522,7 @@ for (const section of frontmatterSections) {
522
522
  addSectionIssue(section.id, {
523
523
  severity: 'error',
524
524
  message: `Image "${imageName}" does not exist anywhere under ${siteImagesLabel}/.`,
525
- fix: `Add the source image to ${siteImagesLabel}/${section.id}/ or remove the gallery row.`,
525
+ fix: `Add the source image to ${siteImagesLabel}/${section.id}/ or remove the image row.`,
526
526
  });
527
527
  continue;
528
528
  }
@@ -43,9 +43,8 @@ const makePngHeader = ({ width, height }) => {
43
43
  };
44
44
 
45
45
  const defaultTheme = `---
46
- presentation:
47
- typography:
48
- preset: quiet-gallery
46
+ typography:
47
+ preset: quiet-gallery
49
48
  frame:
50
49
  colors: presentation
51
50
  ---
@@ -114,7 +113,7 @@ test('content:check groups section issues, global issues, and unreferenced image
114
113
 
115
114
  assert.equal(result.status, 1, output);
116
115
  assert.match(output, /^Content check failed\./m);
117
- assert.match(output, /Section and Gallery Issues\n\n\[min-konst\]\n Errors:/);
116
+ assert.match(output, /Section And Image Issues\n\n\[min-konst\]\n Errors:/);
118
117
  assert.match(output, /Image "vav\.jpeg" is used here but is located in site\/images\/mitt-hem\/\./);
119
118
  assert.match(output, /Image "missing\.jpeg" does not exist anywhere under site\/images\/\./);
120
119
  assert.match(output, /Image "duplicate\.jpg" is referenced more than once in this section\./);
@@ -47,12 +47,12 @@ try {
47
47
  assert.equal(initializedPackageJson.scripts['engine:update'], undefined);
48
48
  assert.equal(initializedPackageJson.scripts['engine:version'], undefined);
49
49
 
50
- const presetsResult = runCli(['typography:presets']);
50
+ const presetsResult = runCli(['typography', 'presets']);
51
51
  assert.equal(presetsResult.status, 0, presetsResult.stderr || presetsResult.stdout);
52
52
  assert.match(presetsResult.stdout, /quiet-gallery:/);
53
53
  assert.match(presetsResult.stdout, /text-forward:/);
54
54
 
55
- const showResult = runCli(['--site-dir', path.join(initializedSiteRoot, 'site'), 'typography:show']);
55
+ const showResult = runCli(['--site-dir', path.join(initializedSiteRoot, 'site'), 'typography', 'show']);
56
56
  assert.equal(showResult.status, 0, showResult.stderr || showResult.stdout);
57
57
  assert.match(showResult.stdout, /theme:/);
58
58
  assert.match(showResult.stdout, /pages:/);
@@ -181,18 +181,18 @@ try {
181
181
  await writeFile(
182
182
  packageCheckConfigPath,
183
183
  packageCheckConfig
184
- .replace("url: 'https://example.com/'", "url: 'https://example.com/gallery/'")
185
- .replace("basePath: '/'", "basePath: '/gallery/'"),
184
+ .replace("url: 'https://example.com/'", "url: 'https://example.com/site/'")
185
+ .replace("basePath: '/'", "basePath: '/site/'"),
186
186
  );
187
187
  const packageCheckThemePath = path.join(siteProjectRoot, 'site', 'theme.md');
188
188
  const packageCheckTheme = await readFile(packageCheckThemePath, 'utf8');
189
189
  await writeFile(
190
190
  packageCheckThemePath,
191
- packageCheckTheme.replace('brand: Example Gallery', 'brand: Package Check Brand'),
191
+ packageCheckTheme.replace('brand: Example Site', 'brand: Package Check Brand'),
192
192
  );
193
193
  await mkdir(path.join(siteProjectRoot, 'site', 'routes', 'about'), { recursive: true });
194
194
  await writeFile(path.join(siteProjectRoot, 'site', 'routes', 'about', 'route-content.md'), `---
195
- title: About the gallery
195
+ title: About the site
196
196
  description: Route used by package checks.
197
197
  navigation:
198
198
  label: About
@@ -235,7 +235,7 @@ This route verifies that packaged norna sites can build route pages.
235
235
  );
236
236
  await assertFileIncludes(
237
237
  path.join(siteProjectRoot, 'dist', 'index.html'),
238
- '--section-heading-font-size-desktop: clamp(1.35rem, 1.1rem + 0.9vw, 2rem)',
238
+ '--section-heading-font-size-desktop: clamp(1.6rem, 1.22rem + 1.45vw, 2.75rem)',
239
239
  );
240
240
  await assertFileIncludes(
241
241
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -247,15 +247,15 @@ This route verifies that packaged norna sites can build route pages.
247
247
  );
248
248
  await assertFileIncludes(
249
249
  path.join(siteProjectRoot, 'dist', 'about', 'index.html'),
250
- '<link rel="canonical" href="https://example.com/gallery/about/">',
250
+ '<link rel="canonical" href="https://example.com/site/about/">',
251
251
  );
252
252
  await assertFileIncludes(
253
253
  path.join(siteProjectRoot, 'dist', 'about', 'index.html'),
254
- 'href="/gallery/about/" aria-current="page"',
254
+ 'href="/site/about/" aria-current="page"',
255
255
  );
256
256
  await assertFileIncludes(
257
257
  path.join(siteProjectRoot, 'dist', 'index.html'),
258
- '<a class="site-brand" href="/gallery/">Package Check Brand</a>',
258
+ '<a class="site-brand" href="/site/">Package Check Brand</a>',
259
259
  );
260
260
  await assertFileIncludes(
261
261
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -306,7 +306,7 @@ This route verifies that packaged norna sites can build route pages.
306
306
  await assertFileExists(path.join(siteProjectRoot, 'dist', 'favicon.ico'));
307
307
  await assertFileIncludes(
308
308
  path.join(siteProjectRoot, 'dist', 'index.html'),
309
- '<link rel="icon" sizes="any" href="/gallery/favicon.ico">',
309
+ '<link rel="icon" sizes="any" href="/site/favicon.ico">',
310
310
  );
311
311
  await assertFileExcludes(
312
312
  path.join(siteProjectRoot, 'dist', 'index.html'),
@@ -316,12 +316,12 @@ This route verifies that packaged norna sites can build route pages.
316
316
  const siteContent = await readFile(siteContentPath, 'utf8');
317
317
  const siteThemePath = path.join(siteProjectRoot, 'site', 'theme.md');
318
318
  const siteTheme = await readFile(siteThemePath, 'utf8');
319
- await writeFile(siteThemePath, siteTheme.replace('\n preset: quiet-gallery', '\n preset: noisy-gallery'));
319
+ await writeFile(siteThemePath, siteTheme.replace('\n preset: quiet-gallery', '\n preset: noisy-gallery'));
320
320
  await runExpectFailure(
321
321
  npxBin,
322
322
  ['norna', 'build'],
323
323
  { cwd: siteProjectRoot, env: npmEnv },
324
- 'presentation.typography.preset',
324
+ 'typography.preset',
325
325
  );
326
326
  await writeFile(
327
327
  siteThemePath,
@@ -390,11 +390,9 @@ This route verifies that packaged norna sites can build route pages.
390
390
  { cwd: siteProjectRoot, env: npmEnv },
391
391
  'sections.0.presentation.typography.preset',
392
392
  );
393
- const siteConfigPath = path.join(siteProjectRoot, 'site', 'config.mjs');
394
- const siteConfig = await readFile(siteConfigPath, 'utf8');
395
393
  await writeFile(
396
- siteConfigPath,
397
- siteConfig.replace(
394
+ siteThemePath,
395
+ siteTheme.replace(
398
396
  'fontFamily: "Arial, \'Helvetica Neue\', Helvetica, sans-serif"',
399
397
  'fontFamily: "Arial; color: red"',
400
398
  ),
@@ -405,9 +403,12 @@ This route verifies that packaged norna sites can build route pages.
405
403
  { cwd: siteProjectRoot, env: npmEnv },
406
404
  'typography.fontFamily',
407
405
  );
406
+ await writeFile(siteThemePath, siteTheme);
407
+ const siteConfigPath = path.join(siteProjectRoot, 'site', 'config.mjs');
408
+ const siteConfig = await readFile(siteConfigPath, 'utf8');
408
409
  await writeFile(
409
410
  siteConfigPath,
410
- siteConfig.replace("basePath: '/gallery/'", "basePath: 'gallery'"),
411
+ siteConfig.replace("basePath: '/site/'", "basePath: 'site'"),
411
412
  );
412
413
  await runExpectFailure(
413
414
  npxBin,
@@ -49,9 +49,8 @@ try {
49
49
  };
50
50
  `);
51
51
  await writeFile(path.join(siteDir, 'theme.md'), `---
52
- presentation:
53
- typography:
54
- preset: quiet-gallery
52
+ typography:
53
+ preset: quiet-gallery
55
54
  frame:
56
55
  colors: presentation
57
56
  ---
@@ -27,10 +27,9 @@ const navigationPages = getNavigationPages(sitePages);
27
27
  const homePage = sitePages.find((page) => page.isHome) ?? currentPage;
28
28
  const themeData = theme?.data ?? {};
29
29
  const brandLabel = themeData.navigation?.brand ?? homePage.title;
30
- const pagePresentation = resolvePagePresentation(themeData.presentation, entry.data.presentation);
30
+ const pagePresentation = resolvePagePresentation(themeData, entry.data.presentation);
31
31
  const frameColors = resolveFrameColors({
32
- themePresentation: themeData.presentation,
33
- themeFrame: themeData.frame,
32
+ theme: themeData,
34
33
  pagePresentation,
35
34
  pageFrame: entry.data.frame,
36
35
  });