@janga/norna 0.7.23 → 0.7.24

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 (71) hide show
  1. package/README.md +14 -18
  2. package/astro.config.mjs +12 -0
  3. package/bin/norna-cli.mjs +6 -0
  4. package/package.json +13 -2
  5. package/schemas/category.schema.json +2 -2
  6. package/schemas/config.schema.json +45 -15
  7. package/schemas/content-frontmatter.schema.json +7 -7
  8. package/schemas/page-theme.schema.json +26 -26
  9. package/schemas/sitewide-content.schema.json +18 -18
  10. package/schemas/theme.schema.json +246 -246
  11. package/scripts/build-site.mjs +1 -0
  12. package/scripts/check-config.mjs +12 -1
  13. package/scripts/generate-search-index.mjs +57 -0
  14. package/scripts/init-site.mjs +1 -0
  15. package/scripts/lib/code-fence-metadata.mjs +223 -0
  16. package/scripts/lib/edit-source-link.mjs +49 -0
  17. package/scripts/lib/editor-language-service.mjs +32 -12
  18. package/scripts/lib/image-presentation.mjs +4 -0
  19. package/scripts/lib/navigation-model.mjs +34 -13
  20. package/scripts/lib/navigation-review.mjs +396 -0
  21. package/scripts/lib/norna-markdown-blocks.mjs +73 -26
  22. package/scripts/lib/norna-markdown-render-plugin.mjs +64 -1
  23. package/scripts/lib/page-aliases.mjs +21 -1
  24. package/scripts/lib/page-markdown.mjs +32 -1
  25. package/scripts/lib/page-move-plan.mjs +659 -0
  26. package/scripts/lib/presentation-palette-metadata.mjs +1 -1
  27. package/scripts/lib/presentation.mjs +1 -10
  28. package/scripts/lib/project-config.mjs +111 -5
  29. package/scripts/lib/public-asset-conventions.mjs +36 -2
  30. package/scripts/lib/schema-definitions.mjs +22 -5
  31. package/scripts/lib/schema-editor-metadata.mjs +19 -4
  32. package/scripts/lib/schema-value-definitions.mjs +1 -1
  33. package/scripts/lib/semantic-callouts.mjs +128 -0
  34. package/scripts/lib/site-content.mjs +2 -1
  35. package/scripts/lib/site-link-graph.mjs +33 -2
  36. package/scripts/lib/site-navigation-tree.mjs +85 -0
  37. package/scripts/lib/social-image-assets.mjs +30 -0
  38. package/scripts/lib/theme-presets.mjs +2 -2
  39. package/scripts/lib/theme-profiles.mjs +0 -4
  40. package/scripts/move-site-page.mjs +256 -0
  41. package/scripts/review-navigation.mjs +32 -0
  42. package/scripts/sync-content-sections.mjs +67 -4
  43. package/scripts/sync-site-public.mjs +24 -5
  44. package/src/components/CodeBlockCopyScript.astro +9 -4
  45. package/src/components/EditSourceLink.astro +17 -0
  46. package/src/components/ImageCarousel.astro +5 -6
  47. package/src/components/NavigationPageTree.astro +66 -55
  48. package/src/components/NavigationTreeControls.astro +77 -0
  49. package/src/components/PageAliasRedirect.astro +1 -1
  50. package/src/components/PageList.astro +33 -0
  51. package/src/components/PageSequenceNavigation.astro +37 -0
  52. package/src/components/SearchPage.astro +151 -0
  53. package/src/components/SiteNavigation.astro +35 -8
  54. package/src/components/SitePage.astro +79 -26
  55. package/src/components/SiteSection.astro +11 -3
  56. package/src/components/SiteTreeNavigation.astro +8 -1
  57. package/src/components/TreeNavigationScript.astro +207 -26
  58. package/src/layouts/BaseLayout.astro +57 -2
  59. package/src/lib/sectionContent.ts +32 -3
  60. package/src/lib/siteNavigation.ts +31 -52
  61. package/src/lib/sitePublicAssets.ts +1 -0
  62. package/src/pages/404.astro +110 -0
  63. package/src/pages/[...slug].astro +15 -5
  64. package/src/styles/content.css +242 -18
  65. package/src/styles/media.css +0 -5
  66. package/src/styles/navigation.css +31 -0
  67. package/src/styles/page-layout.css +438 -89
  68. package/src/styles/responsive.css +77 -8
  69. package/starters/basic/README.md +6 -19
  70. package/starters/basic/package.json +1 -0
  71. package/starters/basic/site/pages/000-home/content.md +13 -50
