@wxxb789/anc 0.0.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/astro.config.mjs +255 -0
  4. package/bin/anc.mjs +798 -0
  5. package/package.json +93 -0
  6. package/public/_headers +45 -0
  7. package/public/favicon.ico +0 -0
  8. package/public/favicon.svg +9 -0
  9. package/public/fonts/Temml.woff2 +0 -0
  10. package/scripts/build-snapshot.js +69 -0
  11. package/scripts/build-wasm.js +67 -0
  12. package/scripts/build-workspace.js +50 -0
  13. package/scripts/copy-snapshot.js +48 -0
  14. package/scripts/copy-wasm.js +57 -0
  15. package/scripts/emit-redirects.js +64 -0
  16. package/scripts/generate-corpus.js +421 -0
  17. package/scripts/init-repository.js +249 -0
  18. package/scripts/load-config.js +975 -0
  19. package/scripts/markdown-to-artifact.js +1286 -0
  20. package/scripts/preview-site.js +364 -0
  21. package/scripts/publish-set-review.js +157 -0
  22. package/scripts/resolve-links.js +640 -0
  23. package/scripts/run-pagefind.js +125 -0
  24. package/scripts/scan-residue.js +1016 -0
  25. package/scripts/scan-secrets.js +268 -0
  26. package/scripts/seed-gitignore.js +410 -0
  27. package/scripts/snapshot-rows.js +205 -0
  28. package/scripts/validate-content.js +95 -0
  29. package/scripts/vendor-provenance.js +307 -0
  30. package/scripts/verify-output-inventory.js +307 -0
  31. package/scripts/write-report.js +351 -0
  32. package/scripts/write-snapshot.js +136 -0
  33. package/src/components/CollectionExplorer.astro +149 -0
  34. package/src/components/FacetIndex.astro +45 -0
  35. package/src/components/LinkedNotes.astro +94 -0
  36. package/src/components/NoteGraph.astro +434 -0
  37. package/src/components/NoteList.astro +54 -0
  38. package/src/components/SiteMetadata.astro +143 -0
  39. package/src/components/TableOfContents.astro +69 -0
  40. package/src/components/TagBrowser.astro +70 -0
  41. package/src/layouts/Layout.astro +305 -0
  42. package/src/lib/artifact-source.js +72 -0
  43. package/src/lib/build-bindings.js +11 -0
  44. package/src/lib/collection-navigation.js +145 -0
  45. package/src/lib/content.js +92 -0
  46. package/src/lib/diagram-mode.js +18 -0
  47. package/src/lib/graph-layout.js +219 -0
  48. package/src/lib/graph-selection.js +84 -0
  49. package/src/lib/graph.js +42 -0
  50. package/src/lib/link-resolution.js +389 -0
  51. package/src/lib/markdown.js +1469 -0
  52. package/src/lib/math-mode.js +31 -0
  53. package/src/lib/math-output.js +131 -0
  54. package/src/lib/math.js +165 -0
  55. package/src/lib/mermaid-environment.js +475 -0
  56. package/src/lib/mermaid-render.js +769 -0
  57. package/src/lib/preview-model.js +96 -0
  58. package/src/lib/relations.js +242 -0
  59. package/src/lib/rendered-marker.js +55 -0
  60. package/src/lib/route-path.js +192 -0
  61. package/src/lib/routes.js +342 -0
  62. package/src/lib/schema.js +578 -0
  63. package/src/lib/site.js +578 -0
  64. package/src/lib/snapshot-contract.js +216 -0
  65. package/src/lib/snapshot-operations.js +147 -0
  66. package/src/lib/snapshot-queries.js +216 -0
  67. package/src/lib/snapshot-reader.js +210 -0
  68. package/src/lib/snapshot.js +233 -0
  69. package/src/lib/sqlite-warning.js +22 -0
  70. package/src/lib/sqlite.js +14 -0
  71. package/src/lib/tag-browser-model.js +18 -0
  72. package/src/lib/translations.js +533 -0
  73. package/src/lib/wasm-asset.js +47 -0
  74. package/src/lib/worker-protocol.js +93 -0
  75. package/src/pages/404.astro +35 -0
  76. package/src/pages/about.astro +100 -0
  77. package/src/pages/collections/[slug].astro +36 -0
  78. package/src/pages/collections/index.astro +33 -0
  79. package/src/pages/graph.astro +64 -0
  80. package/src/pages/index.astro +33 -0
  81. package/src/pages/notes/[slug].astro +509 -0
  82. package/src/pages/privacy.astro +121 -0
  83. package/src/pages/private.astro +59 -0
  84. package/src/pages/recent.astro +33 -0
  85. package/src/pages/robots.txt.js +15 -0
  86. package/src/pages/rss.xml.js +21 -0
  87. package/src/pages/sitemap.xml.js +20 -0
  88. package/src/pages/tags/[tag].astro +56 -0
  89. package/src/pages/tags/index.astro +37 -0
  90. package/src/scripts/code-copy.js +45 -0
  91. package/src/scripts/diagram-disabled.js +16 -0
  92. package/src/scripts/diagram.js +322 -0
  93. package/src/scripts/graph-client.js +222 -0
  94. package/src/scripts/graph-draw.js +166 -0
  95. package/src/scripts/link-preview.js +334 -0
  96. package/src/scripts/math-disabled.js +18 -0
  97. package/src/scripts/math.js +133 -0
  98. package/src/scripts/preferences.js +123 -0
  99. package/src/scripts/search-dialog.js +651 -0
  100. package/src/scripts/snapshot-client.js +157 -0
  101. package/src/scripts/snapshot-worker.js +341 -0
  102. package/src/scripts/tag-browser.js +153 -0
  103. package/src/scripts/theme-init.js +35 -0
  104. package/src/scripts/toc-active.js +38 -0
  105. package/src/styles/code.css +152 -0
  106. package/src/styles/diagram.css +149 -0
  107. package/src/styles/global.css +1739 -0
  108. package/src/styles/graph.css +317 -0
  109. package/src/styles/math.css +376 -0
  110. package/src/styles/tokens.css +171 -0
  111. package/tsconfig.json +25 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The lazy snapshot Worker's message contract.
