@janga/norna 0.7.3 → 0.7.4
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.
- package/docs/README.md +3 -3
- package/docs/commands.md +2 -2
- package/docs/configuration.md +2 -1
- package/docs/design/command-organization.md +4 -3
- package/docs/design/site-examples-structure.md +4 -3
- package/docs/getting-started.md +8 -7
- package/docs/theme.md +27 -43
- package/docs/typography.md +47 -31
- package/examples/dog-gallery/site/content.md +1 -1
- package/examples/dog-gallery/site/theme.md +12 -14
- package/package.json +1 -1
- package/scripts/check-config.mjs +1 -0
- package/scripts/lib/project-config.mjs +87 -32
- package/scripts/lib/site-content.mjs +4 -4
- package/scripts/lib/typography.mjs +79 -39
- package/scripts/show-typography.mjs +75 -11
- package/scripts/test-engine-commands.mjs +18 -0
- package/scripts/test-package-check.mjs +19 -3
- package/src/components/SiteSection.astro +82 -20
- package/src/content.config.ts +11 -9
- package/src/layouts/BaseLayout.astro +0 -6
- package/src/styles/global.css +44 -17
- package/starters/basic/README.md +4 -3
- package/starters/basic/site/theme.md +19 -46
- package/starters/project/README.md +7 -7
- package/starters/project/package-lock.json +4 -4
- package/starters/project/package.json +1 -1
- package/starters/project/site/content.md +86 -46
- package/starters/project/site/routes/guide/route-content.md +52 -25
- package/starters/project/site/theme.md +29 -1
- package/examples/dog-gallery/.astro/collections/site.schema.json +0 -802
- package/examples/dog-gallery/.astro/collections/theme.schema.json +0 -714
- package/examples/dog-gallery/.astro/content-assets.mjs +0 -4
- package/examples/dog-gallery/.astro/content-modules.mjs +0 -1
- package/examples/dog-gallery/.astro/content.d.ts +0 -175
- package/examples/dog-gallery/.astro/data-store.json +0 -1
- package/examples/dog-gallery/.astro/dev-local.json +0 -8
- package/examples/dog-gallery/.astro/dev.json +0 -13
- package/examples/dog-gallery/.astro/dev.log +0 -49
- package/examples/dog-gallery/.astro/settings.json +0 -5
- package/examples/dog-gallery/.astro/types.d.ts +0 -2
- package/examples/dog-gallery/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +0 -1
- package/examples/dog-gallery/dist/_astro/SitePage.BFHZm5UG.css +0 -1
- package/examples/dog-gallery/dist/dog-care/index.html +0 -15
- package/examples/dog-gallery/dist/favicon.svg +0 -7
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-1536.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/black-puppy-meadow-7255e3d9-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/black-dogs/photo-of-a-black-dog-5cf614d2-960.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-3939.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/brown-dog-50ef114e-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-2400.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/brown-dogs/dog-accompanies-master-7ef804d5-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-1600.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/golden-retriever-f797fd88-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-1920.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-2576.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/golden-dogs/toller-puppy-a2a8fbc0-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-1277.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-cute-dog-077a0689-768.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1080.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1440.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-1536.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-480.webp +0 -0
- package/examples/dog-gallery/dist/images/generated/white-dogs/white-puppy-garden-d3ab2ca0-768.webp +0 -0
- package/examples/dog-gallery/dist/index.html +0 -43
- package/examples/dog-gallery/dist/robots.txt +0 -2
|
@@ -30,6 +30,7 @@ const knownNestedFrontmatterKeys = new Set([
|
|
|
30
30
|
'carousel',
|
|
31
31
|
'color',
|
|
32
32
|
'colors',
|
|
33
|
+
'density',
|
|
33
34
|
'desktop',
|
|
34
35
|
'fontFamily',
|
|
35
36
|
'from',
|
|
@@ -59,13 +60,12 @@ const knownNestedFrontmatterKeys = new Set([
|
|
|
59
60
|
'pageWidth',
|
|
60
61
|
'preset',
|
|
61
62
|
'presentation',
|
|
63
|
+
'rhythm',
|
|
62
64
|
'sections',
|
|
63
65
|
'sectionGap',
|
|
64
|
-
'sectionHeadingToBody',
|
|
65
66
|
'size',
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'subheadingTop',
|
|
67
|
+
'spacingAfter',
|
|
68
|
+
'spacingBefore',
|
|
69
69
|
'textColor',
|
|
70
70
|
'theme',
|
|
71
71
|
'typography',
|
|
@@ -5,171 +5,199 @@ export const typographyPresetNames = [
|
|
|
5
5
|
'statement',
|
|
6
6
|
];
|
|
7
7
|
|
|
8
|
+
export const typographyRhythmNames = [
|
|
9
|
+
'compact',
|
|
10
|
+
'normal',
|
|
11
|
+
'airy',
|
|
12
|
+
];
|
|
13
|
+
|
|
8
14
|
export const typographyPresets = {
|
|
9
15
|
'quiet-gallery': {
|
|
10
16
|
headings: {
|
|
11
17
|
h1: {
|
|
12
18
|
align: { desktop: 'left', mobile: 'left' },
|
|
13
|
-
size: '
|
|
19
|
+
size: 'medium',
|
|
14
20
|
lineHeight: 1.04,
|
|
15
|
-
spacing: '0.55em',
|
|
16
21
|
},
|
|
17
22
|
h2: {
|
|
18
23
|
align: { desktop: 'left', mobile: 'left' },
|
|
19
24
|
size: 'medium',
|
|
20
25
|
lineHeight: 1.08,
|
|
21
|
-
spacing: '0.65em',
|
|
22
26
|
},
|
|
23
27
|
h3: {
|
|
24
28
|
align: { desktop: 'left', mobile: 'left' },
|
|
25
|
-
size: '
|
|
29
|
+
size: 'medium',
|
|
26
30
|
lineHeight: 1.35,
|
|
27
|
-
spacing: '0.9rem',
|
|
28
31
|
},
|
|
29
32
|
h4: {
|
|
30
33
|
align: { desktop: 'left', mobile: 'left' },
|
|
31
34
|
size: 'medium',
|
|
32
35
|
lineHeight: 1.5,
|
|
33
|
-
spacing: '0.85rem',
|
|
34
36
|
},
|
|
35
37
|
},
|
|
36
38
|
body: {
|
|
37
39
|
align: { desktop: 'left', mobile: 'left' },
|
|
38
40
|
size: 'medium',
|
|
39
41
|
lineHeight: 1.5,
|
|
40
|
-
paragraphSpacing: '0.85em',
|
|
41
42
|
},
|
|
42
43
|
caption: {
|
|
43
44
|
align: { desktop: 'center', mobile: 'center' },
|
|
44
|
-
size: '
|
|
45
|
+
size: 'medium',
|
|
45
46
|
lineHeight: 1.35,
|
|
46
|
-
spacing: '0.5em',
|
|
47
47
|
},
|
|
48
48
|
},
|
|
49
49
|
'compact-gallery': {
|
|
50
50
|
headings: {
|
|
51
51
|
h1: {
|
|
52
52
|
align: { desktop: 'left', mobile: 'left' },
|
|
53
|
-
size: '
|
|
53
|
+
size: 'medium',
|
|
54
54
|
lineHeight: 1.04,
|
|
55
|
-
spacing: '0.45em',
|
|
56
55
|
},
|
|
57
56
|
h2: {
|
|
58
57
|
align: { desktop: 'left', mobile: 'left' },
|
|
59
58
|
size: 'medium',
|
|
60
59
|
lineHeight: 1.08,
|
|
61
|
-
spacing: '0.45em',
|
|
62
60
|
},
|
|
63
61
|
h3: {
|
|
64
62
|
align: { desktop: 'left', mobile: 'left' },
|
|
65
|
-
size: '
|
|
63
|
+
size: 'medium',
|
|
66
64
|
lineHeight: 1.3,
|
|
67
|
-
spacing: '0.75rem',
|
|
68
65
|
},
|
|
69
66
|
h4: {
|
|
70
67
|
align: { desktop: 'left', mobile: 'left' },
|
|
71
68
|
size: 'medium',
|
|
72
69
|
lineHeight: 1.42,
|
|
73
|
-
spacing: '0.6rem',
|
|
74
70
|
},
|
|
75
71
|
},
|
|
76
72
|
body: {
|
|
77
73
|
align: { desktop: 'left', mobile: 'left' },
|
|
78
74
|
size: 'medium',
|
|
79
75
|
lineHeight: 1.42,
|
|
80
|
-
paragraphSpacing: '0.6em',
|
|
81
76
|
},
|
|
82
77
|
caption: {
|
|
83
78
|
align: { desktop: 'center', mobile: 'center' },
|
|
84
|
-
size: '
|
|
79
|
+
size: 'medium',
|
|
85
80
|
lineHeight: 1.25,
|
|
86
|
-
spacing: '0.35em',
|
|
87
81
|
},
|
|
88
82
|
},
|
|
89
83
|
'text-forward': {
|
|
90
84
|
headings: {
|
|
91
85
|
h1: {
|
|
92
86
|
align: { desktop: 'left', mobile: 'left' },
|
|
93
|
-
size: '
|
|
87
|
+
size: 'medium',
|
|
94
88
|
lineHeight: 1.06,
|
|
95
|
-
spacing: '0.65em',
|
|
96
89
|
},
|
|
97
90
|
h2: {
|
|
98
91
|
align: { desktop: 'left', mobile: 'left' },
|
|
99
92
|
size: 'medium',
|
|
100
93
|
lineHeight: 1.12,
|
|
101
|
-
spacing: '0.8em',
|
|
102
94
|
},
|
|
103
95
|
h3: {
|
|
104
96
|
align: { desktop: 'left', mobile: 'left' },
|
|
105
|
-
size: '
|
|
97
|
+
size: 'medium',
|
|
106
98
|
lineHeight: 1.35,
|
|
107
|
-
spacing: '1rem',
|
|
108
99
|
},
|
|
109
100
|
h4: {
|
|
110
101
|
align: { desktop: 'left', mobile: 'left' },
|
|
111
102
|
size: 'medium',
|
|
112
103
|
lineHeight: 1.55,
|
|
113
|
-
spacing: '0.85rem',
|
|
114
104
|
},
|
|
115
105
|
},
|
|
116
106
|
body: {
|
|
117
107
|
align: { desktop: 'left', mobile: 'left' },
|
|
118
108
|
size: 'medium',
|
|
119
109
|
lineHeight: 1.62,
|
|
120
|
-
paragraphSpacing: '1em',
|
|
121
110
|
},
|
|
122
111
|
caption: {
|
|
123
112
|
align: { desktop: 'left', mobile: 'left' },
|
|
124
|
-
size: '
|
|
113
|
+
size: 'medium',
|
|
125
114
|
lineHeight: 1.4,
|
|
126
|
-
spacing: '0.6em',
|
|
127
115
|
},
|
|
128
116
|
},
|
|
129
117
|
statement: {
|
|
130
118
|
headings: {
|
|
131
119
|
h1: {
|
|
132
120
|
align: { desktop: 'left', mobile: 'left' },
|
|
133
|
-
size: '
|
|
121
|
+
size: 'medium',
|
|
134
122
|
lineHeight: 1.02,
|
|
135
|
-
spacing: '0.45em',
|
|
136
123
|
},
|
|
137
124
|
h2: {
|
|
138
125
|
align: { desktop: 'left', mobile: 'left' },
|
|
139
|
-
size: '
|
|
126
|
+
size: 'medium',
|
|
140
127
|
lineHeight: 1.04,
|
|
141
|
-
spacing: '0.5em',
|
|
142
128
|
},
|
|
143
129
|
h3: {
|
|
144
130
|
align: { desktop: 'left', mobile: 'left' },
|
|
145
131
|
size: 'medium',
|
|
146
132
|
lineHeight: 1.18,
|
|
147
|
-
spacing: '0.85rem',
|
|
148
133
|
},
|
|
149
134
|
h4: {
|
|
150
135
|
align: { desktop: 'left', mobile: 'left' },
|
|
151
136
|
size: 'medium',
|
|
152
137
|
lineHeight: 1.42,
|
|
153
|
-
spacing: '0.75rem',
|
|
154
138
|
},
|
|
155
139
|
},
|
|
156
140
|
body: {
|
|
157
141
|
align: { desktop: 'left', mobile: 'left' },
|
|
158
142
|
size: 'medium',
|
|
159
143
|
lineHeight: 1.42,
|
|
160
|
-
paragraphSpacing: '0.75em',
|
|
161
144
|
},
|
|
162
145
|
caption: {
|
|
163
146
|
align: { desktop: 'center', mobile: 'center' },
|
|
164
|
-
size: '
|
|
147
|
+
size: 'medium',
|
|
165
148
|
lineHeight: 1.3,
|
|
166
|
-
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const typographyRhythms = {
|
|
154
|
+
compact: {
|
|
155
|
+
headings: {
|
|
156
|
+
h1: { spacingBefore: '0', spacingAfter: '0.4em' },
|
|
157
|
+
h2: { spacingBefore: '0', spacingAfter: '0.45em' },
|
|
158
|
+
h3: { spacingBefore: '1.2em', spacingAfter: '0.4em' },
|
|
159
|
+
h4: { spacingBefore: '0.9em', spacingAfter: '0.35em' },
|
|
160
|
+
},
|
|
161
|
+
body: {
|
|
162
|
+
paragraphSpacing: '0.65em',
|
|
163
|
+
},
|
|
164
|
+
caption: {
|
|
165
|
+
spacingBefore: '0.35em',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
normal: {
|
|
169
|
+
headings: {
|
|
170
|
+
h1: { spacingBefore: '0', spacingAfter: '0.5em' },
|
|
171
|
+
h2: { spacingBefore: '0', spacingAfter: '0.55em' },
|
|
172
|
+
h3: { spacingBefore: '1.5em', spacingAfter: '0.5em' },
|
|
173
|
+
h4: { spacingBefore: '1.1em', spacingAfter: '0.4em' },
|
|
174
|
+
},
|
|
175
|
+
body: {
|
|
176
|
+
paragraphSpacing: '0.85em',
|
|
177
|
+
},
|
|
178
|
+
caption: {
|
|
179
|
+
spacingBefore: '0.5em',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
airy: {
|
|
183
|
+
headings: {
|
|
184
|
+
h1: { spacingBefore: '0', spacingAfter: '0.65em' },
|
|
185
|
+
h2: { spacingBefore: '0', spacingAfter: '0.7em' },
|
|
186
|
+
h3: { spacingBefore: '1.9em', spacingAfter: '0.6em' },
|
|
187
|
+
h4: { spacingBefore: '1.35em', spacingAfter: '0.5em' },
|
|
188
|
+
},
|
|
189
|
+
body: {
|
|
190
|
+
paragraphSpacing: '1em',
|
|
191
|
+
},
|
|
192
|
+
caption: {
|
|
193
|
+
spacingBefore: '0.65em',
|
|
167
194
|
},
|
|
168
195
|
},
|
|
169
196
|
};
|
|
170
197
|
|
|
171
198
|
export const defaultTypography = {
|
|
172
199
|
preset: 'quiet-gallery',
|
|
200
|
+
rhythm: 'normal',
|
|
173
201
|
};
|
|
174
202
|
|
|
175
203
|
const isPlainObject = (value) => (
|
|
@@ -196,26 +224,38 @@ export const mergeDeep = (base, override) => {
|
|
|
196
224
|
|
|
197
225
|
export const resolveTypographyConfig = (typography = defaultTypography) => {
|
|
198
226
|
const presetName = typography?.preset ?? defaultTypography.preset;
|
|
227
|
+
const rhythmName = typography?.rhythm ?? defaultTypography.rhythm;
|
|
199
228
|
const preset = typographyPresets[presetName];
|
|
229
|
+
const rhythm = typographyRhythms[rhythmName];
|
|
200
230
|
|
|
201
231
|
if (!preset) {
|
|
202
232
|
throw new Error(`Unknown typography preset: ${presetName}`);
|
|
203
233
|
}
|
|
204
234
|
|
|
235
|
+
if (!rhythm) {
|
|
236
|
+
throw new Error(`Unknown typography rhythm: ${rhythmName}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
205
239
|
return {
|
|
206
240
|
preset: presetName,
|
|
207
|
-
|
|
241
|
+
rhythm: rhythmName,
|
|
242
|
+
values: mergeDeep(mergeDeep(preset, rhythm), typography?.overrides),
|
|
208
243
|
};
|
|
209
244
|
};
|
|
210
245
|
|
|
211
246
|
export const resolveTypographyOverride = (baseResolved, typographyConfig) => {
|
|
212
|
-
if (typographyConfig?.preset) {
|
|
213
|
-
return resolveTypographyConfig(
|
|
247
|
+
if (typographyConfig?.preset || typographyConfig?.rhythm) {
|
|
248
|
+
return resolveTypographyConfig({
|
|
249
|
+
preset: typographyConfig.preset ?? baseResolved.preset,
|
|
250
|
+
rhythm: typographyConfig.rhythm ?? baseResolved.rhythm,
|
|
251
|
+
overrides: typographyConfig.overrides,
|
|
252
|
+
});
|
|
214
253
|
}
|
|
215
254
|
|
|
216
255
|
if (typographyConfig?.overrides) {
|
|
217
256
|
return {
|
|
218
257
|
preset: baseResolved.preset,
|
|
258
|
+
rhythm: baseResolved.rhythm,
|
|
219
259
|
values: mergeDeep(baseResolved.values, typographyConfig.overrides),
|
|
220
260
|
};
|
|
221
261
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
resolveTypographyConfig,
|
|
7
7
|
toYamlLines,
|
|
8
8
|
typographyPresets,
|
|
9
|
+
typographyRhythms,
|
|
9
10
|
} from './lib/typography.mjs';
|
|
10
11
|
import {
|
|
11
12
|
getContentFiles,
|
|
@@ -29,22 +30,26 @@ const typographyValuePaths = [
|
|
|
29
30
|
['headings', 'h1', 'align', 'mobile'],
|
|
30
31
|
['headings', 'h1', 'size'],
|
|
31
32
|
['headings', 'h1', 'lineHeight'],
|
|
32
|
-
['headings', 'h1', '
|
|
33
|
+
['headings', 'h1', 'spacingBefore'],
|
|
34
|
+
['headings', 'h1', 'spacingAfter'],
|
|
33
35
|
['headings', 'h2', 'align', 'desktop'],
|
|
34
36
|
['headings', 'h2', 'align', 'mobile'],
|
|
35
37
|
['headings', 'h2', 'size'],
|
|
36
38
|
['headings', 'h2', 'lineHeight'],
|
|
37
|
-
['headings', 'h2', '
|
|
39
|
+
['headings', 'h2', 'spacingBefore'],
|
|
40
|
+
['headings', 'h2', 'spacingAfter'],
|
|
38
41
|
['headings', 'h3', 'align', 'desktop'],
|
|
39
42
|
['headings', 'h3', 'align', 'mobile'],
|
|
40
43
|
['headings', 'h3', 'size'],
|
|
41
44
|
['headings', 'h3', 'lineHeight'],
|
|
42
|
-
['headings', 'h3', '
|
|
45
|
+
['headings', 'h3', 'spacingBefore'],
|
|
46
|
+
['headings', 'h3', 'spacingAfter'],
|
|
43
47
|
['headings', 'h4', 'align', 'desktop'],
|
|
44
48
|
['headings', 'h4', 'align', 'mobile'],
|
|
45
49
|
['headings', 'h4', 'size'],
|
|
46
50
|
['headings', 'h4', 'lineHeight'],
|
|
47
|
-
['headings', 'h4', '
|
|
51
|
+
['headings', 'h4', 'spacingBefore'],
|
|
52
|
+
['headings', 'h4', 'spacingAfter'],
|
|
48
53
|
['body', 'align', 'desktop'],
|
|
49
54
|
['body', 'align', 'mobile'],
|
|
50
55
|
['body', 'size'],
|
|
@@ -54,8 +59,20 @@ const typographyValuePaths = [
|
|
|
54
59
|
['caption', 'align', 'mobile'],
|
|
55
60
|
['caption', 'size'],
|
|
56
61
|
['caption', 'lineHeight'],
|
|
57
|
-
['caption', '
|
|
62
|
+
['caption', 'spacingBefore'],
|
|
58
63
|
];
|
|
64
|
+
const rhythmValuePaths = new Set([
|
|
65
|
+
'headings.h1.spacingBefore',
|
|
66
|
+
'headings.h1.spacingAfter',
|
|
67
|
+
'headings.h2.spacingBefore',
|
|
68
|
+
'headings.h2.spacingAfter',
|
|
69
|
+
'headings.h3.spacingBefore',
|
|
70
|
+
'headings.h3.spacingAfter',
|
|
71
|
+
'headings.h4.spacingBefore',
|
|
72
|
+
'headings.h4.spacingAfter',
|
|
73
|
+
'body.paragraphSpacing',
|
|
74
|
+
'caption.spacingBefore',
|
|
75
|
+
]);
|
|
59
76
|
const countIndent = (line) => line.match(/^\s*/)?.[0].length ?? 0;
|
|
60
77
|
|
|
61
78
|
const isPlainObject = (value) => (
|
|
@@ -106,12 +123,15 @@ const annotateResolvedValues = (resolved, sources) => {
|
|
|
106
123
|
return annotated;
|
|
107
124
|
};
|
|
108
125
|
|
|
109
|
-
const
|
|
126
|
+
const defaultSources = (presetName, rhythmName) => {
|
|
110
127
|
const sources = {};
|
|
111
128
|
|
|
112
129
|
for (const path of typographyValuePaths) {
|
|
130
|
+
const pathKey = path.join('.');
|
|
113
131
|
setPath(sources, path, {
|
|
114
|
-
source:
|
|
132
|
+
source: rhythmValuePaths.has(pathKey)
|
|
133
|
+
? `rhythm:${rhythmName}`
|
|
134
|
+
: `preset:${presetName}`,
|
|
115
135
|
inherited: false,
|
|
116
136
|
});
|
|
117
137
|
}
|
|
@@ -151,7 +171,7 @@ const inheritedSources = (sources) => {
|
|
|
151
171
|
const resolveAnnotatedTypographyConfig = (typographyConfig, sourceLabel) => {
|
|
152
172
|
const resolved = resolveTypographyConfig(typographyConfig ?? defaultTypography);
|
|
153
173
|
const sources = applyOverrideSources(
|
|
154
|
-
|
|
174
|
+
defaultSources(resolved.preset, resolved.rhythm),
|
|
155
175
|
typographyConfig,
|
|
156
176
|
sourceLabel,
|
|
157
177
|
);
|
|
@@ -162,14 +182,44 @@ const resolveAnnotatedTypographyConfig = (typographyConfig, sourceLabel) => {
|
|
|
162
182
|
source: typographyConfig?.preset ? sourceLabel : 'engine default',
|
|
163
183
|
...(typographyConfig?.preset ? {} : { inherited: true }),
|
|
164
184
|
},
|
|
185
|
+
rhythm: {
|
|
186
|
+
value: resolved.rhythm,
|
|
187
|
+
source: typographyConfig?.rhythm ? sourceLabel : 'engine default',
|
|
188
|
+
...(typographyConfig?.rhythm ? {} : { inherited: true }),
|
|
189
|
+
},
|
|
165
190
|
resolved,
|
|
166
191
|
sources,
|
|
167
192
|
};
|
|
168
193
|
};
|
|
169
194
|
|
|
170
195
|
const resolveAnnotatedTypographyOverride = (baseAnnotated, typographyConfig, sourceLabel) => {
|
|
171
|
-
if (typographyConfig?.preset) {
|
|
172
|
-
|
|
196
|
+
if (typographyConfig?.preset || typographyConfig?.rhythm) {
|
|
197
|
+
const resolvedConfig = {
|
|
198
|
+
preset: typographyConfig.preset ?? baseAnnotated.resolved.preset,
|
|
199
|
+
rhythm: typographyConfig.rhythm ?? baseAnnotated.resolved.rhythm,
|
|
200
|
+
overrides: typographyConfig.overrides,
|
|
201
|
+
};
|
|
202
|
+
const resolved = resolveTypographyConfig(resolvedConfig);
|
|
203
|
+
const sources = applyOverrideSources(
|
|
204
|
+
defaultSources(resolved.preset, resolved.rhythm),
|
|
205
|
+
typographyConfig,
|
|
206
|
+
sourceLabel,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
preset: {
|
|
211
|
+
value: resolved.preset,
|
|
212
|
+
source: typographyConfig.preset ? sourceLabel : baseAnnotated.preset.source,
|
|
213
|
+
...(typographyConfig.preset ? {} : { inherited: true }),
|
|
214
|
+
},
|
|
215
|
+
rhythm: {
|
|
216
|
+
value: resolved.rhythm,
|
|
217
|
+
source: typographyConfig.rhythm ? sourceLabel : baseAnnotated.rhythm.source,
|
|
218
|
+
...(typographyConfig.rhythm ? {} : { inherited: true }),
|
|
219
|
+
},
|
|
220
|
+
resolved,
|
|
221
|
+
sources,
|
|
222
|
+
};
|
|
173
223
|
}
|
|
174
224
|
|
|
175
225
|
if (typographyConfig?.overrides) {
|
|
@@ -186,6 +236,11 @@ const resolveAnnotatedTypographyOverride = (baseAnnotated, typographyConfig, sou
|
|
|
186
236
|
source: baseAnnotated.preset.source,
|
|
187
237
|
inherited: true,
|
|
188
238
|
},
|
|
239
|
+
rhythm: {
|
|
240
|
+
value: resolved.rhythm,
|
|
241
|
+
source: baseAnnotated.rhythm.source,
|
|
242
|
+
inherited: true,
|
|
243
|
+
},
|
|
189
244
|
resolved,
|
|
190
245
|
sources,
|
|
191
246
|
};
|
|
@@ -197,6 +252,11 @@ const resolveAnnotatedTypographyOverride = (baseAnnotated, typographyConfig, sou
|
|
|
197
252
|
source: baseAnnotated.preset.source,
|
|
198
253
|
inherited: true,
|
|
199
254
|
},
|
|
255
|
+
rhythm: {
|
|
256
|
+
value: baseAnnotated.resolved.rhythm,
|
|
257
|
+
source: baseAnnotated.rhythm.source,
|
|
258
|
+
inherited: true,
|
|
259
|
+
},
|
|
200
260
|
resolved: baseAnnotated.resolved,
|
|
201
261
|
sources: inheritedSources(baseAnnotated.sources),
|
|
202
262
|
};
|
|
@@ -204,6 +264,7 @@ const resolveAnnotatedTypographyOverride = (baseAnnotated, typographyConfig, sou
|
|
|
204
264
|
|
|
205
265
|
const formatAnnotatedTypography = (annotated) => ({
|
|
206
266
|
preset: annotated.preset,
|
|
267
|
+
rhythm: annotated.rhythm,
|
|
207
268
|
resolved: annotateResolvedValues(annotated.resolved, annotated.sources),
|
|
208
269
|
});
|
|
209
270
|
|
|
@@ -305,7 +366,10 @@ const readPageTypography = async (contentFile, themeTypography) => {
|
|
|
305
366
|
};
|
|
306
367
|
|
|
307
368
|
if (mode === 'presets') {
|
|
308
|
-
console.log(toYamlLines(
|
|
369
|
+
console.log(toYamlLines({
|
|
370
|
+
presets: typographyPresets,
|
|
371
|
+
rhythms: typographyRhythms,
|
|
372
|
+
}).join('\n'));
|
|
309
373
|
} else if (mode === 'show') {
|
|
310
374
|
const themeTypography = await readThemeTypography();
|
|
311
375
|
const pages = await Promise.all((await getContentFiles()).map((contentFile) => readPageTypography(contentFile, themeTypography)));
|
|
@@ -4,6 +4,7 @@ import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promi
|
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { typographyPresets, typographyRhythms } from './lib/typography.mjs';
|
|
7
8
|
|
|
8
9
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
10
|
const cliPath = path.join(repoRoot, 'bin', 'norna.mjs');
|
|
@@ -51,6 +52,23 @@ try {
|
|
|
51
52
|
assert.equal(presetsResult.status, 0, presetsResult.stderr || presetsResult.stdout);
|
|
52
53
|
assert.match(presetsResult.stdout, /quiet-gallery:/);
|
|
53
54
|
assert.match(presetsResult.stdout, /text-forward:/);
|
|
55
|
+
assert.match(presetsResult.stdout, /rhythms:/);
|
|
56
|
+
assert.match(presetsResult.stdout, /normal:/);
|
|
57
|
+
for (const [presetName, preset] of Object.entries(typographyPresets)) {
|
|
58
|
+
for (const [level, heading] of Object.entries(preset.headings)) {
|
|
59
|
+
assert.equal(heading.size, 'medium', `${presetName} ${level} should use medium size`);
|
|
60
|
+
}
|
|
61
|
+
assert.equal(preset.body.size, 'medium', `${presetName} body should use medium size`);
|
|
62
|
+
assert.equal(preset.caption.size, 'medium', `${presetName} caption should use medium size`);
|
|
63
|
+
}
|
|
64
|
+
for (const [rhythmName, rhythm] of Object.entries(typographyRhythms)) {
|
|
65
|
+
for (const [level, heading] of Object.entries(rhythm.headings)) {
|
|
66
|
+
assert.match(heading.spacingBefore, /^(0|[\d.]+em)$/, `${rhythmName} ${level} spacingBefore should be text-relative`);
|
|
67
|
+
assert.match(heading.spacingAfter, /^[\d.]+em$/, `${rhythmName} ${level} spacingAfter should be text-relative`);
|
|
68
|
+
}
|
|
69
|
+
assert.match(rhythm.body.paragraphSpacing, /^[\d.]+em$/, `${rhythmName} body paragraphSpacing should be text-relative`);
|
|
70
|
+
assert.match(rhythm.caption.spacingBefore, /^[\d.]+em$/, `${rhythmName} caption spacingBefore should be text-relative`);
|
|
71
|
+
}
|
|
54
72
|
|
|
55
73
|
const showResult = runCli(['--site-dir', path.join(initializedSiteRoot, 'site'), 'typography', 'show']);
|
|
56
74
|
assert.equal(showResult.status, 0, showResult.stderr || showResult.stdout);
|
|
@@ -235,7 +235,15 @@ 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.
|
|
238
|
+
'--section-heading-font-size-desktop: clamp(1.35rem, 1.1rem + 0.9vw, 2rem)',
|
|
239
|
+
);
|
|
240
|
+
await assertFileIncludes(
|
|
241
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
242
|
+
'--section-heading-spacing-after: 0.55em',
|
|
243
|
+
);
|
|
244
|
+
await assertFileIncludes(
|
|
245
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
246
|
+
'--section-markdown-h3-spacing-before: 1.5em',
|
|
239
247
|
);
|
|
240
248
|
await assertFileIncludes(
|
|
241
249
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
@@ -273,6 +281,14 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
273
281
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
274
282
|
'--section-caption-line-height: 1.35',
|
|
275
283
|
);
|
|
284
|
+
await assertFileIncludes(
|
|
285
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
286
|
+
'--section-caption-spacing-before: 0.5em',
|
|
287
|
+
);
|
|
288
|
+
await assertFileIncludes(
|
|
289
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
290
|
+
'--section-caption-font-size-desktop: clamp(0.86rem, 0.84rem + 0.08vw, 0.92rem)',
|
|
291
|
+
);
|
|
276
292
|
await assertFileIncludes(
|
|
277
293
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
278
294
|
'class="inline-style inline-style-highlight" style="--inline-style-color: #ffd84d"',
|
|
@@ -371,14 +387,14 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
371
387
|
siteContentPath,
|
|
372
388
|
siteContent.replace(
|
|
373
389
|
' typography:\n preset: statement',
|
|
374
|
-
' typography:\n overrides:\n caption:\n
|
|
390
|
+
' typography:\n overrides:\n caption:\n spacingBefore: wide',
|
|
375
391
|
),
|
|
376
392
|
);
|
|
377
393
|
await runExpectFailure(
|
|
378
394
|
npxBin,
|
|
379
395
|
['norna', 'build'],
|
|
380
396
|
{ cwd: siteProjectRoot, env: npmEnv },
|
|
381
|
-
'sections.0.presentation.typography.overrides.caption.
|
|
397
|
+
'sections.0.presentation.typography.overrides.caption.spacingBefore',
|
|
382
398
|
);
|
|
383
399
|
await writeFile(
|
|
384
400
|
siteContentPath,
|