@@ -36,7 +36,16 @@ type ParsedCardListBlock = {
36
36
  width?: CardListWidth;
37
37
  cards: CardListItem[];
38
38
  };
39
- type ParsedNornaBlock = ParsedImageBlock | ParsedCardListBlock;
39
+ type ParsedPageListBlock = {
40
+ type: 'page-list';
41
+ line: number;
42
+ };
43
+ type ParsedNornaBlock = ParsedImageBlock | ParsedCardListBlock | ParsedPageListBlock;
44
+ type PageListItem = {
45
+ title: string;
46
+ pathname: string;
47
+ description?: string;
48
+ };
40
49
  type InlineNote = {
41
50
  markdown: string;
42
51
  number: number;
@@ -48,7 +57,8 @@ type SectionContentBlock =
48
57
  | { type: 'html'; html: string }
49
58
  | { type: 'image-stack'; images: ManagedImage[] }
50
59
  | { type: 'image-carousel'; images: ManagedImage[] }
51
- | { type: 'card-list'; layout: CardListLayout; flow: CardListFlow; size: CardListSize; width?: CardListWidth; cards: CardListItem[] };
60
+ | { type: 'card-list'; layout: CardListLayout; flow: CardListFlow; size: CardListSize; width?: CardListWidth; cards: CardListItem[] }
61
+ | { type: 'page-list'; pages: PageListItem[] };
52
62
  export type ResolvedSection = {
53
63
  id: string | null;
54
64
  title: string;
@@ -222,6 +232,7 @@ const resolveContentBlocks = async (
222
232
  html: string,
223
233
  blocks: ParsedNornaBlock[],
224
234
  page: SitePage,
235
+ childPages: SitePage[],
225
236
  inlineNotes: InlineNote[] = [],
226
237
  ) => {
227
238
  const renderedNotes = await Promise.all(inlineNotes.map(async (note) => ({
@@ -252,6 +263,20 @@ const resolveContentBlocks = async (
252
263
  });
253
264
  continue;
254
265
  }
266
+ if (block.type === 'page-list') {
267
+ if (childPages.length === 0) {
268
+ throw new Error(`${page.contentLabel} line ${block.line}: page-list has no listed direct child pages to display. Navigation categories are not pages. Add a listed direct child page or remove the block.`);
269
+ }
270
+ resolvedBlocks.push({
271
+ type: 'page-list',
272
+ pages: childPages.map((childPage) => ({
273
+ title: childPage.title,
274
+ pathname: childPage.pathname,
275
+ description: childPage.entry.data.page?.description,
276
+ })),
277
+ });
278
+ continue;
279
+ }
255
280
 
256
281
  resolvedBlocks.push({
257
282
  type: block.type,
@@ -268,6 +293,7 @@ const resolveContentBlocks = async (
268
293
  export const getSectionsContent = async (
269
294
  html: string,
270
295
  page: SitePage,
296
+ childPages: SitePage[] = [],
271
297
  ) => {
272
298
  const pageDocument = page.markdownDocument;
273
299
  const sourceLabel = page.contentLabel;
@@ -307,6 +333,9 @@ export const getSectionsContent = async (
307
333
  if (bodySection.noteErrors.length > 0) {
308
334
  throw new Error(bodySection.noteErrors[0].message);
309
335
  }
336
+ if (bodySection.calloutErrors.length > 0) {
337
+ throw new Error(bodySection.calloutErrors[0].message);
338
+ }
310
339
 
311
340
  const inlineNotes = bodySection.notes.map((note) => {
312
341
  const number = nextNoteNumber;
@@ -326,7 +355,7 @@ export const getSectionsContent = async (
326
355
  title,
327
356
  titleHtml: await renderHeadingTitleHtml(bodySection.heading.source),
328
357
  headingLevel,
329
- contentBlocks: await resolveContentBlocks(content, bodySection.blocks, page, inlineNotes),
358
+ contentBlocks: await resolveContentBlocks(content, bodySection.blocks, page, childPages, inlineNotes),
330
359
  });
331
360
  }
332
361
 
@@ -1,5 +1,12 @@
1
1
  import type { HeadingNavigation, SectionNavigation } from './sectionContent';
2
2
  import type { SiteNode, SitePage } from './sitePages';
3
+ import {
4
+ flattenSiteNavigationTree as flattenSiteNavigationTreeShared,
5
+ getDirectChildPages as getDirectChildPagesShared,
6
+ getFirstPageInNavigationNode as getFirstPageInNavigationNodeShared,
7
+ getListedSiteNavigationTree as getListedSiteNavigationTreeShared,
8
+ getSequentialPageNavigation as getSequentialPageNavigationShared,
9
+ } from '../../scripts/lib/site-navigation-tree.mjs';
3
10
 
4
11
  export type SiteNavigationEntry = {
5
12
  node: SiteNode;
@@ -11,57 +18,29 @@ export type SiteNavigationNode = SiteNavigationEntry & {
11
18
  children: SiteNavigationNode[];
12
19
  };
13
20
 
14
- export const getSiteNavigationTree = (entries: SiteNavigationEntry[]) => {
15
- const nodes = new Map<string, SiteNavigationNode>();
16
- const roots: SiteNavigationNode[] = [];
17
-
18
- for (const entry of entries) {
19
- nodes.set(entry.node.pagePath, { ...entry, children: [] });
20
- }
21
-
22
- for (const entry of entries) {
23
- const node = nodes.get(entry.node.pagePath);
24
- if (!node) continue;
25
-
26
- if (entry.node.isHome || entry.node.parentPagePath === null) {
27
- roots.push(node);
28
- continue;
29
- }
30
-
31
- const parent = nodes.get(entry.node.parentPagePath);
32
- if (!parent) {
33
- throw new Error(`Site node "${entry.node.pagePath}" has no parent node "${entry.node.parentPagePath}".`);
34
- }
35
-
36
- parent.children.push(node);
37
- }
38
-
39
- return roots;
40
- };
41
-
42
- export const getListedSiteNavigationTree = (entries: SiteNavigationEntry[]) => {
43
- const filterListed = (nodes: SiteNavigationNode[]): SiteNavigationNode[] => nodes.flatMap((node) => {
44
- if (!node.node.isHome && !node.node.navigation.listed) return [];
45
- const children = filterListed(node.children);
46
- if (node.node.kind === 'category' && children.length === 0) return [];
47
- return [{ ...node, children }];
48
- });
49
-
50
- return filterListed(getSiteNavigationTree(entries));
51
- };
52
-
53
- export const getFirstPageInNavigationNode = (node: SiteNavigationNode): SitePage | null => {
54
- if (node.node.kind === 'page') return node.node;
55
-
56
- for (const child of node.children) {
57
- const page = getFirstPageInNavigationNode(child);
58
- if (page) return page;
21
+ export const getListedSiteNavigationTree = (entries: SiteNavigationEntry[]) => (
22
+ getListedSiteNavigationTreeShared(entries) as SiteNavigationNode[]
23
+ );
24
+
25
+ export const getFirstPageInNavigationNode = (node: SiteNavigationNode): SitePage | null => (
26
+ getFirstPageInNavigationNodeShared(node) as SitePage | null
27
+ );
28
+
29
+ export const flattenSiteNavigationTree = (nodes: SiteNavigationNode[]): SiteNavigationNode[] => (
30
+ flattenSiteNavigationTreeShared(nodes) as SiteNavigationNode[]
31
+ );
32
+
33
+ export const getSequentialPageNavigation = (
34
+ nodes: SiteNavigationNode[],
35
+ currentPagePath: string,
36
+ ): { previous: SitePage | null; next: SitePage | null } => (
37
+ getSequentialPageNavigationShared(nodes, currentPagePath) as {
38
+ previous: SitePage | null;
39
+ next: SitePage | null;
59
40
  }
41
+ );
60
42
 
61
- return null;
62
- };
63
-
64
- export const flattenSiteNavigationTree = (nodes: SiteNavigationNode[]): SiteNavigationNode[] => nodes.flatMap((node) => [
65
- node,
66
- ...flattenSiteNavigationTree(node.children),
67
- ]);
43
+ export const getDirectChildPages = (
44
+ nodes: SiteNavigationNode[],
45
+ currentPagePath: string,
46
+ ): SitePage[] => getDirectChildPagesShared(nodes, currentPagePath) as SitePage[];
@@ -4,6 +4,7 @@ import { sitePublicDir } from '../../scripts/lib/site-paths.mjs';
4
4
  import { browserIconAssetDefinitions } from '../../scripts/lib/public-asset-conventions.mjs';
5
5
  import projectConfig from '../../scripts/lib/project-config.mjs';
6
6
  export { getLogoAsset } from '../../scripts/lib/logo-assets.mjs';
7
+ export { getSocialImageAsset } from '../../scripts/lib/social-image-assets.mjs';
7
8
  import { withBasePath } from './basePath';
8
9
 
9
10
  type IconLink = {
@@ -0,0 +1,110 @@
1
+ ---
2
+ import { getCollection } from 'astro:content';
3
+ import { resolveNavigationModel } from '../../scripts/lib/navigation-model.mjs';
4
+ import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
5
+ import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
6
+ import SiteBanners from '../components/SiteBanners.astro';
7
+ import SiteNavigation from '../components/SiteNavigation.astro';
8
+ import SiteSection from '../components/SiteSection.astro';
9
+ import BaseLayout from '../layouts/BaseLayout.astro';
10
+ import type { ResolvedSection } from '../lib/sectionContent';
11
+ import { flattenSiteNavigationTree, getListedSiteNavigationTree } from '../lib/siteNavigation';
12
+ import { getSiteModel, type SitePage } from '../lib/sitePages';
13
+ import { getLogoAsset } from '../lib/sitePublicAssets';
14
+ import { getToday, isVisibleToday } from '../lib/visibility';
15
+
16
+ const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
17
+ const siteModel = await getSiteModel(await getCollection('site'));
18
+ const { nodes: siteNodes, pages: sitePages } = siteModel;
19
+ const homePage = sitePages.find((page) => page.isHome);
20
+ if (!homePage) throw new Error('The required homepage is missing from the site page model.');
21
+
22
+ const [siteTheme] = await getCollection('theme');
23
+ if (!siteTheme) throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
24
+ const [sitewide] = await getCollection('sitewide');
25
+ const sitewideData = sitewide?.data ?? {};
26
+ const siteThemeData = siteTheme.data;
27
+ const themeLabel = `${siteDirectory || 'site'}/${siteTheme.id}`;
28
+ const visualTheme = resolveThemeVisualConfig(siteThemeData, themeLabel);
29
+ const getPageContent = (page: SitePage) => page.markdownDocument.navigationHeadings.map((heading) => ({ ...heading }));
30
+ const siteNavigation = siteNodes.map((node) => {
31
+ const headings = node.kind === 'page' ? getPageContent(node) : [];
32
+ return {
33
+ node,
34
+ headings,
35
+ sections: headings
36
+ .filter((heading) => heading.depth === 2)
37
+ .map(({ id, title }) => ({ id, title })),
38
+ };
39
+ });
40
+ const renderedNavigationEntries = flattenSiteNavigationTree(getListedSiteNavigationTree(siteNavigation));
41
+ const navigationModel = resolveNavigationModel({
42
+ mode: projectConfig.navigation.mode,
43
+ nodes: renderedNavigationEntries.map(({ node, headings }) => ({
44
+ depth: Math.max(1, node.depth),
45
+ headings,
46
+ isHome: node.isHome,
47
+ kind: node.kind,
48
+ listed: node.navigation.listed,
49
+ pagePath: node.pagePath,
50
+ })),
51
+ });
52
+ const pagePresentation = resolvePagePresentation(siteThemeData, themeLabel, {
53
+ navigationMode: navigationModel.mode,
54
+ });
55
+ const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
56
+ const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: themeLabel });
57
+ const logoAsset = getLogoAsset();
58
+ const logo = logoAsset
59
+ ? {
60
+ src: logoAsset.href,
61
+ alt: homePage.title,
62
+ height: sitewideData.logo?.height,
63
+ }
64
+ : undefined;
65
+ const today = getToday();
66
+ const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
67
+ const notFoundSection: ResolvedSection = {
68
+ id: null,
69
+ title: projectConfig.locale.labels.notFound,
70
+ titleHtml: projectConfig.locale.labels.notFound,
71
+ headingLevel: 1,
72
+ contentBlocks: [{
73
+ type: 'html',
74
+ html: `<p>${projectConfig.locale.labels.notFoundText}</p><p><a href="${projectConfig.site.basePath}">${projectConfig.locale.labels.returnHome}</a></p>`,
75
+ }],
76
+ };
77
+ const hasNavigationRailFrame = navigationModel.requestedMode === 'tree'
78
+ || (
79
+ navigationModel.requestedMode === 'automatic'
80
+ && (navigationModel.hasNestedPages || navigationModel.hasCategories)
81
+ );
82
+ ---
83
+
84
+ <BaseLayout
85
+ title={projectConfig.locale.labels.notFound}
86
+ presentation={pagePresentation}
87
+ visualTheme={visualTheme}
88
+ frameColors={frameColors}
89
+ footer={sitewideData.footer}
90
+ indexable={false}
91
+ >
92
+ <SiteNavigation
93
+ logo={logo}
94
+ navigationEntries={siteNavigation}
95
+ navigationMode={navigationModel.mode}
96
+ showTopNavigationSubmenus={navigationModel.requestedMode !== 'automatic' && navigationModel.mode !== 'tree'}
97
+ hasNavigationRailFrame={hasNavigationRailFrame}
98
+ frameColors={frameColors}
99
+ readerPreferences={pagePresentation.readerPreferences}
100
+ />
101
+ {siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
102
+ <main class="site-content" id="main-content" style={`--page-text-width: ${pageTextWidth}`}>
103
+ <SiteSection
104
+ section={notFoundSection}
105
+ pagePresentation={pagePresentation}
106
+ visualTheme={visualTheme}
107
+ sectionIndex={0}
108
+ />
109
+ </main>
110
+ </BaseLayout>
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  import { getCollection } from 'astro:content';
3
3
  import { assertPageAliasModel, createPageAliasModel } from '../../scripts/lib/page-aliases.mjs';
4
+ import { getGeneratedSiteRoutes } from '../../scripts/lib/page-aliases.mjs';
4
5
  import { getSitePublicFiles } from '../../scripts/lib/site-link-graph.mjs';
6
+ import projectConfig from '../../scripts/lib/project-config.mjs';
5
7
  import PageAliasRedirect from '../components/PageAliasRedirect.astro';
8
+ import SearchPage from '../components/SearchPage.astro';
6
9
  import SitePage from '../components/SitePage.astro';
7
10
  import { getSiteModel, getSitePage, isHomePageEntry } from '../lib/sitePages';
8
11
 
@@ -13,6 +16,7 @@ export async function getStaticPaths() {
13
16
  categories: siteModel.categories,
14
17
  pages: siteModel.pages,
15
18
  publicFiles: await getSitePublicFiles(),
19
+ generatedRoutes: getGeneratedSiteRoutes({ searchEnabled: projectConfig.search.enabled }),
16
20
  }));
17
21
  const pagesByPathname = new Map(siteModel.pages.map((page) => [page.pathname, page]));
18
22
 
@@ -37,22 +41,28 @@ export async function getStaticPaths() {
37
41
  };
38
42
  });
39
43
 
40
- return [...pagePaths, ...aliasPaths];
44
+ const searchPath = projectConfig.search.enabled
45
+ ? [{ params: { slug: 'search' }, props: { searchPage: true } }]
46
+ : [];
47
+
48
+ return [...pagePaths, ...aliasPaths, ...searchPath];
41
49
  }
42
50
 
43
- const { alias, entry } = Astro.props;
51
+ const { alias, entry, searchPage } = Astro.props;
44
52
 
45
- if (!alias && (!entry || isHomePageEntry(entry))) {
53
+ if (!searchPage && !alias && (!entry || isHomePageEntry(entry))) {
46
54
  throw new Error('Page entry is missing.');
47
55
  }
48
56
 
49
57
  const currentPage = entry ? await getSitePage(entry) : null;
50
58
 
51
- if (!alias && !currentPage?.pathSegment) {
59
+ if (!searchPage && !alias && !currentPage?.pathSegment) {
52
60
  throw new Error(`Page "${entry.id}" needs a page id.`);
53
61
  }
54
62
  ---
55
63
 
56
- {alias
64
+ {searchPage
65
+ ? <SearchPage />
66
+ : alias
57
67
  ? <PageAliasRedirect targetPathname={alias.targetPathname} targetTitle={alias.targetTitle} />
58
68
  : <SitePage entry={entry} />}
@@ -252,24 +252,6 @@ ul {
252
252
  min-width: 0;
253
253
  }
254
254
 
255
- @media (min-width: 1101px) {
256
- @container (min-width: 55rem) {
257
- .section-note {
258
- float: right;
259
- clear: right;
260
- width: var(--layout-note-width);
261
- margin: 0 calc(0px - var(--layout-note-width) - var(--layout-note-gap)) 0.75rem var(--layout-note-gap);
262
- }
263
- }
264
-
265
- .site-page-layout-contents .section-note {
266
- float: none;
267
- clear: none;
268
- width: 100%;
269
- margin: var(--section-body-paragraph-spacing, 0.85em) 0 0;
270
- }
271
- }
272
-
273
255
  .section-markdown {
274
256
  width: var(--layout-reading-width);
275
257
  max-width: none;
@@ -280,6 +262,95 @@ ul {
280
262
  text-align: var(--section-body-align-desktop, left);
281
263
  }
282
264
 
265
+ .norna-search {
266
+ --pagefind-ui-scale: 0.88;
267
+ --pagefind-ui-primary: var(--color-link-text);
268
+ --pagefind-ui-text: var(--color-primary-text);
269
+ --pagefind-ui-background: var(--color-page);
270
+ --pagefind-ui-border: var(--color-nav-separator);
271
+ --pagefind-ui-tag: var(--color-surface-soft-background);
272
+ --pagefind-ui-border-width: 1px;
273
+ --pagefind-ui-border-radius: var(--corner-radius-medium);
274
+ --pagefind-ui-image-border-radius: var(--corner-radius-medium);
275
+ --pagefind-ui-font: var(--font-sans);
276
+ width: var(--layout-reading-width);
277
+ margin: var(--space-content-block-gap) var(--layout-inline-end-margin) 0 var(--layout-inline-start-margin);
278
+ }
279
+
280
+ .norna-search > [data-search-status] {
281
+ margin: 0;
282
+ color: var(--color-secondary-text);
283
+ }
284
+
285
+ .child-page-list {
286
+ width: var(--layout-reading-width);
287
+ margin-block: 0;
288
+ margin-inline: var(--layout-inline-start-margin) var(--layout-inline-end-margin);
289
+ font-size: var(--section-body-font-size-desktop, clamp(0.96rem, 0.94rem + 0.08vw, 1rem));
290
+ line-height: var(--section-body-line-height, 1.6);
291
+ text-align: left;
292
+ }
293
+
294
+ .child-page-list ul {
295
+ display: grid;
296
+ gap: 0;
297
+ margin: 0;
298
+ padding: 0;
299
+ list-style: none;
300
+ }
301
+
302
+ .child-page-list li {
303
+ margin: 0;
304
+ border-top: 1px solid var(--color-nav-separator);
305
+ }
306
+
307
+ .child-page-list li:last-child {
308
+ border-bottom: 1px solid var(--color-nav-separator);
309
+ }
310
+
311
+ .child-page-list a {
312
+ display: grid;
313
+ grid-template-columns: minmax(0, 1fr) auto;
314
+ align-items: center;
315
+ gap: 1rem;
316
+ padding: 0.9rem 0.15rem;
317
+ color: inherit;
318
+ text-decoration: none;
319
+ }
320
+
321
+ .child-page-list a:hover,
322
+ .child-page-list a:focus-visible {
323
+ background: var(--color-surface-soft-background);
324
+ }
325
+
326
+ .child-page-list-copy {
327
+ display: grid;
328
+ min-width: 0;
329
+ gap: 0.2rem;
330
+ }
331
+
332
+ .child-page-list-copy strong {
333
+ font-size: 1em;
334
+ font-weight: 650;
335
+ line-height: 1.35;
336
+ }
337
+
338
+ .child-page-list-copy > span {
339
+ color: var(--section-secondary-text-color, var(--color-secondary-text));
340
+ font-size: 0.9em;
341
+ line-height: 1.45;
342
+ }
343
+
344
+ .child-page-list svg {
345
+ width: 1rem;
346
+ height: 1rem;
347
+ stroke-width: 1.75;
348
+ }
349
+
350
+ .section-body > .child-page-list:not(:last-child) {
351
+ margin-bottom: var(--space-content-block-gap);
352
+ }
353
+
283
354
  .section-markdown table {
284
355
  display: block;
285
356
  max-width: 100%;
@@ -363,6 +434,61 @@ ul {
363
434
  margin-bottom: 0;
364
435
  }
365
436
 
437
+ .section-markdown .norna-callout {
438
+ --callout-accent: var(--color-accent);
439
+ --callout-background: var(--color-surface-soft-background);
440
+ --callout-text: var(--color-surface-soft-text);
441
+ display: flow-root;
442
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
443
+ border: 1px solid color-mix(in srgb, var(--callout-accent) 52%, transparent);
444
+ border-inline-start: 0.3rem solid var(--callout-accent);
445
+ border-radius: var(--corner-radius-medium, 6px);
446
+ padding: 0.85rem 1rem;
447
+ background: var(--callout-background);
448
+ color: var(--callout-text);
449
+ }
450
+
451
+ .section-markdown .norna-callout-important {
452
+ --callout-background: var(--color-surface-emphasis-background);
453
+ --callout-text: var(--color-surface-emphasis-text);
454
+ }
455
+
456
+ .section-markdown .norna-callout-tip {
457
+ --callout-accent: var(--color-success-accent);
458
+ --callout-background: var(--color-success-surface);
459
+ --callout-text: var(--color-success-text);
460
+ }
461
+
462
+ .section-markdown :is(.norna-callout-warning, .norna-callout-caution) {
463
+ --callout-accent: var(--color-warning-accent);
464
+ --callout-background: var(--color-warning-surface);
465
+ --callout-text: var(--color-warning-text);
466
+ }
467
+
468
+ .section-markdown .norna-callout-danger {
469
+ --callout-accent: var(--color-error-accent);
470
+ --callout-background: var(--color-error-surface);
471
+ --callout-text: var(--color-error-text);
472
+ }
473
+
474
+ .section-markdown .norna-callout-label {
475
+ margin: 0 0 0.35rem;
476
+ color: inherit;
477
+ font-family: var(--font-sans);
478
+ font-size: 0.78rem;
479
+ font-weight: 700;
480
+ letter-spacing: 0;
481
+ line-height: 1.35;
482
+ }
483
+
484
+ .section-markdown .norna-callout a {
485
+ color: inherit;
486
+ }
487
+
488
+ .section-markdown .norna-callout > :last-child {
489
+ margin-bottom: 0;
490
+ }
491
+
366
492
  .section-markdown pre.astro-code {
367
493
  margin: 0 0 var(--section-body-paragraph-spacing, 1em);
368
494
  border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
@@ -380,11 +506,55 @@ ul {
380
506
  margin: 0 0 var(--section-body-paragraph-spacing, 1em);
381
507
  }
382
508
 
509
+ .section-markdown .norna-code-example {
510
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
511
+ border: 1px solid color-mix(in srgb, var(--color-code-text) 18%, transparent);
512
+ border-radius: var(--corner-radius-medium, 6px);
513
+ overflow: hidden;
514
+ background: var(--color-code-background);
515
+ color: var(--color-code-text);
516
+ }
517
+
518
+ .section-markdown .norna-code-title {
519
+ min-height: 3.25rem;
520
+ margin: 0;
521
+ padding: 0.85rem 3.85rem 0.75rem 0.9rem;
522
+ border-bottom: 1px solid color-mix(in srgb, var(--color-code-text) 22%, transparent);
523
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
524
+ font-size: 0.82rem;
525
+ font-weight: 650;
526
+ line-height: 1.35;
527
+ overflow-wrap: anywhere;
528
+ }
529
+
530
+ .section-markdown .norna-code-example > pre.astro-code {
531
+ margin: 0;
532
+ border: 0;
533
+ border-radius: 0;
534
+ }
535
+
536
+ .section-markdown .norna-code-example.code-block > pre.astro-code {
537
+ padding-right: 0.9rem;
538
+ }
539
+
383
540
  .section-markdown .code-block > pre.astro-code {
384
541
  margin: 0;
385
542
  padding-right: 3.85rem;
386
543
  }
387
544
 
545
+ .section-markdown pre.astro-code.norna-code-has-highlighted-lines code {
546
+ display: block;
547
+ }
548
+
549
+ .section-markdown pre.astro-code .norna-code-line-highlighted {
550
+ display: inline-block;
551
+ width: calc(100% + 1.8rem);
552
+ margin-inline: -0.9rem;
553
+ padding-inline: calc(0.9rem - 3px) 0.9rem;
554
+ border-inline-start: 3px solid var(--color-code-text);
555
+ background: color-mix(in srgb, var(--color-code-text) 14%, var(--color-code-background));
556
+ }
557
+
388
558
  .code-block-copy {
389
559
  position: absolute;
390
560
  top: 0.5rem;
@@ -443,6 +613,10 @@ ul {
443
613
  margin-bottom: 0;
444
614
  }
445
615
 
616
+ .section-markdown > .norna-code-example:last-child {
617
+ margin-bottom: 0;
618
+ }
619
+
446
620
  .section-markdown blockquote > pre.astro-code:last-child {
447
621
  margin-bottom: 0;
448
622
  }
@@ -492,3 +666,53 @@ ul {
492
666
  .section-markdown li + li {
493
667
  margin-top: 0.35rem;
494
668
  }
669
+
670
+ .section-markdown [data-footnote-ref] {
671
+ color: inherit;
672
+ font-size: 0.72em;
673
+ font-weight: 700;
674
+ text-decoration-thickness: 0.08em;
675
+ text-underline-offset: 0.15em;
676
+ }
677
+
678
+ .section-markdown [data-footnotes] {
679
+ margin-top: calc(var(--section-body-paragraph-spacing, 1em) * 1.75);
680
+ border-top: 1px solid color-mix(in srgb, currentColor 24%, transparent);
681
+ padding-top: var(--section-body-paragraph-spacing, 1em);
682
+ font-size: 0.88em;
683
+ line-height: 1.5;
684
+ }
685
+
686
+ .section-markdown [data-footnotes] > .sr-only {
687
+ position: absolute;
688
+ width: 1px;
689
+ height: 1px;
690
+ margin: -1px;
691
+ padding: 0;
692
+ border: 0;
693
+ overflow: hidden;
694
+ clip: rect(0 0 0 0);
695
+ clip-path: inset(50%);
696
+ white-space: nowrap;
697
+ }
698
+
699
+ .section-markdown [data-footnotes] ol {
700
+ display: block;
701
+ margin: 0;
702
+ padding-inline-start: 1.5rem;
703
+ }
704
+
705
+ .section-markdown [data-footnotes] li {
706
+ padding-inline-start: 0.2rem;
707
+ scroll-margin-top: calc(var(--site-top-anchor-offset) + 1rem);
708
+ }
709
+
710
+ .section-markdown [data-footnotes] li:target {
711
+ background: color-mix(in srgb, currentColor 6%, transparent);
712
+ }
713
+
714
+ .section-markdown [data-footnote-backref] {
715
+ margin-inline-start: 0.15em;
716
+ color: inherit;
717
+ text-decoration: none;
718
+ }
@@ -291,10 +291,6 @@
291
291
  outline: none;
292
292
  }
293
293
 
294
- :root[data-image-presentation='prose-aligned'] .image-carousel {
295
- --layout-carousel-width: min(100%, var(--layout-media-width));
296
- }
297
-
298
294
  .image-carousel:focus-visible {
299
295
  outline: var(--focus-indicator-width) solid var(--color-focus-ring);
300
296
  outline-offset: 0.5rem;
@@ -317,7 +313,6 @@
317
313
  }
318
314
 
319
315
  :root[data-image-presentation='prose-aligned'] .image-carousel-stage {
320
- max-height: none;
321
316
  margin-inline: 0 auto;
322
317
  }
323
318