3
+ *
4
+ * Pure and dependency-free so the Worker and the main-thread client import the
5
+ * same definition: a message the client can send is one the Worker can validate,
6
+ * and a result the Worker can return is one the client can type.
7
+ *
8
+ * The design constrains this shape (`docs/core-design/build-and-runtime.md`):
9
+ * messages carry a discriminated `type`, a request id, and operation-specific
10
+ * validated arguments. There is deliberately **no field that can carry SQL, a
11
+ * URL, a filesystem path, a table name, or a column name** — "named operations,
12
+ * not an SQL console" is structural here, not a convention enforced elsewhere.
13
+ */
14
+ import { isLookupSlug, isLookupTagKey, normalizePageSize, } from "./snapshot-queries.js";
15
+ /**
16
+ * Finite provisional bounds. Goal 0003 establishes these on the correctness
17
+ * fixtures; goal 0008 must validate or replace them on the benchmark workloads.
18
+ * They are not a capacity claim.
19
+ */
20
+ export const WORKER_LIMITS = {
21
+ /** Fetch + hash + WASM init + schema validation. */
22
+ startupDeadlineMs: 20_000,
23
+ /** One dispatched request to a validated result. */
24
+ requestDeadlineMs: 8_000,
25
+ /** Concurrent requests before a new one is rejected rather than queued. */
26
+ maxPendingRequests: 16,
27
+ /** Decoded snapshot bytes read before WASM allocation. */
28
+ maxSnapshotBytes: 64 * 1024 * 1024,
29
+ /** Decoded WASM bytes read before instantiation. */
30
+ maxWasmBytes: 8 * 1024 * 1024,
31
+ };
32
+ export const SNAPSHOT_OPERATIONS = [
33
+ 'preview',
34
+ 'backlinks',
35
+ 'outgoing',
36
+ 'byTag',
37
+ 'localGraph',
38
+ 'globalGraph',
39
+ ];
40
+ /**
41
+ * Whether an untrusted value is a request the Worker may run.
42
+ *
43
+ * Checks the discriminator, the id, and the lookup-shape of every argument.
44
+ * Page size and cursor are validated here too; `normalizePageSize` clamps rather
45
+ * than rejects, which is the accepted bound ("cap and validate the positive
46
+ * integer page size").
47
+ */
48
+ export function isSnapshotMessage(value) {
49
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
50
+ return false;
51
+ const message = value;
52
+ const id = message['id'];
53
+ if (typeof id !== 'number' || !Number.isInteger(id) || id <= 0)
54
+ return false;
55
+ const type = message['type'];
56
+ if (typeof type !== 'string' || !SNAPSHOT_OPERATIONS.includes(type))
57
+ return false;
58
+ // The instrumentation flag is optional, and only a boolean is a request the
59
+ // Worker may act on; any other value is refused rather than coerced. Checked
60
+ // before the per-operation branches because `preview`, `localGraph`, and
61
+ // `globalGraph` return from those branches, and an invalid flag must not slip
62
+ // past one of them. Unrelated extra keys stay ignored, as before.
63
+ const measure = message['measure'];
64
+ if (measure !== undefined && typeof measure !== 'boolean')
65
+ return false;
66
+ if (type === 'preview' || type === 'localGraph')
67
+ return isLookupSlug(message['slug']);
68
+ if (type === 'globalGraph') {
69
+ return message['tagKey'] === undefined || message['tagKey'] === null || isLookupTagKey(message['tagKey']);
70
+ }
71
+ if (type === 'byTag') {
72
+ if (!isLookupTagKey(message['tagKey']))
73
+ return false;
74
+ }
75
+ else if (!isLookupSlug(message['slug'])) {
76
+ return false;
77
+ }
78
+ const cursor = message['cursor'];
79
+ if (cursor !== undefined && cursor !== null && !isLookupSlug(cursor))
80
+ return false;
81
+ const pageSize = message['pageSize'];
82
+ if (pageSize !== undefined && typeof pageSize !== 'number')
83
+ return false;
84
+ return true;
85
+ }
86
+ /** The page size a validated request resolves to, clamped to the accepted range. */
87
+ export function requestPageSize(message) {
88
+ return normalizePageSize(message['pageSize']);
89
+ }
90
+ /** Narrow a reply to the result type a caller asked for. */
91
+ export function isResultOf(reply, type) {
92
+ return reply.ok && reply.result.type === type;
93
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ /**
3
+ * Static 404.
4
+ *
5
+ * Astro emits `src/pages/404.astro` as `dist/404.html`, which Cloudflare Pages
6
+ * serves for any unmatched request. It is a real page in the layout shell, not
7
+ * a bare message: a reader who lands here still has the full site navigation.
8
+ *
9
+ * The routes offered are the site's own fixed routes. Nothing here reports what
10
+ * was requested — echoing the URL back into the page is how a 404 becomes a
11
+ * reflected-content sink, and it would tell a visitor nothing they did not type.
12
+ */
13
+ import Layout from '../layouts/Layout.astro';
14
+ import { SITE_MAP } from '../lib/routes.js';
15
+ import { TITLE_SUFFIX } from '../lib/site.js';
16
+ import { NAV_LANGUAGE, translate } from '../lib/translations.js';
17
+
18
+ // The one route with no document behind it at all: the host serves this body
19
+ // for any unmatched address, so there is nothing whose language could be read.
20
+ const t = translate(NAV_LANGUAGE);
21
+ ---
22
+
23
+ <Layout title={`${t.notFoundTitle}${TITLE_SUFFIX}`} description={t.notFoundDescription}>
24
+ <article class="prose">
25
+ <h1>{t.notFoundTitle}</h1>
26
+
27
+ <p>{t.notFoundBody}</p>
28
+
29
+ <h2>{t.notFoundWhereTo}</h2>
30
+
31
+ <ul>
32
+ {SITE_MAP.map((item) => <li><a href={item.href}>{t[item.label]}</a></li>)}
33
+ </ul>
34
+ </article>
35
+ </Layout>
@@ -0,0 +1,100 @@
1
+ ---
2
+ /**
3
+ * What this site is, for a reader who did not build it.
4
+ *
5
+ * **Rewritten by TK-31, because every substantive claim it made was false.**
6
+ * The previous version told a reader that "every page here is a note that was
7
+ * reviewed and explicitly approved for publication, one item at a time" and
8
+ * that a note "appears here only because it was named on an explicit approval
9
+ * list". That described the retired owner-specific bridge. This tool publishes a directory
10
+ * of Markdown by default and withholds what the user's `publish.config.yaml`
11
+ * excludes — the opposite arrangement — so the sentence a reader consults *to
12
+ * find out whether the boundary is trustworthy* was the sentence that lied
13
+ * about which direction it runs.
14
+ *
15
+ * **A shipped page rather than a Markdown note the user seeds.** The plan
16
+ * (§4.4) proposes deleting both prose pages and having `init` write them into
17
+ * the notes repository. That is the better end state and it is not reachable
18
+ * from here: `init` does not exist yet — it is TK-32's — and deleting the pages
19
+ * now would remove `/about/` and `/privacy/` from `SITE_MAP` and from three
20
+ * gates, leaving a build with no privacy statement at all in the interval. A
21
+ * page that is true is worth more than a page that is absent, and the sentences
22
+ * below are true of *every* build this tool produces rather than of one corpus.
23
+ *
24
+ * **So it says only what the tool guarantees.** Every claim here is a property
25
+ * of the generator — static output, no analytics, no third-party requests,
26
+ * exclusion by configuration — and none of it describes the user's own
27
+ * editorial process, which this repository cannot know and must not narrate on
28
+ * their behalf. Where the honest answer depends on the operator, it says so and
29
+ * points at them rather than inventing a policy.
30
+ *
31
+ * The prose is in the navigation language and is not translated, which is the
32
+ * same treatment `tests/translations.test.ts` records for this file: it is a
33
+ * document rather than chrome, and translating a document is a content decision
34
+ * this ticket does not own.
35
+ *
36
+ * **The withheld-link paragraph was rewritten on 2026-08-17** and it is the one
37
+ * claim here a reader could previously have been misled by. It said a link to an
38
+ * unpublished note "is not rendered as a link" and that "their content, their
39
+ * number, or that any exist" is not represented. Both became false when the
40
+ * owner made those links live: they are anchors now, they land on `/private/`,
41
+ * and a link's text is routinely the note's own path — so a reader can count
42
+ * them and read where they lived. The paragraph says that plainly, because a
43
+ * reader consulting this page to calibrate trust is exactly the person the
44
+ * omission would cost.
45
+ */
46
+ import Layout from '../layouts/Layout.astro';
47
+ import { DEFAULT_NAV_LANGUAGE } from '../lib/translations.js';
48
+ import { SITE_NAME, TITLE_SUFFIX } from '../lib/site.js';
49
+ ---
50
+
51
+ {/* The prose below is English whatever the site language, so the page says so. */}
52
+ <Layout
53
+ title={`About${TITLE_SUFFIX}`}
54
+ lang={DEFAULT_NAV_LANGUAGE}
55
+ description="What this site is, and how its pages came to be here."
56
+ >
57
+ <article class="prose">
58
+ <h1>About</h1>
59
+
60
+ <p>
61
+ {SITE_NAME} is a reading site. Each page is a note written in Markdown and published as a
62
+ static page, generated ahead of time rather than assembled when you ask for it.
63
+ </p>
64
+
65
+ <h2>How pages get here</h2>
66
+
67
+ <p>
68
+ The site is built from a set of notes written in Markdown. Publication is the default: a note
69
+ becomes a page unless it was explicitly excluded, and the person who runs the build chooses
70
+ what to exclude by listing patterns in a configuration file, or by marking an individual note.
71
+ An excluded note produces no page, no search entry, and none of its content appears here.
72
+ </p>
73
+
74
+ <p>
75
+ Links between notes are resolved when the site is built. A link whose target was not
76
+ published points at a page saying so, rather than at the note — so following one cannot
77
+ lead to something that is not here.
78
+ </p>
79
+
80
+ <p>
81
+ One thing this site does still show you about an unpublished note is the words a published
82
+ note used to refer to it. That link keeps its text exactly as the author wrote it, which
83
+ often means the note's own name and the path it had among the author's notes. It is the
84
+ author's writing rather than anything read from the note it names, and nothing else about
85
+ unpublished notes — their content, their titles, their number — is represented here.
86
+ </p>
87
+
88
+ <h2>How it is built</h2>
89
+
90
+ <p>
91
+ Every page is generated ahead of time and served as a static file. There is no server-side
92
+ application, no database behind a page view, and no account system. Reading, navigating,
93
+ following links, and browsing backlinks all work with JavaScript turned off.
94
+ </p>
95
+
96
+ <p>
97
+ For what this site does and does not collect, see <a href="/privacy/">Privacy</a>.
98
+ </p>
99
+ </article>
100
+ </Layout>
@@ -0,0 +1,36 @@
1
+ ---
2
+ /**
3
+ * `/collections/<key>/` — the notes in one flat first-folder collection.
4
+ *
5
+ * A facet aggregates many documents, so its chrome is the navigation language
6
+ * rather than any member's; see `src/lib/translations.ts`. The collection name
7
+ * is artifact text, interpolated unchanged.
8
+ */
9
+ import Layout from '../../layouts/Layout.astro';
10
+ import NoteList from '../../components/NoteList.astro';
11
+ import { entries } from '../../lib/content.js';
12
+ import { collectionFacets } from '../../lib/routes.js';
13
+ import { TITLE_SUFFIX } from '../../lib/site.js';
14
+ import { NAV_LANGUAGE, translate } from '../../lib/translations.js';
15
+
16
+ export function getStaticPaths() {
17
+ return collectionFacets(entries).map((facet) => ({ params: { slug: facet.key }, props: { facet } }));
18
+ }
19
+
20
+ const { facet } = Astro.props;
21
+ const t = translate(NAV_LANGUAGE);
22
+ ---
23
+
24
+ <Layout
25
+ title={`${t.collectionPageTitle(facet.label)}${TITLE_SUFFIX}`}
26
+ description={t.collectionPageDescription(facet.label)}
27
+ >
28
+ <section aria-labelledby="collection-title">
29
+ <div class="section-heading">
30
+ <h1 id="collection-title">{t.collectionPageTitle(facet.label)}</h1>
31
+ <span>{t.noteCount(facet.entries.length)}</span>
32
+ </div>
33
+ <p class="facet-note"><a href="/collections/">{t.allCollections}</a></p>
34
+ <NoteList entries={facet.entries} level={2} lang={NAV_LANGUAGE} />
35
+ </section>
36
+ </Layout>
@@ -0,0 +1,33 @@
1
+ ---
2
+ /**
3
+ * `/collections/` — the curated public collections.
4
+ *
5
+ * A corpus route, so the chrome is the navigation language. The collection
6
+ * labels are artifact text and are never translated.
7
+ */
8
+ import Layout from '../../layouts/Layout.astro';
9
+ import FacetIndex from '../../components/FacetIndex.astro';
10
+ import { entries } from '../../lib/content.js';
11
+ import { collectionFacets, collectionRoute } from '../../lib/routes.js';
12
+ import { TITLE_SUFFIX } from '../../lib/site.js';
13
+ import { NAV_LANGUAGE, translate } from '../../lib/translations.js';
14
+
15
+ const t = translate(NAV_LANGUAGE);
16
+ const facets = collectionFacets(entries);
17
+ ---
18
+
19
+ <Layout title={`${t.collectionsTitle}${TITLE_SUFFIX}`} description={t.collectionsDescription}>
20
+ <section aria-labelledby="collections-title">
21
+ <div class="section-heading">
22
+ <h1 id="collections-title">{t.collectionsTitle}</h1>
23
+ <span>{t.collectionCount(facets.length)}</span>
24
+ </div>
25
+ <p class="facet-note">{t.collectionsNote}</p>
26
+ <FacetIndex
27
+ facets={facets}
28
+ route={collectionRoute}
29
+ empty={t.collectionsEmpty}
30
+ lang={NAV_LANGUAGE}
31
+ />
32
+ </section>
33
+ </Layout>
@@ -0,0 +1,64 @@
1
+ ---
2
+ /**
3
+ * `/graph/` — the site-wide graph, requirements section 9.1's reserved route.
4
+ *
5
+ * A corpus route rather than a document route, so its chrome is the navigation
6
+ * language. The note titles it draws are their authors' own words and carry
7
+ * their own `lang`; `NoteGraph` handles that.
8
+ *
9
+ * Requirements section 13.3 admits a global graph only on four conditions, and
10
+ * this page meets them by construction rather than by intent:
11
+ *
12
+ * - *Legible at current node count* — `GLOBAL_NODE_LIMIT` bounds the figure, the
13
+ * bound is stated on the page rather than applied silently, and the figure
14
+ * scrolls below `min-width` rather than shrinking its labels past reading size.
15
+ * - *Keyboard-accessible, or an equivalent searchable list* — keyboard: every
16
+ * node is a real `<a>` in the tab order, and the table under the figure lists
17
+ * the same notes with the notes each is linked to. It is **not** in the site
18
+ * search index: only `/notes/<slug>/` carries `data-pagefind-body`, so
19
+ * Pagefind indexes note bodies alone. The requirement is satisfied by the
20
+ * keyboard half, which is why that is the half this says.
21
+ * - *JS and database budgets* — there is no JavaScript and no database. TK-21
22
+ * owns the layer that would have either, and it enhances this rather than
23
+ * replacing it.
24
+ * - *Decision value beyond visual novelty* — the ranked packing puts the most
25
+ * linked notes at the centre, so the figure answers "what is this site mostly
26
+ * about" at a glance, and the table answers it exactly.
27
+ */
28
+ import Layout from '../layouts/Layout.astro';
29
+ import NoteGraph from '../components/NoteGraph.astro';
30
+ import { entries, tagFacets } from '../lib/content.js';
31
+ import { globalGraph, hasDrawableGraph } from '../lib/graph.js';
32
+ import { TITLE_SUFFIX } from '../lib/site.js';
33
+ import { NAV_LANGUAGE, translate } from '../lib/translations.js';
34
+ // Loaded as a URL rather than imported, so the stylesheet becomes its own
35
+ // content-hashed asset and the `<link>` below can be emitted conditionally —
36
+ // owner decision 7. A page with nothing to draw requests nothing.
37
+ import graphStylesheet from '../styles/graph.css?url';
38
+
39
+ const t = translate(NAV_LANGUAGE);
40
+ const graph = globalGraph(entries);
41
+ ---
42
+
43
+ <Layout title={`${t.graphTitle}${TITLE_SUFFIX}`} description={t.graphDescription}>
44
+ {/* The same predicate the component branches on, so the stylesheet and the
45
+ figure can never disagree about whether there is a graph. */}
46
+ {hasDrawableGraph(graph) && <link slot="head" rel="stylesheet" href={graphStylesheet} />}
47
+
48
+ <NoteGraph
49
+ graph={graph}
50
+ region="site-graph"
51
+ level={1}
52
+ heading={t.graphTitle}
53
+ derivation={t.graphGlobalDerivation}
54
+ empty={t.graphSiteEmpty}
55
+ bounded={t.graphBounded}
56
+ expand={{ href: '/#notes', text: t.graphExpand, label: t.graphExpandLabel }}
57
+ lang={NAV_LANGUAGE}
58
+ explore={{
59
+ scope: 'global',
60
+ centerSlug: null,
61
+ tags: tagFacets().map((facet) => ({ key: facet.key, label: facet.label })),
62
+ }}
63
+ />
64
+ </Layout>
@@ -0,0 +1,33 @@
1
+ ---
2
+ /**
3
+ * The home page.
4
+ *
5
+ * Not one document but the whole corpus, so its chrome is the navigation
6
+ * language — see `src/lib/translations.ts` for the rule, and for why the
7
+ * alternative of borrowing some member's language would make a public page
8
+ * change language because an unrelated note was published.
9
+ */
10
+ import Layout from '../layouts/Layout.astro';
11
+ import NoteList from '../components/NoteList.astro';
12
+ import { entries } from '../lib/content.js';
13
+ import { SITE_NAME } from '../lib/site.js';
14
+ import { NAV_LANGUAGE, translate } from '../lib/translations.js';
15
+
16
+ const t = translate(NAV_LANGUAGE);
17
+ ---
18
+
19
+ <Layout title={SITE_NAME} description={t.homeDescription}>
20
+ <section class="hero">
21
+ <p class="eyebrow">{t.homeEyebrow}</p>
22
+ <h1>{t.homeHeading}</h1>
23
+ <p>{t.homeIntro}</p>
24
+ </section>
25
+
26
+ <section id="notes" aria-labelledby="notes-title">
27
+ <div class="section-heading">
28
+ <h2 id="notes-title">{t.homeNotesHeading}</h2>
29
+ <span>{t.publishedCount(entries.length)}</span>
30
+ </div>
31
+ <NoteList entries={entries} lang={NAV_LANGUAGE} />
32
+ </section>
33
+ </Layout>