@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
@@ -32,6 +32,9 @@ interface Props {
32
32
 
33
33
  const { section, pagePresentation, headingLevel, priorityGalleryImage = false } = Astro.props;
34
34
  const HeadingTag = headingLevel === 1 ? 'h1' : 'h2';
35
+ // Sections are authored with Markdown ## headings even when the first one renders as HTML h1.
36
+ const sectionHeadingVisualLevel = 'h2';
37
+ type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4';
35
38
  const headingSizeValues: Record<TextSize, ResponsiveValue<string>> = {
36
39
  small: {
37
40
  desktop: 'clamp(1.1rem, 1.02rem + 0.35vw, 1.35rem)',
@@ -97,10 +100,26 @@ const textColor = sectionOverride?.textColor
97
100
  ?? pagePresentation.textColor;
98
101
  const resolvedTypography = resolveTypographyOverride(pagePresentation.typography, sectionOverride?.typography);
99
102
  const typography = resolvedTypography.values;
100
- const headingAlign = typography.heading.align as ResponsiveValue<TextAlign>;
103
+ const heading = typography.headings[sectionHeadingVisualLevel] as {
104
+ align: ResponsiveValue<TextAlign>;
105
+ size: TextSize;
106
+ lineHeight: number;
107
+ spacing: string;
108
+ };
109
+ const markdownHeadingLevels = ['h3', 'h4'] as const;
110
+ const markdownHeadings = Object.fromEntries(markdownHeadingLevels.map((level) => [
111
+ level,
112
+ typography.headings[level] as {
113
+ align: ResponsiveValue<TextAlign>;
114
+ size: TextSize;
115
+ lineHeight: number;
116
+ spacing: string;
117
+ },
118
+ ]));
119
+ const headingAlign = heading.align;
101
120
  const bodyAlign = typography.body.align as ResponsiveValue<TextAlign>;
102
121
  const captionAlign = typography.caption.align as ResponsiveValue<TextAlign>;
103
- const headingFontSize = getSizeValue(headingSizeValues, typography.heading.size as TextSize);
122
+ const headingFontSize = getSizeValue(headingSizeValues, heading.size);
104
123
  const bodyFontSize = getSizeValue(bodySizeValues, typography.body.size as TextSize);
105
124
  const captionFontSize = getSizeValue(captionSizeValues, typography.caption.size as TextSize);
106
125
  const justifyByAlign: Record<TextAlign, string> = {
@@ -118,6 +137,19 @@ const getAlignedTextWidth = (
118
137
  const headingWidth = getAlignedTextWidth(headingAlign, '760px');
119
138
  const bodyWidth = getAlignedTextWidth(bodyAlign, 'var(--text-width)');
120
139
  const hasGallery = section.gallery.length > 0;
140
+ const markdownHeadingStyle = (level: 'h3' | 'h4') => {
141
+ const headingConfig = markdownHeadings[level];
142
+ const fontSize = getSizeValue(headingSizeValues, headingConfig.size);
143
+
144
+ return [
145
+ `--section-markdown-${level}-align-desktop: ${headingConfig.align.desktop}`,
146
+ `--section-markdown-${level}-align-mobile: ${headingConfig.align.mobile}`,
147
+ `--section-markdown-${level}-font-size-desktop: ${fontSize.desktop}`,
148
+ `--section-markdown-${level}-font-size-mobile: ${fontSize.mobile}`,
149
+ `--section-markdown-${level}-line-height: ${headingConfig.lineHeight}`,
150
+ `--section-markdown-${level}-spacing: ${headingConfig.spacing}`,
151
+ ];
152
+ };
121
153
  const sectionStyle = [
122
154
  `--section-background-color: ${backgroundColor}`,
123
155
  `--section-text-color: ${textColor}`,
@@ -125,10 +157,12 @@ const sectionStyle = [
125
157
  `--section-heading-align-mobile: ${headingAlign.mobile}`,
126
158
  `--section-heading-font-size-desktop: ${headingFontSize.desktop}`,
127
159
  `--section-heading-font-size-mobile: ${headingFontSize.mobile}`,
128
- `--section-heading-line-height: ${typography.heading.lineHeight}`,
129
- `--section-heading-spacing: ${typography.heading.spacing}`,
160
+ `--section-heading-line-height: ${heading.lineHeight}`,
161
+ `--section-heading-spacing: ${heading.spacing}`,
130
162
  `--section-heading-width-desktop: ${headingWidth.desktop}`,
131
163
  `--section-heading-width-mobile: ${headingWidth.mobile}`,
164
+ ...markdownHeadingStyle('h3'),
165
+ ...markdownHeadingStyle('h4'),
132
166
  `--section-body-align-desktop: ${bodyAlign.desktop}`,
133
167
  `--section-body-align-mobile: ${bodyAlign.mobile}`,
134
168
  `--section-body-font-size-desktop: ${bodyFontSize.desktop}`,
@@ -150,7 +184,7 @@ const sectionStyle = [
150
184
 
151
185
  <section
152
186
  id={section.id}
153
- class:list={['site-section', { 'site-section-primary': headingLevel === 1 }]}
187
+ class="site-section"
154
188
  data-section-title={section.title}
155
189
  style={sectionStyle}
156
190
  >
@@ -25,6 +25,12 @@ const cssLength = z.string().regex(
25
25
  /^(?:0|(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em|ch|lh))$/,
26
26
  'Use a CSS length such as "0", "0.8em", "1rem", or "12px".',
27
27
  );
28
+ const visualCssLengthUnit = String.raw`(?:px|rem|em|vw|vh|vmin|vmax|ch|%)`;
29
+ const visualCssLengthValue = String.raw`(?:\d+(?:\.\d+)?|\.\d+)${visualCssLengthUnit}`;
30
+ const visualCssLength = z.string().regex(
31
+ new RegExp(`^(?:0|${visualCssLengthValue}|clamp\\(\\s*${visualCssLengthValue}\\s*,\\s*${visualCssLengthValue}\\s*,\\s*${visualCssLengthValue}\\s*\\))$`),
32
+ 'Use a CSS length such as "0", "900px", "56rem", "90%", or "clamp(1rem, 4vw, 3rem)".',
33
+ );
28
34
  const inlineStyleName = z.string().regex(/^[a-z][a-z0-9-]*$/);
29
35
  const dateOnly = z.string()
30
36
  .regex(/^\d{4}-\d{2}-\d{2}$/, 'Use YYYY-MM-DD format.')
@@ -55,6 +61,12 @@ const headingPresentationOverride = z.object({
55
61
  ...commonTextPresentationOverride,
56
62
  spacing: cssLength.optional(),
57
63
  }).strict();
64
+ const headingLevelsPresentationOverride = z.object({
65
+ h1: headingPresentationOverride.optional(),
66
+ h2: headingPresentationOverride.optional(),
67
+ h3: headingPresentationOverride.optional(),
68
+ h4: headingPresentationOverride.optional(),
69
+ }).strict();
58
70
  const bodyPresentationOverride = z.object({
59
71
  ...commonTextPresentationOverride,
60
72
  paragraphSpacing: cssLength.optional(),
@@ -64,7 +76,7 @@ const captionPresentationOverride = z.object({
64
76
  spacing: cssLength.optional(),
65
77
  }).strict();
66
78
  const typographyOverrides = z.object({
67
- heading: headingPresentationOverride.optional(),
79
+ headings: headingLevelsPresentationOverride.optional(),
68
80
  body: bodyPresentationOverride.optional(),
69
81
  caption: captionPresentationOverride.optional(),
70
82
  }).strict();
@@ -75,6 +87,50 @@ const typography = z.object({
75
87
  (value) => value.preset !== undefined || value.overrides !== undefined,
76
88
  'Specify preset, overrides, or both.',
77
89
  );
90
+ const themeTypography = z.object({
91
+ fontFamily: z.string()
92
+ .min(1)
93
+ .refine((value) => !/[\n\r;{}]/.test(value), 'Do not use semicolons, braces, or line breaks.').optional(),
94
+ preset: typographyPreset.optional(),
95
+ overrides: typographyOverrides.optional(),
96
+ }).strict().refine(
97
+ (value) => value.fontFamily !== undefined || value.preset !== undefined || value.overrides !== undefined,
98
+ 'Specify fontFamily, preset, overrides, or both.',
99
+ );
100
+ const responsiveCssLength = z.union([
101
+ visualCssLength,
102
+ z.object({
103
+ desktop: visualCssLength,
104
+ mobile: visualCssLength,
105
+ }).strict(),
106
+ ]);
107
+ const responsivePercent = z.union([
108
+ z.number().positive().max(100),
109
+ z.object({
110
+ desktop: z.number().positive().max(100),
111
+ mobile: z.number().positive().max(100),
112
+ }).strict(),
113
+ ]);
114
+ const themeLayoutSpacing = z.object({
115
+ bodyToImages: responsiveCssLength.optional(),
116
+ finalSectionBottom: responsiveCssLength.optional(),
117
+ firstSectionTop: responsiveCssLength.optional(),
118
+ imageGap: responsiveCssLength.optional(),
119
+ sectionGap: responsiveCssLength.optional(),
120
+ sectionHeadingToBody: responsiveCssLength.optional(),
121
+ subheadingRuleTop: responsiveCssLength.optional(),
122
+ subheadingTop: responsiveCssLength.optional(),
123
+ }).strict();
124
+ const themeLayout = z.object({
125
+ pageWidth: visualCssLength.optional(),
126
+ gutter: responsiveCssLength.optional(),
127
+ spacing: themeLayoutSpacing.optional(),
128
+ }).strict();
129
+ const themeGallery = z.object({
130
+ width: visualCssLength.optional(),
131
+ maxAvailableWidthPercent: responsivePercent.optional(),
132
+ maxAvailableHeightPercent: responsivePercent.optional(),
133
+ }).strict();
78
134
  const sectionPresentationOverride = z.object({
79
135
  backgroundColor: colorValue.optional(),
80
136
  textColor: colorValue.optional(),
@@ -86,7 +142,6 @@ const themePresentation = z.object({
86
142
  inlineStyles: z.record(inlineStyleName, z.object({
87
143
  color: colorValue,
88
144
  }).strict()).optional(),
89
- typography: typography.optional(),
90
145
  }).strict();
91
146
  const pagePresentation = z.object({
92
147
  backgroundColor: colorValue.optional(),
@@ -141,6 +196,9 @@ const siteSchema = z.object({
141
196
 
142
197
  const themeSchema = z.object({
143
198
  navigation: themeNavigation.optional(),
199
+ layout: themeLayout.optional(),
200
+ gallery: themeGallery.optional(),
201
+ typography: themeTypography.optional(),
144
202
  presentation: themePresentation.optional(),
145
203
  frame: frame.optional(),
146
204
  }).strict();
@@ -29,12 +29,29 @@ const iconLinks = getIconLinks();
29
29
  const scrollBehavior = projectConfig.navigation.smoothScroll.enabled ? 'smooth' : 'auto';
30
30
  const formatPercent = (value: number) => `${value}%`;
31
31
  const formatViewportHeightPercent = (value: number) => `${value}svh`;
32
+ const layoutSpacing = projectConfig.layout.spacing;
32
33
  const documentStyle = [
33
34
  `scroll-behavior: ${scrollBehavior}`,
34
35
  `--font-sans: ${projectConfig.typography.fontFamily}`,
35
36
  `--page-width: ${projectConfig.layout.pageWidth}`,
36
37
  `--content-gutter-desktop: ${projectConfig.layout.gutter.desktop}`,
37
38
  `--content-gutter-mobile: ${projectConfig.layout.gutter.mobile}`,
39
+ `--space-nav-to-first-section-desktop: ${layoutSpacing.firstSectionTop.desktop}`,
40
+ `--space-nav-to-first-section-mobile: ${layoutSpacing.firstSectionTop.mobile}`,
41
+ `--space-section-title-to-body-desktop: ${layoutSpacing.sectionHeadingToBody.desktop}`,
42
+ `--space-section-title-to-body-mobile: ${layoutSpacing.sectionHeadingToBody.mobile}`,
43
+ `--space-section-body-to-gallery-desktop: ${layoutSpacing.bodyToImages.desktop}`,
44
+ `--space-section-body-to-gallery-mobile: ${layoutSpacing.bodyToImages.mobile}`,
45
+ `--space-gallery-item-desktop: ${layoutSpacing.imageGap.desktop}`,
46
+ `--space-gallery-item-mobile: ${layoutSpacing.imageGap.mobile}`,
47
+ `--space-section-to-section-desktop: ${layoutSpacing.sectionGap.desktop}`,
48
+ `--space-section-to-section-mobile: ${layoutSpacing.sectionGap.mobile}`,
49
+ `--space-final-section-bottom-desktop: ${layoutSpacing.finalSectionBottom.desktop}`,
50
+ `--space-final-section-bottom-mobile: ${layoutSpacing.finalSectionBottom.mobile}`,
51
+ `--space-subheading-top-desktop: ${layoutSpacing.subheadingTop.desktop}`,
52
+ `--space-subheading-top-mobile: ${layoutSpacing.subheadingTop.mobile}`,
53
+ `--space-subheading-rule-top-desktop: ${layoutSpacing.subheadingRuleTop.desktop}`,
54
+ `--space-subheading-rule-top-mobile: ${layoutSpacing.subheadingRuleTop.mobile}`,
38
55
  `--gallery-width: ${projectConfig.gallery.width}`,
39
56
  `--gallery-max-available-width-desktop: ${formatPercent(projectConfig.gallery.maxAvailableWidthPercent.desktop)}`,
40
57
  `--gallery-max-available-width-mobile: ${formatPercent(projectConfig.gallery.maxAvailableWidthPercent.mobile)}`,
@@ -28,20 +28,30 @@
28
28
  --gallery-max-image-height: var(--gallery-max-image-height-desktop);
29
29
  --nav-top-safety-inset: 0px;
30
30
 
31
- /* Avstand fran sticky navigationsrad till forsta sektionsrubriken. */
32
- --space-nav-to-first-section: clamp(1.875rem, 3vw, 2.75rem);
33
-
34
- /* Avstand fran sektionsrubrik till sektionens forsta textstycke. */
35
- --space-section-title-to-body: clamp(0.5625rem, 1.25vw, 0.75rem);
36
-
37
- /* Avstand fran sektionens lopande text till efterfoljande galleri. */
38
- --space-section-body-to-gallery: clamp(1.25rem, 2.5vw, 2rem);
39
-
40
- /* Avstand mellan enskilda bilder/verk inom samma galleri. */
41
- --space-gallery-item: clamp(1.5rem, 3.5vw, 2.75rem);
42
-
43
- /* Avstand fran sista galleriobjektet eller textblocket till nasta sektion. */
44
- --space-section-to-section: clamp(2.55rem, 4.2vw, 3.9rem);
31
+ --space-nav-to-first-section-desktop: clamp(1.875rem, 3vw, 2.75rem);
32
+ --space-nav-to-first-section-mobile: 1.375rem;
33
+ --space-nav-to-first-section: var(--space-nav-to-first-section-desktop);
34
+ --space-section-title-to-body-desktop: clamp(0.5625rem, 1.25vw, 0.75rem);
35
+ --space-section-title-to-body-mobile: 0.4375rem;
36
+ --space-section-title-to-body: var(--space-section-title-to-body-desktop);
37
+ --space-section-body-to-gallery-desktop: clamp(1.25rem, 2.5vw, 2rem);
38
+ --space-section-body-to-gallery-mobile: 1.25rem;
39
+ --space-section-body-to-gallery: var(--space-section-body-to-gallery-desktop);
40
+ --space-gallery-item-desktop: clamp(1.5rem, 3.5vw, 2.75rem);
41
+ --space-gallery-item-mobile: 2.75rem;
42
+ --space-gallery-item: var(--space-gallery-item-desktop);
43
+ --space-section-to-section-desktop: clamp(2.55rem, 4.2vw, 3.9rem);
44
+ --space-section-to-section-mobile: 2.25rem;
45
+ --space-section-to-section: var(--space-section-to-section-desktop);
46
+ --space-final-section-bottom-desktop: var(--space-section-to-section-desktop);
47
+ --space-final-section-bottom-mobile: var(--space-section-to-section-mobile);
48
+ --space-final-section-bottom: var(--space-final-section-bottom-desktop);
49
+ --space-subheading-top-desktop: 2rem;
50
+ --space-subheading-top-mobile: 1.6rem;
51
+ --space-subheading-top: var(--space-subheading-top-desktop);
52
+ --space-subheading-rule-top-desktop: 1rem;
53
+ --space-subheading-rule-top-mobile: 0.8rem;
54
+ --space-subheading-rule-top: var(--space-subheading-rule-top-desktop);
45
55
 
46
56
  --content-gutter-desktop: clamp(1.25rem, 4vw, 3rem);
47
57
  --content-gutter-mobile: 1rem;
@@ -316,7 +326,7 @@ a {
316
326
  }
317
327
 
318
328
  .site-section:last-child {
319
- padding-bottom: var(--space-section-to-section);
329
+ padding-bottom: var(--space-final-section-bottom);
320
330
  }
321
331
 
322
332
  .section-card {
@@ -331,15 +341,15 @@ a {
331
341
  text-align: var(--section-heading-align-desktop, center);
332
342
  }
333
343
 
334
- h1,
335
- h2,
344
+ .section-header h1,
345
+ .section-header h2,
336
346
  p,
337
347
  ul {
338
348
  margin-top: 0;
339
349
  }
340
350
 
341
- h1,
342
- h2 {
351
+ .section-header h1,
352
+ .section-header h2 {
343
353
  margin-bottom: 0;
344
354
  color: inherit;
345
355
  font-size: var(--section-heading-font-size-desktop, clamp(1.35rem, 1.1rem + 0.9vw, 2rem));
@@ -350,11 +360,6 @@ h2 {
350
360
  overflow-wrap: anywhere;
351
361
  }
352
362
 
353
- .site-section-primary h1 {
354
- font-size: var(--section-heading-font-size-desktop, clamp(1.6rem, 1.22rem + 1.45vw, 2.75rem));
355
- font-weight: 700;
356
- }
357
-
358
363
  .section-body {
359
364
  width: 100%;
360
365
  margin: 0 auto;
@@ -389,23 +394,25 @@ h2 {
389
394
  }
390
395
 
391
396
  .section-markdown h3 {
392
- margin: 2.75rem 0 0.9rem;
397
+ margin: var(--space-subheading-top) 0 var(--section-markdown-h3-spacing, 0.9rem);
393
398
  border-top: 1px solid var(--color-soft);
394
- padding-top: 1.75rem;
399
+ padding-top: var(--space-subheading-rule-top);
395
400
  font-family: var(--font-sans);
396
- font-size: 0.82rem;
397
- letter-spacing: 0.14em;
398
- line-height: 1.35;
401
+ font-size: var(--section-markdown-h3-font-size-desktop, 0.82rem);
402
+ letter-spacing: 0;
403
+ line-height: var(--section-markdown-h3-line-height, 1.35);
404
+ text-align: var(--section-markdown-h3-align-desktop, left);
399
405
  text-transform: uppercase;
400
406
  }
401
407
 
402
408
  .section-markdown h4 {
403
- margin: 0 0 0.85rem;
409
+ margin: 0 0 var(--section-markdown-h4-spacing, 0.85rem);
404
410
  color: inherit;
405
- font-size: clamp(0.96rem, 0.94rem + 0.08vw, 1rem);
411
+ font-size: var(--section-markdown-h4-font-size-desktop, clamp(0.96rem, 0.94rem + 0.08vw, 1rem));
406
412
  font-weight: 400;
407
- letter-spacing: 0.02em;
408
- line-height: 1.5;
413
+ letter-spacing: 0;
414
+ line-height: var(--section-markdown-h4-line-height, 1.5);
415
+ text-align: var(--section-markdown-h4-align-desktop, left);
409
416
  }
410
417
 
411
418
  .section-markdown ul,
@@ -639,11 +646,14 @@ h2 {
639
646
 
640
647
  @media (max-width: 700px) {
641
648
  :root {
642
- --space-nav-to-first-section: 1.375rem;
643
- --space-section-title-to-body: 0.4375rem;
644
- --space-section-body-to-gallery: 1.25rem;
645
- --space-gallery-item: 2.75rem;
646
- --space-section-to-section: 2.25rem;
649
+ --space-nav-to-first-section: var(--space-nav-to-first-section-mobile);
650
+ --space-section-title-to-body: var(--space-section-title-to-body-mobile);
651
+ --space-section-body-to-gallery: var(--space-section-body-to-gallery-mobile);
652
+ --space-gallery-item: var(--space-gallery-item-mobile);
653
+ --space-section-to-section: var(--space-section-to-section-mobile);
654
+ --space-final-section-bottom: var(--space-final-section-bottom-mobile);
655
+ --space-subheading-top: var(--space-subheading-top-mobile);
656
+ --space-subheading-rule-top: var(--space-subheading-rule-top-mobile);
647
657
  --content-gutter: var(--content-gutter-mobile);
648
658
  --gallery-max-available-width: var(--gallery-max-available-width-mobile);
649
659
  --gallery-max-image-height: var(--gallery-max-image-height-mobile);
@@ -780,21 +790,14 @@ h2 {
780
790
  line-height: 1.72;
781
791
  }
782
792
 
783
- h1,
784
- h2 {
793
+ .section-header h1,
794
+ .section-header h2 {
785
795
  font-size: var(
786
796
  --section-heading-font-size-mobile,
787
797
  var(--section-heading-font-size-desktop, clamp(1.3rem, 1.05rem + 1.35vw, 1.75rem))
788
798
  );
789
799
  }
790
800
 
791
- .site-section-primary h1 {
792
- font-size: var(
793
- --section-heading-font-size-mobile,
794
- var(--section-heading-font-size-desktop, clamp(1.5rem, 1.12rem + 2vw, 2.25rem))
795
- );
796
- }
797
-
798
801
  .section-header {
799
802
  width: min(100%, var(--section-heading-width-mobile, var(--section-heading-width-desktop, 760px)));
800
803
  text-align: var(--section-heading-align-mobile, var(--section-heading-align-desktop, center));
@@ -819,12 +822,13 @@ h2 {
819
822
  }
820
823
 
821
824
  .section-markdown h3 {
822
- margin-top: 2.25rem;
823
- padding-top: 1.35rem;
825
+ font-size: var(--section-markdown-h3-font-size-mobile, var(--section-markdown-h3-font-size-desktop, 0.82rem));
826
+ text-align: var(--section-markdown-h3-align-mobile, var(--section-markdown-h3-align-desktop, left));
824
827
  }
825
828
 
826
829
  .section-markdown h4 {
827
- text-align: center;
830
+ font-size: var(--section-markdown-h4-font-size-mobile, var(--section-markdown-h4-font-size-desktop, clamp(0.96rem, 0.94rem + 0.08vw, 1rem)));
831
+ text-align: var(--section-markdown-h4-align-mobile, var(--section-markdown-h4-align-desktop, left));
828
832
  }
829
833
 
830
834
  .section-markdown ul,
@@ -6,7 +6,7 @@ This is a minimal site repository starter for `@janga/norna`.
6
6
 
7
7
  ```sh
8
8
  npm install
9
- npm run norna:dev
9
+ npm run dev
10
10
  ```
11
11
 
12
12
  If the standard local port is blocked, start with:
@@ -15,9 +15,9 @@ If the standard local port is blocked, start with:
15
15
  npm run dev -- --kill
16
16
  ```
17
17
 
18
- Edit site-wide theme defaults in `site/theme.md`, homepage content and section
19
- overrides in `site/content.md`, technical settings such as URL, layout, font,
20
- and locale labels in `site/config.mjs`, source images under
18
+ Edit site-wide visual defaults in `site/theme.md`, homepage content and section
19
+ overrides in `site/content.md`, technical settings such as URL and locale
20
+ labels in `site/config.mjs`, source images under
21
21
  `site/images/<section-id>/`, and static public files under `site/public/`.
22
22
 
23
23
  Use `site.basePath: '/'` for a custom domain or root-hosted site. For a GitHub
@@ -32,14 +32,9 @@ site: {
32
32
 
33
33
  In GitHub repository settings, configure Pages to build from GitHub Actions.
34
34
 
35
- The page width is configured with `layout.pageWidth`; gallery width is
36
- configured with `gallery.width`. Side margins are configured with
37
- `layout.gutter`; image height is limited with
38
- `gallery.maxAvailableHeightPercent`.
39
-
40
- The site font is configured with `typography.fontFamily` in `site/config.mjs`.
41
- Site-wide text presentation is configured with typography presets and overrides
42
- in `site/theme.md`; page and section overrides live in `site/content.md`.
35
+ Page width, side gutters, vertical spacing, image width, image height limits,
36
+ font, colors, and site-wide typography are configured in `site/theme.md`. Page
37
+ and section presentation overrides live in `site/content.md`.
43
38
 
44
39
  Commit `package-lock.json` after the first install so GitHub Actions can use
45
40
  `npm ci`.
@@ -54,9 +49,9 @@ npm run norna:typography:show
54
49
  npm run norna:build
55
50
  ```
56
51
 
57
- This keeps gallery commands separate from repository-specific build or
58
- publishing commands in projects that embed a gallery inside a larger GitHub
59
- project. This pure starter also keeps `npm run build` as an alias for
52
+ This keeps Norna commands separate from repository-specific build or publishing
53
+ commands in projects that embed a Norna site inside a larger GitHub project.
54
+ This pure starter also keeps `npm run build` as an alias for
60
55
  `npm run norna:build`.
61
56
 
62
57
  Use `npm run norna:engine:version` to inspect the installed engine and
@@ -14,8 +14,8 @@
14
14
  "norna:config:check": "norna config:check",
15
15
  "norna:content:check": "norna content:check",
16
16
  "norna:sync": "norna content:sync",
17
- "norna:typography:presets": "norna typography:presets",
18
- "norna:typography:show": "norna typography:show",
17
+ "norna:typography:presets": "norna typography presets",
18
+ "norna:typography:show": "norna typography show",
19
19
  "norna:public": "norna site:public",
20
20
  "norna:images": "norna images",
21
21
  "norna:build": "norna build",
@@ -3,27 +3,6 @@ export default {
3
3
  url: 'https://example.com/',
4
4
  basePath: '/',
5
5
  },
6
- layout: {
7
- pageWidth: '1180px',
8
- gutter: {
9
- desktop: 'clamp(1.25rem, 4vw, 3rem)',
10
- mobile: '1rem',
11
- },
12
- },
13
- gallery: {
14
- width: '900px',
15
- maxAvailableWidthPercent: {
16
- desktop: 100,
17
- mobile: 100,
18
- },
19
- maxAvailableHeightPercent: {
20
- desktop: 74,
21
- mobile: 68,
22
- },
23
- },
24
- typography: {
25
- fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif",
26
- },
27
6
  navigation: {
28
7
  smoothScroll: {
29
8
  enabled: true,
@@ -37,11 +16,11 @@ export default {
37
16
  labels: {
38
17
  skipToContent: 'Skip to content',
39
18
  sectionNavigation: 'Sections',
40
- gallery: 'Gallery',
19
+ gallery: 'Images',
41
20
  },
42
21
  },
43
22
  footer: {
44
- copyrightMessage: '(c) Example Artist.',
23
+ copyrightMessage: '(c) Example Owner.',
45
24
  buildInfo: {
46
25
  enabled: true,
47
26
  text: 'Built',
@@ -54,7 +33,7 @@ export default {
54
33
  },
55
34
  },
56
35
  github: {
57
- repo: 'owner/example-gallery',
36
+ repo: 'owner/example-site',
58
37
  branch: 'main',
59
38
  pagesWorkflow: 'Deploy to GitHub Pages',
60
39
  },
@@ -1,23 +1,56 @@
1
1
  ---
2
- title: Example Gallery
2
+ title: Example Site
3
3
  description: Minimal norna starter site.
4
4
  sections:
5
5
  - id: intro
6
6
  presentation:
7
7
  typography:
8
8
  preset: statement
9
- gallery: []
10
9
  - id: work
11
- gallery: []
10
+ - id: lorem1
11
+ - id: lorem2
12
+ - id: lorem3
13
+
12
14
  ---
13
15
 
14
16
  ## Intro {#intro}
15
-
16
17
  This is a minimal starter site for norna. Since this site doesn't have any
17
18
  routes defined, the navigation menu will be a sticky bar with anchor links for
18
19
  ease of use on phones etc. [Highlighted inline text]{.highlight} when a short
19
20
  phrase needs emphasis
20
-
21
21
  ## Work {#work}
22
-
23
- Add gallery rows in frontmatter and matching source images under images/work/.
22
+ Add image rows in frontmatter and matching source images under images/work/.
23
+ ## What is Lorem Ipsum? {#lorem1}
24
+ Lorem Ipsum is simply dummy text of the printing and typesetting
25
+ industry. Lorem Ipsum has been the industry's standard dummy text ever
26
+ since 1966, when designers at Letraset and James Mosley, the librarian
27
+ at St Bride Printing Library in London, took a 1914 Cicero translation
28
+ and scrambled it to make dummy text for Letraset's Body Type
29
+ sheets. It has survived not only many decades, but also the leap into
30
+ electronic typesetting, remaining essentially unchanged. It was
31
+ popularised thanks to these sheets and more recently with desktop
32
+ publishing software like Aldus PageMaker and Microsoft Word including
33
+ versions of Lorem Ipsum.
34
+ ## Why do we use it? {#lorem2}
35
+ It is a long established fact that a reader will be distracted by the
36
+ readable content of a page when looking at its layout. The point of
37
+ using Lorem Ipsum is that it has a more-or-less normal distribution of
38
+ letters, as opposed to using 'Content here, content here', making it
39
+ look like readable English. Many desktop publishing packages and web
40
+ page editors now use Lorem Ipsum as their default model text, and a
41
+ search for 'lorem ipsum' will uncover many web sites still in their
42
+ infancy. Various versions have evolved over the years, sometimes by
43
+ accident, sometimes on purpose (injected humour and the like).
44
+ ## Where does it come from? {#lorem3}
45
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It
46
+ has roots in a piece of classical Latin literature from 45 BC, making
47
+ it over 2000 years old. Richard McClintock, a Latin professor at
48
+ Hampden-Sydney College in Virginia, looked up one of the more obscure
49
+ Latin words, consectetur, from a Lorem Ipsum passage, and going
50
+ through the cites of the word in classical literature, discovered the
51
+ undoubtable source. Lorem Ipsum comes from sections 1.10.32 and
52
+ 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and
53
+ Evil) by Cicero, written in 45 BC. This book is a treatise on the
54
+ theory of ethics, very popular during the Renaissance. The first line
55
+ of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in
56
+ section 1.10.32