@janga/norna 0.7.17 → 0.7.18
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/README.md +15 -6
- package/bin/norna-cli.mjs +1 -1
- package/docs/README.md +11 -9
- package/docs/configuration.md +25 -2
- package/docs/content.md +21 -12
- package/docs/design/navigation-and-theme-plan.md +90 -19
- package/docs/design/site-examples-structure.md +7 -3
- package/docs/engine-development.md +19 -22
- package/docs/getting-started.md +3 -3
- package/docs/images-and-metadata.md +6 -4
- package/docs/pages.md +120 -64
- package/docs/public-files.md +1 -1
- package/docs/site-files.md +36 -29
- package/docs/sitewide-content.md +1 -1
- package/docs/theme.md +107 -141
- package/docs/typography.md +15 -27
- package/examples/README.md +8 -2
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +5 -5
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +10 -10
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +35 -43
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/content.md +2 -1
- package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +2 -3
- package/examples/feature-demos/theme-preset-documentation/README.md +16 -0
- package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/content.md +1 -1
- package/examples/feature-demos/theme-preset-portfolio/README.md +16 -0
- package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/content.md +1 -1
- package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +3 -0
- package/examples/feature-demos/theme-preset-project/README.md +16 -0
- package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-project/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/content.md +1 -1
- package/examples/feature-demos/theme-preset-project/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +3 -0
- package/examples/feature-demos/theme-preset-statement/README.md +16 -0
- package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-statement/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/content.md +1 -1
- package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +3 -0
- package/fixtures/basic/site/theme.yaml +1 -8
- package/fixtures/nested-pages/.astro/collections/site.schema.json +35 -0
- package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +97 -0
- package/fixtures/nested-pages/.astro/collections/theme.schema.json +844 -0
- package/fixtures/nested-pages/.astro/content-assets.mjs +4 -0
- package/fixtures/nested-pages/.astro/content-modules.mjs +1 -0
- package/fixtures/nested-pages/.astro/content.d.ts +183 -0
- package/fixtures/nested-pages/.astro/data-store.json +1 -0
- package/fixtures/nested-pages/.astro/dev-local.json +8 -0
- package/fixtures/nested-pages/.astro/dev.log +1128 -0
- package/fixtures/nested-pages/.astro/settings.json +5 -0
- package/fixtures/nested-pages/.astro/types.d.ts +2 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +74 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/package.json +3 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
- package/fixtures/nested-pages/site/config.yaml +2 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +4 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +2 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +9 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +7 -3
- package/fixtures/nested-pages/site/theme.yaml +1 -3
- package/package.json +2 -1
- package/schemas/config.schema.json +45 -4
- package/schemas/content-frontmatter.schema.json +5 -5
- package/schemas/manifest.json +3 -2
- package/schemas/page-theme.schema.json +192 -0
- package/schemas/sitewide-content.schema.json +17 -17
- package/schemas/theme.schema.json +188 -216
- package/scripts/check-config.mjs +2 -1
- package/scripts/deploy-site.mjs +1 -1
- package/scripts/generate-images.mjs +7 -14
- package/scripts/generate-schemas.mjs +4 -1
- package/scripts/lib/editor-language-service.mjs +0 -14
- package/scripts/lib/example-sites.mjs +15 -3
- package/scripts/lib/page-model.mjs +26 -7
- package/scripts/lib/presentation.mjs +25 -17
- package/scripts/lib/project-config.mjs +26 -16
- package/scripts/lib/schema-definitions.mjs +42 -14
- package/scripts/lib/schema-editor-metadata.mjs +70 -26
- package/scripts/lib/schema-value-definitions.mjs +14 -0
- package/scripts/lib/site-content.mjs +50 -25
- package/scripts/lib/site-conventions.mjs +1 -0
- package/scripts/lib/site-paths.mjs +9 -3
- package/scripts/lib/theme-config.mjs +3 -2
- package/scripts/lib/theme-presets.mjs +54 -21
- package/scripts/show-typography.mjs +1 -33
- package/scripts/sync-content-sections.mjs +1 -3
- package/scripts/test-banner-visibility.mjs +2 -2
- package/scripts/test-client-javascript.mjs +11 -4
- package/scripts/test-content-check.mjs +25 -25
- package/scripts/test-content-model-v2.mjs +165 -101
- package/scripts/test-documentation.mjs +4 -2
- package/scripts/test-editor-language-service.mjs +11 -8
- package/scripts/test-engine-commands.mjs +5 -5
- package/scripts/test-examples.mjs +30 -1
- package/scripts/test-heading-ids.mjs +2 -2
- package/scripts/test-navigation.mjs +11 -2
- package/scripts/test-nested-pages.mjs +25 -10
- package/scripts/test-package-check.mjs +29 -60
- package/scripts/test-page-model.mjs +30 -2
- package/scripts/test-project-config.mjs +5 -10
- package/scripts/test-schemas.mjs +18 -3
- package/scripts/test-theme-presets.mjs +33 -27
- package/src/components/NavigationPageTree.astro +114 -25
- package/src/components/PageContentsLinks.astro +40 -0
- package/src/components/SectionNavigationScript.astro +12 -5
- package/src/components/SiteBreadcrumbs.astro +17 -14
- package/src/components/SiteNavigation.astro +45 -12
- package/src/components/SitePage.astro +37 -23
- package/src/components/SiteSection.astro +2 -7
- package/src/components/SiteTreeNavigation.astro +35 -0
- package/src/components/TreeNavigationScript.astro +83 -0
- package/src/content.config.ts +2 -8
- package/src/layouts/BaseLayout.astro +10 -0
- package/src/lib/pageThemes.ts +11 -9
- package/src/lib/sectionContent.ts +1 -5
- package/src/lib/siteNavigation.ts +2 -1
- package/src/lib/sitePages.ts +25 -14
- package/src/pages/index.astro +2 -1
- package/src/styles/global.css +411 -43
- package/starters/basic/README.md +7 -7
- package/starters/basic/site/theme.yaml +1 -105
- package/starters/project/README.md +8 -8
- package/starters/project/site/theme.yaml +1 -31
- package/examples/feature-demos/theme-presets/README.md +0 -18
- package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +0 -290
- package/examples/feature-demos/theme-presets/site/content.md +0 -14
- package/examples/feature-demos/theme-presets/site/theme.yaml +0 -1
- /package/examples/complete-sites/dog-shelter-multi-page/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{images → pages/000-home/images}/shelter-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images → pages/000-home/images}/foster-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images → pages/000-home/images}/rescue-dog.jpg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/feature-demos/sitewide-content/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/config.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/images/black-schnauzer.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/images/retriever-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/images/schnauzer-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/public/robots.txt +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/sitewide-content.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/puppy-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/shepherd-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/white-puppy.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/routeTheme.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/site.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/sitewide.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/theme.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-assets.mjs +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-modules.mjs +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content.d.ts +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/data-store.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev-local.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.log +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/settings.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/types.d.ts +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/_metadata.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/package.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/images/boxer-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/images/grass-puppy.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/images/grey-street-dog.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/black-pedigree.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/sheepdog-grass.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/sheepdog-wide.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site}/theme.yaml +0 -0
- /package/fixtures/basic/site/{content.md → pages/000-home/content.md} +0 -0
- /package/fixtures/basic/site/{images → pages/000-home/images}/.gitkeep +0 -0
- /package/fixtures/content-model-v2/site/{content.md → pages/000-home/content.md} +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/adopt.svg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/detail.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/duplicate.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/foster.svg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/hero.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/image.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-one.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-three.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-two.jpg +0 -0
- /package/fixtures/nested-pages/site/{content.md → pages/000-home/content.md} +0 -0
- /package/starters/basic/site/{content.md → pages/000-home/content.md} +0 -0
- /package/starters/basic/site/{images → pages/000-home/images}/.gitkeep +0 -0
- /package/starters/project/site/{content.md → pages/000-home/content.md} +0 -0
package/scripts/check-config.mjs
CHANGED
|
@@ -45,7 +45,8 @@ try {
|
|
|
45
45
|
console.log(`Theme preset: ${themeConfig.preset ?? '(none)'}`);
|
|
46
46
|
console.log(`Page width: ${projectConfig.layout.pageWidth}`);
|
|
47
47
|
console.log(`Gutter: desktop ${projectConfig.layout.gutter.desktop}, mobile ${projectConfig.layout.gutter.mobile}`);
|
|
48
|
-
console.log(`
|
|
48
|
+
console.log(`Content spacing: ${projectConfig.layout.contentSpacing}`);
|
|
49
|
+
console.log(`Text width: ${projectConfig.layout.textWidth}`);
|
|
49
50
|
console.log(`Image area width: ${projectConfig.images.width}`);
|
|
50
51
|
console.log(`Image max available width: desktop ${projectConfig.images.maxAvailableWidthPercent.desktop}%, mobile ${projectConfig.images.maxAvailableWidthPercent.mobile}%`);
|
|
51
52
|
console.log(`Image max available height: desktop ${projectConfig.images.maxAvailableHeightPercent.desktop}%, mobile ${projectConfig.images.maxAvailableHeightPercent.mobile}%`);
|
package/scripts/deploy-site.mjs
CHANGED
|
@@ -211,7 +211,7 @@ const assertDeployableStatus = async (entries, expectedImagePaths) => {
|
|
|
211
211
|
if (unexpectedUntracked.length > 0) {
|
|
212
212
|
fail([
|
|
213
213
|
'Refusing to deploy: unexpected untracked files are present.',
|
|
214
|
-
`Only new referenced images directly under
|
|
214
|
+
`Only new referenced images directly under a page's images/ directory in ${sitePagesLabel}/ are staged automatically.`,
|
|
215
215
|
...unexpectedUntracked.map((entry) => `- ${formatEntry(entry)}`),
|
|
216
216
|
].join('\n'));
|
|
217
217
|
}
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
generatedImagesManifestPath,
|
|
24
24
|
originalImagesDir,
|
|
25
25
|
siteDir,
|
|
26
|
-
siteImagesDir,
|
|
27
26
|
} from './lib/site-paths.mjs';
|
|
28
27
|
|
|
29
28
|
const execFileAsync = promisify(execFile);
|
|
@@ -88,10 +87,8 @@ const getFilePathFromPublicPath = (publicPath) => path.join(astroPublicDir, publ
|
|
|
88
87
|
|
|
89
88
|
const getSourceHashSlug = (sourceHash) => sourceHash.slice(0, sourceHashSlugLength);
|
|
90
89
|
|
|
91
|
-
const getGeneratedPath = (
|
|
92
|
-
const parsed = path.parse(
|
|
93
|
-
? path.relative(path.dirname(siteImagesDir), sourcePath)
|
|
94
|
-
: path.relative(siteImagesDir, sourcePath));
|
|
90
|
+
const getGeneratedPath = (sourceKey, sourceHash, width) => {
|
|
91
|
+
const parsed = path.parse(sourceKey);
|
|
95
92
|
return path.join(generatedImagesDir, parsed.dir, `${parsed.name}-${getSourceHashSlug(sourceHash)}-${width}.webp`);
|
|
96
93
|
};
|
|
97
94
|
|
|
@@ -104,11 +101,7 @@ const fail = (message) => {
|
|
|
104
101
|
throw new Error(message);
|
|
105
102
|
};
|
|
106
103
|
|
|
107
|
-
const getImageSourceKey = (contentFile, image) =>
|
|
108
|
-
contentFile.isHome
|
|
109
|
-
? `images/${image}`
|
|
110
|
-
: `pages/${contentFile.pageDirectory}/images/${image}`
|
|
111
|
-
);
|
|
104
|
+
const getImageSourceKey = (contentFile, image) => `pages/${contentFile.pageDirectory}/images/${image}`;
|
|
112
105
|
|
|
113
106
|
const getReferencedImages = async (contentFile) => {
|
|
114
107
|
const { body } = await readSiteFile(contentFile.contentPath, contentFile.contentLabel);
|
|
@@ -241,8 +234,8 @@ const getVariantWidths = ({ width }) => {
|
|
|
241
234
|
return variantWidths;
|
|
242
235
|
};
|
|
243
236
|
|
|
244
|
-
const getVariants = (
|
|
245
|
-
src: getPublicPath(getGeneratedPath(
|
|
237
|
+
const getVariants = (sourceKey, sourceHash, variantWidths) => variantWidths.map((width) => ({
|
|
238
|
+
src: getPublicPath(getGeneratedPath(sourceKey, sourceHash, width)),
|
|
246
239
|
width,
|
|
247
240
|
}));
|
|
248
241
|
|
|
@@ -417,9 +410,9 @@ for (const { sourcePath, sourceKey } of sources) {
|
|
|
417
410
|
|
|
418
411
|
const dimensions = await identify(sourcePath);
|
|
419
412
|
const variantWidths = getVariantWidths(dimensions);
|
|
420
|
-
const variants = getVariants(
|
|
413
|
+
const variants = getVariants(sourceKey, sourceHash, variantWidths);
|
|
421
414
|
for (const width of variantWidths) {
|
|
422
|
-
const outputPath = getGeneratedPath(
|
|
415
|
+
const outputPath = getGeneratedPath(sourceKey, sourceHash, width);
|
|
423
416
|
await convert(sourcePath, outputPath, width);
|
|
424
417
|
}
|
|
425
418
|
|
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { z } from 'astro/zod';
|
|
5
5
|
import {
|
|
6
6
|
configSchema,
|
|
7
|
+
pageThemeSchema,
|
|
7
8
|
siteSchema,
|
|
8
9
|
sitewideSchema,
|
|
9
10
|
themeVisualSchema,
|
|
@@ -15,6 +16,7 @@ const schemaDirectory = path.join(root, 'schemas');
|
|
|
15
16
|
const definitions = [
|
|
16
17
|
['config.schema.json', 'Norna site configuration', configSchema],
|
|
17
18
|
['theme.schema.json', 'Norna visual theme', themeVisualSchema],
|
|
19
|
+
['page-theme.schema.json', 'Norna page theme', pageThemeSchema],
|
|
18
20
|
['sitewide-content.schema.json', 'Norna site-wide content', sitewideSchema],
|
|
19
21
|
['content-frontmatter.schema.json', 'Norna page frontmatter', siteSchema],
|
|
20
22
|
];
|
|
@@ -30,12 +32,13 @@ for (const [filename, title, schema] of definitions) {
|
|
|
30
32
|
|
|
31
33
|
schemaFiles.set('manifest.json', `${JSON.stringify({
|
|
32
34
|
editorApiVersion: 1,
|
|
33
|
-
schemaVersion:
|
|
35
|
+
schemaVersion: 2,
|
|
34
36
|
files: {
|
|
35
37
|
config: 'config.schema.json',
|
|
36
38
|
contentFrontmatter: 'content-frontmatter.schema.json',
|
|
37
39
|
sitewideContent: 'sitewide-content.schema.json',
|
|
38
40
|
theme: 'theme.schema.json',
|
|
41
|
+
pageTheme: 'page-theme.schema.json',
|
|
39
42
|
},
|
|
40
43
|
}, null, 2)}\n`);
|
|
41
44
|
|
|
@@ -116,15 +116,6 @@ export const getSitePublicAssetStatus = async (documentPath) => {
|
|
|
116
116
|
const getPageContext = (siteRoot, documentPath) => {
|
|
117
117
|
const absoluteDocumentPath = path.resolve(documentPath);
|
|
118
118
|
const relativePath = toPosixPath(path.relative(siteRoot, absoluteDocumentPath));
|
|
119
|
-
if (relativePath === 'content.md') {
|
|
120
|
-
return {
|
|
121
|
-
contentPath: absoluteDocumentPath,
|
|
122
|
-
imagesRoot: path.join(siteRoot, 'images'),
|
|
123
|
-
pageLabel: 'Home',
|
|
124
|
-
pageDirectory: null,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
119
|
const match = relativePath.match(/^pages\/(.+)\/content\.md$/);
|
|
129
120
|
if (!match) return null;
|
|
130
121
|
|
|
@@ -316,8 +307,6 @@ const collectPageImageRoots = async (directory, siteRoot, roots) => {
|
|
|
316
307
|
|
|
317
308
|
const getContentFiles = async (siteRoot) => {
|
|
318
309
|
const files = [];
|
|
319
|
-
const homePath = path.join(siteRoot, 'content.md');
|
|
320
|
-
if (await fileExists(homePath)) files.push(homePath);
|
|
321
310
|
|
|
322
311
|
const visit = async (directory) => {
|
|
323
312
|
const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
|
|
@@ -350,9 +339,6 @@ const getReferencesByFilename = async (siteRoot) => {
|
|
|
350
339
|
|
|
351
340
|
export const createSiteImageIndex = async (siteRoot) => {
|
|
352
341
|
const files = [];
|
|
353
|
-
const homeImageRoot = path.join(siteRoot, 'images');
|
|
354
|
-
await collectImageFiles(homeImageRoot, homeImageRoot, 'Home', files);
|
|
355
|
-
|
|
356
342
|
const pageRoots = [];
|
|
357
343
|
await collectPageImageRoots(path.join(siteRoot, 'pages'), siteRoot, pageRoots);
|
|
358
344
|
for (const root of pageRoots) {
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readdir } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { homePageDirectory } from './site-conventions.mjs';
|
|
4
5
|
|
|
5
6
|
export const exampleCategories = ['complete-sites', 'feature-demos'];
|
|
6
7
|
|
|
8
|
+
const isGeneratedOnlyCacheDirectory = async (exampleDirectory, siteDirectory) => {
|
|
9
|
+
if (!existsSync(path.join(siteDirectory, '.norna'))) return false;
|
|
10
|
+
|
|
11
|
+
const exampleEntries = await readdir(exampleDirectory);
|
|
12
|
+
const siteEntries = await readdir(siteDirectory);
|
|
13
|
+
return exampleEntries.every((name) => name === 'site' || name === '.DS_Store')
|
|
14
|
+
&& siteEntries.every((name) => name === '.norna' || name === '.DS_Store');
|
|
15
|
+
};
|
|
16
|
+
|
|
7
17
|
export const getExampleSites = async (root) => {
|
|
8
18
|
const examples = [];
|
|
9
19
|
|
|
@@ -12,12 +22,14 @@ export const getExampleSites = async (root) => {
|
|
|
12
22
|
const entries = await readdir(categoryDirectory, { withFileTypes: true });
|
|
13
23
|
|
|
14
24
|
for (const entry of entries.filter((candidate) => candidate.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
15
|
-
const
|
|
25
|
+
const exampleDirectory = path.join(categoryDirectory, entry.name);
|
|
26
|
+
const siteDirectory = path.join(exampleDirectory, 'site');
|
|
16
27
|
if (!existsSync(path.join(siteDirectory, 'config.yaml'))) {
|
|
28
|
+
if (await isGeneratedOnlyCacheDirectory(exampleDirectory, siteDirectory)) continue;
|
|
17
29
|
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing config.yaml.`);
|
|
18
30
|
}
|
|
19
|
-
if (!existsSync(path.join(siteDirectory, 'content.md'))) {
|
|
20
|
-
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing content.md.`);
|
|
31
|
+
if (!existsSync(path.join(siteDirectory, 'pages', homePageDirectory, 'content.md'))) {
|
|
32
|
+
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing pages/${homePageDirectory}/content.md.`);
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
examples.push({
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { homePageDirectory } from './site-conventions.mjs';
|
|
2
|
+
|
|
3
|
+
export const pageDirectoryPattern = /^(\d{3})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
2
4
|
|
|
3
5
|
const pageEntryPathSeparator = '--';
|
|
4
6
|
|
|
@@ -7,9 +9,12 @@ export const parsePageDirectory = (pageDirectory, label = 'page directory') => {
|
|
|
7
9
|
|
|
8
10
|
if (!match) {
|
|
9
11
|
throw new Error(
|
|
10
|
-
`Invalid ${label} "${pageDirectory}". Page directories must use the form NNN-page-id, for example 010-getting-started.
|
|
12
|
+
`Invalid ${label} "${pageDirectory}". Page directories must use the form NNN-page-id, for example 010-getting-started. Page ids may contain only lowercase letters, numbers, and single hyphens.`,
|
|
11
13
|
);
|
|
12
14
|
}
|
|
15
|
+
if (match[1] === '000' && pageDirectory !== homePageDirectory) {
|
|
16
|
+
throw new Error(`Invalid ${label} "${pageDirectory}". The 000 prefix is reserved for ${homePageDirectory}.`);
|
|
17
|
+
}
|
|
13
18
|
|
|
14
19
|
return {
|
|
15
20
|
pageDirectory,
|
|
@@ -47,17 +52,31 @@ export const parsePageDirectoryPath = (pageDirectoryPath, label = 'page director
|
|
|
47
52
|
const pageIds = pageDirectories.map(({ pageId }) => pageId);
|
|
48
53
|
const pageOrders = pageDirectories.map(({ pageOrder }) => pageOrder);
|
|
49
54
|
const currentPage = pageDirectories.at(-1);
|
|
55
|
+
const homeIndex = pageDirectories.findIndex(({ pageDirectory }) => pageDirectory === homePageDirectory);
|
|
56
|
+
if (homeIndex > 0) {
|
|
57
|
+
throw new Error(`Invalid ${label} "${pageDirectoryPath}". ${homePageDirectory} is allowed only as a top-level page.`);
|
|
58
|
+
}
|
|
59
|
+
if (homeIndex === 0 && pageDirectories.length > 1) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Invalid ${label} "${pageDirectoryPath}". ${homePageDirectory} is the homepage and cannot contain child pages. Place the page beside it under site/pages/, or below another non-home page.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const isHome = homeIndex === 0;
|
|
65
|
+
const logicalPageIds = isHome ? [] : pageIds;
|
|
66
|
+
const logicalPageOrders = isHome ? [] : pageOrders;
|
|
50
67
|
|
|
51
68
|
return {
|
|
52
69
|
pageDirectory: normalizedPath,
|
|
53
70
|
pageDirectories: pageDirectories.map(({ pageDirectory }) => pageDirectory),
|
|
54
71
|
pageId: currentPage.pageId,
|
|
55
|
-
pageIds,
|
|
72
|
+
pageIds: logicalPageIds,
|
|
56
73
|
pageOrder: currentPage.pageOrder,
|
|
57
|
-
pageOrders,
|
|
58
|
-
pagePath:
|
|
59
|
-
parentPagePath:
|
|
60
|
-
|
|
74
|
+
pageOrders: logicalPageOrders,
|
|
75
|
+
pagePath: logicalPageIds.join('/'),
|
|
76
|
+
parentPagePath: logicalPageIds.length > 1
|
|
77
|
+
? logicalPageIds.slice(0, -1).join('/')
|
|
78
|
+
: null,
|
|
79
|
+
depth: pageDirectories.length,
|
|
61
80
|
};
|
|
62
81
|
};
|
|
63
82
|
|
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
import { resolveThemeConfig } from './theme-presets.mjs';
|
|
6
6
|
|
|
7
7
|
export const presentationPaletteNames = ['dark', 'light', 'paper'];
|
|
8
|
-
const
|
|
8
|
+
const sectionBackgroundPatterns = Object.freeze({
|
|
9
|
+
uniform: ['base'],
|
|
10
|
+
alternating: ['base', 'soft'],
|
|
11
|
+
cycling: ['base', 'soft', 'emphasis'],
|
|
12
|
+
});
|
|
9
13
|
|
|
10
14
|
const presentationPalettes = Object.freeze({
|
|
11
15
|
dark: {
|
|
@@ -92,34 +96,30 @@ export const getPresentationCssVariables = (presentation) => {
|
|
|
92
96
|
};
|
|
93
97
|
};
|
|
94
98
|
|
|
95
|
-
const getSectionSurfaces = (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for (const surface of value) {
|
|
101
|
-
if (!sectionSurfaceNames.includes(surface)) {
|
|
102
|
-
throw new Error(`Unknown section surface "${surface}" in ${sourceLabel}. Use one of: ${sectionSurfaceNames.join(', ')}.`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (new Set(value).size !== value.length) {
|
|
107
|
-
throw new Error(`Each section surface may appear only once in ${sourceLabel}.`);
|
|
99
|
+
const getSectionSurfaces = (pattern = 'uniform', sourceLabel = 'theme.yaml') => {
|
|
100
|
+
const surfaces = sectionBackgroundPatterns[pattern];
|
|
101
|
+
if (!surfaces) {
|
|
102
|
+
throw new Error(`sections.backgroundPattern must be one of ${Object.keys(sectionBackgroundPatterns).join(', ')} in ${sourceLabel}.`);
|
|
108
103
|
}
|
|
109
104
|
|
|
110
|
-
return
|
|
105
|
+
return surfaces;
|
|
111
106
|
};
|
|
112
107
|
|
|
113
108
|
export const resolveThemePresentation = (theme, sourceLabel = 'theme.yaml') => {
|
|
114
109
|
const normalizedTheme = resolveThemeConfig(theme, sourceLabel);
|
|
115
110
|
const paletteName = normalizedTheme.palette ?? 'dark';
|
|
116
111
|
const palette = getPresentationPalette(paletteName);
|
|
112
|
+
const typography = resolveTypographyConfig(normalizedTheme.typography ?? defaultTypography);
|
|
113
|
+
const textWidth = normalizedTheme.layout?.textWidth;
|
|
114
|
+
if (textWidth !== undefined) {
|
|
115
|
+
typography.values.body.width = textWidth;
|
|
116
|
+
}
|
|
117
117
|
|
|
118
118
|
return {
|
|
119
119
|
paletteName,
|
|
120
120
|
palette,
|
|
121
|
-
sectionSurfaces: getSectionSurfaces(normalizedTheme.
|
|
122
|
-
typography
|
|
121
|
+
sectionSurfaces: getSectionSurfaces(normalizedTheme.sections?.backgroundPattern, sourceLabel),
|
|
122
|
+
typography,
|
|
123
123
|
};
|
|
124
124
|
};
|
|
125
125
|
|
|
@@ -129,6 +129,14 @@ export const resolvePagePresentation = (theme, sourceLabel) => {
|
|
|
129
129
|
return resolvedThemePresentation;
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
+
const textWidthCssValues = Object.freeze({
|
|
133
|
+
narrow: 'min(60ch, var(--text-width))',
|
|
134
|
+
normal: 'min(72ch, var(--text-width))',
|
|
135
|
+
wide: 'min(72ch, var(--image-layout-width))',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export const getTextWidthCssValue = (textWidth) => textWidthCssValues[textWidth];
|
|
139
|
+
|
|
132
140
|
export const resolveSectionSurface = (pagePresentation, sectionIndex) => {
|
|
133
141
|
const surfaceName = pagePresentation.sectionSurfaces[sectionIndex % pagePresentation.sectionSurfaces.length];
|
|
134
142
|
|
|
@@ -187,11 +187,10 @@ const localeLabels = Object.freeze({
|
|
|
187
187
|
dismissBanner: 'Dismiss notice',
|
|
188
188
|
built: 'Built',
|
|
189
189
|
images: 'Images',
|
|
190
|
-
|
|
190
|
+
navigationChildren: 'Child pages',
|
|
191
191
|
navigationMenu: 'Menu',
|
|
192
|
-
navigationOverview: 'Overview',
|
|
193
192
|
note: 'Note',
|
|
194
|
-
pageNavigation: '
|
|
193
|
+
pageNavigation: 'Page contents',
|
|
195
194
|
siteBanners: 'Site notices',
|
|
196
195
|
siteNavigation: 'Pages',
|
|
197
196
|
skipToContent: 'Skip to content',
|
|
@@ -201,11 +200,10 @@ const localeLabels = Object.freeze({
|
|
|
201
200
|
dismissBanner: 'Stäng meddelande',
|
|
202
201
|
built: 'Byggd',
|
|
203
202
|
images: 'Bilder',
|
|
204
|
-
|
|
203
|
+
navigationChildren: 'Undersidor',
|
|
205
204
|
navigationMenu: 'Meny',
|
|
206
|
-
navigationOverview: 'Översikt',
|
|
207
205
|
note: 'Not',
|
|
208
|
-
pageNavigation: '
|
|
206
|
+
pageNavigation: 'Sidinnehåll',
|
|
209
207
|
siteBanners: 'Meddelanden',
|
|
210
208
|
siteNavigation: 'Sidor',
|
|
211
209
|
skipToContent: 'Hoppa till innehållet',
|
|
@@ -239,8 +237,8 @@ const siteUrl = readSiteUrl(rawConfig);
|
|
|
239
237
|
const scrollBehaviorNames = ['instant', 'smooth'];
|
|
240
238
|
|
|
241
239
|
const defaultFontFamily = "Arial, 'Helvetica Neue', Helvetica, sans-serif";
|
|
242
|
-
const
|
|
243
|
-
const
|
|
240
|
+
const contentSpacingNames = ['compact', 'normal', 'spacious'];
|
|
241
|
+
const contentSpacingProfiles = Object.freeze({
|
|
244
242
|
compact: Object.freeze({
|
|
245
243
|
blockGap: Object.freeze({
|
|
246
244
|
desktop: '1.25em',
|
|
@@ -293,7 +291,7 @@ const layoutDensityProfiles = Object.freeze({
|
|
|
293
291
|
mobile: '1.5rem',
|
|
294
292
|
}),
|
|
295
293
|
}),
|
|
296
|
-
|
|
294
|
+
spacious: Object.freeze({
|
|
297
295
|
blockGap: Object.freeze({
|
|
298
296
|
desktop: '1.75em',
|
|
299
297
|
mobile: '1.5em',
|
|
@@ -326,12 +324,19 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
326
324
|
const rawLayoutSpacingConfig = assertObject(rawLayoutConfig.spacing ?? {}, 'layout.spacing', sourceLabel);
|
|
327
325
|
const rawImagesConfig = assertObject(rawThemeConfig.images ?? {}, 'images', sourceLabel);
|
|
328
326
|
const rawTypographyConfig = assertObject(rawThemeConfig.typography ?? {}, 'typography', sourceLabel);
|
|
329
|
-
const
|
|
330
|
-
const resolvedLayoutSpacingDefaults =
|
|
327
|
+
const resolvedContentSpacing = readEnum(rawLayoutConfig, 'contentSpacing', 'layout', contentSpacingNames, 'normal', sourceLabel);
|
|
328
|
+
const resolvedLayoutSpacingDefaults = contentSpacingProfiles[resolvedContentSpacing];
|
|
329
|
+
const shape = readEnum(rawThemeConfig, 'shape', 'theme', ['square', 'soft'], 'soft', sourceLabel);
|
|
331
330
|
|
|
332
331
|
return Object.freeze({
|
|
333
332
|
layout: Object.freeze({
|
|
334
|
-
|
|
333
|
+
contentSpacing: resolvedContentSpacing,
|
|
334
|
+
textWidth: readEnum(rawLayoutConfig, 'textWidth', 'layout', ['narrow', 'normal', 'wide'], 'normal', sourceLabel),
|
|
335
|
+
localNavigationGap: readCssLengthValue(
|
|
336
|
+
rawLayoutConfig.localNavigationGap ?? 'clamp(1.5rem, 3vw, 3rem)',
|
|
337
|
+
'layout.localNavigationGap',
|
|
338
|
+
sourceLabel,
|
|
339
|
+
),
|
|
335
340
|
gutter: readResponsiveCssLength(rawLayoutConfig, 'gutter', 'layout', Object.freeze({
|
|
336
341
|
desktop: 'clamp(1.25rem, 4vw, 3rem)',
|
|
337
342
|
mobile: '1rem',
|
|
@@ -360,12 +365,17 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
360
365
|
typography: Object.freeze({
|
|
361
366
|
fontFamily: readFontFamily(rawTypographyConfig, 'fontFamily', 'typography', defaultFontFamily, sourceLabel),
|
|
362
367
|
}),
|
|
368
|
+
shape: Object.freeze({
|
|
369
|
+
name: shape,
|
|
370
|
+
radiusSmall: shape === 'square' ? '0' : '2px',
|
|
371
|
+
radiusMedium: shape === 'square' ? '0' : '6px',
|
|
372
|
+
radiusLarge: shape === 'square' ? '0' : '8px',
|
|
373
|
+
}),
|
|
363
374
|
});
|
|
364
375
|
};
|
|
365
376
|
|
|
366
|
-
export const
|
|
367
|
-
const
|
|
368
|
-
const rawNavigation = assertObject(resolvedTheme.navigation ?? {}, 'navigation', sourceLabel);
|
|
377
|
+
export const resolveNavigationConfig = (config, sourceLabel = siteConfigLabel) => {
|
|
378
|
+
const rawNavigation = assertObject(config.navigation ?? {}, 'navigation', sourceLabel);
|
|
369
379
|
|
|
370
380
|
return Object.freeze({
|
|
371
381
|
mode: readEnum(
|
|
@@ -386,7 +396,7 @@ export const projectConfig = Object.freeze({
|
|
|
386
396
|
}),
|
|
387
397
|
...resolveThemeVisualConfig(rawTheme, siteThemeLabel),
|
|
388
398
|
navigation: Object.freeze({
|
|
389
|
-
...
|
|
399
|
+
...resolveNavigationConfig(rawConfig, siteConfigLabel),
|
|
390
400
|
scrollBehavior: readEnum(
|
|
391
401
|
rawConfig,
|
|
392
402
|
'scrollBehavior',
|
|
@@ -11,13 +11,15 @@ const isDateOnly = (value) => {
|
|
|
11
11
|
|
|
12
12
|
const textAlign = z.enum(['left', 'center', 'right']).describe('Text alignment. Omit an override to keep the active typography profile.');
|
|
13
13
|
const textSize = z.enum(['small', 'medium', 'large', 'xlarge']).describe('Text size from the active typography system. Omit an override to keep the profile value.');
|
|
14
|
-
const textWidth = z.enum(['narrow', 'normal', 'wide']).describe('Maximum width of body text. Omit
|
|
14
|
+
const textWidth = z.enum(['narrow', 'normal', 'wide']).describe('Maximum width of body text. Omit this to keep the inherited page setting.');
|
|
15
15
|
const headingWeight = z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]).describe('CSS font weight.');
|
|
16
16
|
const typographyProfile = z.enum(['restrained', 'dense', 'reading', 'statement']).describe('Coordinated typography defaults. Omit this to use the selected preset.');
|
|
17
17
|
const themePreset = z.enum(themePresetNames).describe('Complete Norna visual preset. Start here and add overrides only when needed.');
|
|
18
18
|
const presentationPalette = z.enum(['dark', 'light', 'paper']).describe('Coordinated site color palette. Omit this to use the selected preset.');
|
|
19
|
-
const sectionSurface = z.enum(['base', 'soft', 'emphasis']).describe('Semantic section surface from the active palette.');
|
|
20
19
|
const spacingDensity = z.enum(['compact', 'normal', 'airy']).describe('Coordinated spacing density. Omit this to use the selected preset.');
|
|
20
|
+
const contentSpacing = z.enum(['compact', 'normal', 'spacious']).describe('Vertical spacing between page sections and structured content blocks.');
|
|
21
|
+
const backgroundPattern = z.enum(['uniform', 'alternating', 'cycling']).describe('How sections cycle through the active palette surfaces.');
|
|
22
|
+
const shapeProfile = z.enum(['square', 'soft']).describe('Site-wide corner treatment for interface and content surfaces.');
|
|
21
23
|
const navigationMode = z.enum(navigationModeNames).describe('Site-wide navigation model. Automatic selects from the site structure.');
|
|
22
24
|
const lineHeight = z.number()
|
|
23
25
|
.min(1, 'Use a unitless line height of at least 1.')
|
|
@@ -73,7 +75,6 @@ const headingLevelsPresentationOverride = z.object({
|
|
|
73
75
|
}).strict().describe('Heading overrides by Markdown level.');
|
|
74
76
|
const bodyPresentationOverride = z.object({
|
|
75
77
|
...commonTextPresentationOverride,
|
|
76
|
-
width: textWidth.optional().describe('Maximum body-text line length.'),
|
|
77
78
|
paragraphSpacing: cssLength.optional().describe('Vertical space between body paragraphs.'),
|
|
78
79
|
}).strict().describe('Focused overrides for body text.');
|
|
79
80
|
const captionPresentationOverride = z.object({
|
|
@@ -121,23 +122,36 @@ const themeLayoutSpacing = z.object({
|
|
|
121
122
|
sectionGap: responsiveCssLength.optional().describe('Vertical separation between page sections.'),
|
|
122
123
|
}).strict().describe('Fine-grained spacing overrides applied after layout density.');
|
|
123
124
|
const themeLayout = z.object({
|
|
124
|
-
|
|
125
|
+
contentSpacing: contentSpacing.optional().describe('Page content spacing. Omit this to keep the selected preset or inherited page setting.'),
|
|
126
|
+
textWidth: textWidth.optional().describe('Body-text line length. Omit this to keep the selected preset or inherited page setting.'),
|
|
125
127
|
pageWidth: visualCssLength.optional().describe('Maximum width of the site layout. Omit this to keep the selected preset.'),
|
|
126
128
|
gutter: responsiveCssLength.optional().describe('Horizontal page gutter as one value or separate desktop and mobile values.'),
|
|
127
129
|
spacing: themeLayoutSpacing.optional().describe('Fine-grained spacing overrides.'),
|
|
128
130
|
}).strict().describe('Optional layout overrides applied after the preset.');
|
|
131
|
+
const pageThemeLayout = z.object({
|
|
132
|
+
contentSpacing: contentSpacing.optional().describe('Page content spacing. Descendant pages inherit this value.'),
|
|
133
|
+
textWidth: textWidth.optional().describe('Body-text line length. Descendant pages inherit this value.'),
|
|
134
|
+
}).strict().refine(
|
|
135
|
+
(value) => value.contentSpacing !== undefined || value.textWidth !== undefined,
|
|
136
|
+
'Specify contentSpacing, textWidth, or both.',
|
|
137
|
+
).describe('Page-local layout settings inherited by descendant pages.');
|
|
129
138
|
const themeImages = z.object({
|
|
130
139
|
width: visualCssLength.optional().describe('Maximum managed-image width. Omit this to keep the selected preset.'),
|
|
131
140
|
maxAvailableWidthPercent: responsivePercent.optional().describe('Maximum percentage of available horizontal space used by managed images.'),
|
|
132
141
|
maxAvailableHeightPercent: responsivePercent.optional().describe('Maximum percentage of viewport height used by managed images.'),
|
|
133
142
|
}).strict().describe('Optional defaults for managed image presentation.');
|
|
134
|
-
const
|
|
135
|
-
mode: navigationMode.optional().describe('Navigation model. Omit this to
|
|
136
|
-
}).strict().describe('Site-wide navigation behavior.
|
|
137
|
-
const
|
|
138
|
-
(
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
const configNavigation = z.object({
|
|
144
|
+
mode: navigationMode.optional().describe('Navigation model. Omit this to let Norna choose from the site structure.'),
|
|
145
|
+
}).strict().describe('Site-wide navigation behavior.');
|
|
146
|
+
const themeSections = z.object({
|
|
147
|
+
backgroundPattern: backgroundPattern.optional().describe('Section background sequence. Omit this to keep the selected preset or inherited page setting.'),
|
|
148
|
+
}).strict().describe('Defaults for page section presentation.');
|
|
149
|
+
const pageThemeSections = z.object({
|
|
150
|
+
backgroundPattern: backgroundPattern.optional().describe('Section background sequence inherited by descendant pages.'),
|
|
151
|
+
}).strict().refine(
|
|
152
|
+
(value) => value.backgroundPattern !== undefined,
|
|
153
|
+
'Specify backgroundPattern.',
|
|
154
|
+
).describe('Page-local section presentation inherited by descendant pages.');
|
|
141
155
|
const pageNavigation = z.object({
|
|
142
156
|
listed: z.boolean().optional().default(true).describe('List this page in site navigation. The page remains public when false.'),
|
|
143
157
|
}).strict();
|
|
@@ -167,6 +181,7 @@ const sitewideFooter = z.object({
|
|
|
167
181
|
const configShape = {
|
|
168
182
|
url: z.string().url().describe('Absolute public URL for the built site.'),
|
|
169
183
|
language: z.string().regex(/^(?:en|sv)(?:-[a-zA-Z0-9]+)*$/).optional().describe('Site language tag using Norna\'s English or Swedish interface text; the default is en.'),
|
|
184
|
+
navigation: configNavigation.optional(),
|
|
170
185
|
scrollBehavior: z.enum(['instant', 'smooth']).optional().default('instant').describe('Use instant anchors by default or the browser\'s native smooth scrolling.'),
|
|
171
186
|
};
|
|
172
187
|
|
|
@@ -177,12 +192,18 @@ const siteShape = {
|
|
|
177
192
|
|
|
178
193
|
const themeVisualShape = {
|
|
179
194
|
preset: themePreset.optional().describe('Complete visual starting point. Add only the overrides the site actually needs.'),
|
|
180
|
-
|
|
195
|
+
shape: shapeProfile.optional().describe('Site-wide shape profile. Omit this to use the selected preset.'),
|
|
181
196
|
layout: themeLayout.optional(),
|
|
182
197
|
images: themeImages.optional(),
|
|
183
198
|
typography: themeTypography.optional(),
|
|
184
199
|
palette: presentationPalette.optional(),
|
|
185
|
-
|
|
200
|
+
sections: themeSections.optional(),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const pageThemeShape = {
|
|
204
|
+
layout: pageThemeLayout.optional(),
|
|
205
|
+
images: themeImages.optional(),
|
|
206
|
+
sections: pageThemeSections.optional(),
|
|
186
207
|
};
|
|
187
208
|
|
|
188
209
|
const sitewideShape = {
|
|
@@ -196,6 +217,7 @@ export const schemaTopLevelKeys = Object.freeze({
|
|
|
196
217
|
content: Object.freeze(Object.keys(siteShape)),
|
|
197
218
|
sitewide: Object.freeze(Object.keys(sitewideShape)),
|
|
198
219
|
theme: Object.freeze(Object.keys(themeVisualShape)),
|
|
220
|
+
pageTheme: Object.freeze(Object.keys(pageThemeShape)),
|
|
199
221
|
});
|
|
200
222
|
|
|
201
223
|
export const configSchema = z.object(configShape).strict()
|
|
@@ -203,6 +225,12 @@ export const configSchema = z.object(configShape).strict()
|
|
|
203
225
|
export const siteSchema = z.object(siteShape).strict()
|
|
204
226
|
.describe('Frontmatter for a homepage or additional page content.md file.');
|
|
205
227
|
export const themeVisualSchema = z.object(themeVisualShape).strict()
|
|
206
|
-
.describe('
|
|
228
|
+
.describe('Site-wide visual identity and default page presentation. Presets may be selected only in the root theme.');
|
|
229
|
+
export const pageThemeSchema = z.object(pageThemeShape).strict()
|
|
230
|
+
.refine(
|
|
231
|
+
(value) => value.layout !== undefined || value.images !== undefined || value.sections !== undefined,
|
|
232
|
+
'Specify layout, images, sections, or a combination of them.',
|
|
233
|
+
)
|
|
234
|
+
.describe('Limited page presentation overrides inherited by descendant pages. Site colors, shapes, typography and navigation remain global.');
|
|
207
235
|
export const sitewideSchema = z.object(sitewideShape).strict()
|
|
208
236
|
.describe('Editorial content and optional navigation logo display settings shared by every page.');
|