reallysimpledocs 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ronan Berder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # ReallySimpleDocs
2
+
3
+ A simple documentation system for Astro, built with **Basecoat** and **Tailwind CSS**.
4
+
5
+ RSD keeps docs portable: content lives in `docs/`, navigation lives in `docs/docs.json`, and the Astro integration owns the docs UI.
6
+
7
+ ## Add to an Astro site
8
+
9
+ ```bash
10
+ npm install reallysimpledocs@beta basecoat-css@beta
11
+ ```
12
+
13
+ Add the integration:
14
+
15
+ ```js
16
+ // astro.config.mjs
17
+ import { defineConfig } from "astro/config";
18
+ import reallySimpleDocs from "reallysimpledocs/astro";
19
+
20
+ export default defineConfig({
21
+ integrations: [
22
+ reallySimpleDocs({
23
+ docsDir: "./docs",
24
+ routeBase: "/docs",
25
+ style: "vega",
26
+ customCss: ["./src/docs.css"],
27
+ site: {
28
+ title: "ReallySimpleDocs",
29
+ description: "A simple documentation system for Astro.",
30
+ },
31
+ }),
32
+ ],
33
+ });
34
+ ```
35
+
36
+ Create docs:
37
+
38
+ ```text
39
+ docs/
40
+ ├── docs.json
41
+ └── index.md
42
+ ```
43
+
44
+ ```json
45
+ {
46
+ "menu": [
47
+ {
48
+ "type": "group",
49
+ "label": "Docs",
50
+ "items": [{ "slug": "index", "icon": "info" }]
51
+ }
52
+ ]
53
+ }
54
+ ```
55
+
56
+ ```md
57
+ # Introduction
58
+
59
+ Welcome to the docs.
60
+ ```
61
+
62
+ RSD injects docs pages, Lunr search, per-page Markdown exports, `llms.txt`, and `llms-full.txt`.
63
+
64
+ ## Local development (this repo)
65
+
66
+ ```bash
67
+ npm install
68
+ npm run dev
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ Go to [ReallySimpleDocs.com](https://reallysimpledocs.com).
74
+
75
+ ## Support the project
76
+
77
+ - [Contribute code](/CONTRIBUTING.md)
78
+ - [Report issues](https://github.com/hunvreus/reallysimpledocs/issues)
79
+ - [Sponsor me](https://github.com/sponsors/hunvreus)
80
+ - [Star the project on GitHub](https://github.com/hunvreus/reallysimpledocs)
81
+
82
+ ## License
83
+
84
+ [MIT](/LICENSE.md)
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "reallysimpledocs",
3
+ "version": "0.1.3",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.js",
7
+ "./astro": "./src/astro/index.js",
8
+ "./components": "./src/components/index.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE.md"
14
+ ],
15
+ "scripts": {
16
+ "dev": "astro dev",
17
+ "build": "rm -rf dist && astro build",
18
+ "preview": "astro preview",
19
+ "smoke:consumer": "node scripts/smoke-consumer.mjs",
20
+ "check": "npm run build && npm pack --dry-run && npm run smoke:consumer"
21
+ },
22
+ "dependencies": {
23
+ "@tailwindcss/vite": "4.1.17",
24
+ "astro": "^6.4.6",
25
+ "github-slugger": "^2.0.0",
26
+ "lucide-static": "^0.552.0",
27
+ "lunr": "^2.3.9",
28
+ "marked": "^16.4.1",
29
+ "shiki": "^4.1.0",
30
+ "tailwindcss": "4.1.17"
31
+ },
32
+ "peerDependencies": {
33
+ "basecoat-css": "^1.0.0-beta.1"
34
+ },
35
+ "devDependencies": {
36
+ "basecoat-css": "^1.0.0-beta.1"
37
+ },
38
+ "overrides": {
39
+ "esbuild": "^0.28.1",
40
+ "picomatch": "^4.0.4"
41
+ }
42
+ }
@@ -0,0 +1,131 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import tailwindcss from "@tailwindcss/vite";
5
+
6
+ const basecoatStyles = new Set(["vega", "nova", "maia", "lyra", "mira", "luma", "sera", "rhea"]);
7
+
8
+ const normalizeRouteBase = (routeBase) => {
9
+ const value = String(routeBase || "/").trim();
10
+ if (!value || value === "/") return "";
11
+ return value.replace(/^\/+|\/+$/g, "");
12
+ };
13
+
14
+ const routePattern = (routeBase, suffix = "[...slug]") => {
15
+ const base = normalizeRouteBase(routeBase);
16
+ return base ? `${base}/${suffix}` : suffix;
17
+ };
18
+
19
+ const runtimePath = (relativePath) =>
20
+ fileURLToPath(new URL(`../runtime/${relativePath}`, import.meta.url));
21
+
22
+ const cssPath = (relativePath) => fileURLToPath(new URL(`../css/${relativePath}`, import.meta.url));
23
+
24
+ const normalizeArray = (value) => (Array.isArray(value) ? value : value ? [value] : []);
25
+
26
+ export default function reallySimpleDocs(options = {}) {
27
+ const style = options.style || "vega";
28
+ if (!basecoatStyles.has(style)) {
29
+ throw new Error(`Invalid ReallySimpleDocs style "${style}". Expected one of: ${Array.from(basecoatStyles).join(", ")}.`);
30
+ }
31
+
32
+ const normalizedOptions = {
33
+ docsDir: options.docsDir || "./docs",
34
+ site: options.site || {},
35
+ siteFile: options.siteFile || null,
36
+ style,
37
+ customCss: normalizeArray(options.customCss),
38
+ components: options.components || {},
39
+ routeBase: options.routeBase ?? "/docs",
40
+ assetsBase: options.assetsBase || "/assets",
41
+ };
42
+
43
+ return {
44
+ name: "reallysimpledocs",
45
+ hooks: {
46
+ "astro:config:setup": ({ injectRoute, updateConfig, config }) => {
47
+ const root = fileURLToPath(config.root);
48
+ const generatedDir = path.join(root, ".astro", "reallysimpledocs");
49
+ const generatedStyles = path.join(generatedDir, "styles.css");
50
+ const virtualConfig = {
51
+ ...normalizedOptions,
52
+ root,
53
+ docsDir: path.resolve(root, normalizedOptions.docsDir),
54
+ siteFile: normalizedOptions.siteFile ? path.resolve(root, normalizedOptions.siteFile) : null,
55
+ routeBase: normalizeRouteBase(normalizedOptions.routeBase),
56
+ };
57
+ const resolveUserId = (id) => (id?.startsWith(".") ? path.resolve(root, id) : id);
58
+ const components = {
59
+ SidebarHeader:
60
+ normalizedOptions.components.SidebarHeader || runtimePath("components/DefaultSidebarHeader.astro"),
61
+ };
62
+ fs.mkdirSync(generatedDir, { recursive: true });
63
+ fs.writeFileSync(
64
+ generatedStyles,
65
+ [
66
+ '@import "tailwindcss";',
67
+ '@import "basecoat-css/base";',
68
+ `@import "basecoat-css/styles/${style}";`,
69
+ `@import ${JSON.stringify(cssPath("sources.css"))};`,
70
+ `@import ${JSON.stringify(cssPath("custom.css"))};`,
71
+ `@import ${JSON.stringify(cssPath("overrides.css"))};`,
72
+ ...normalizedOptions.customCss.map((css) => `@import ${JSON.stringify(resolveUserId(css))};`),
73
+ `@source ${JSON.stringify(runtimePath("**/*.{astro,js,ts}"))};`,
74
+ `@source ${JSON.stringify(path.join(virtualConfig.docsDir, "**/*.md"))};`,
75
+ "",
76
+ ].join("\n"),
77
+ );
78
+
79
+ injectRoute({
80
+ pattern: routePattern(normalizedOptions.routeBase),
81
+ entrypoint: runtimePath("pages/doc.astro"),
82
+ });
83
+ injectRoute({
84
+ pattern: routePattern(normalizedOptions.routeBase, "[...slug].md"),
85
+ entrypoint: runtimePath("pages/markdown.ts"),
86
+ });
87
+ injectRoute({
88
+ pattern: routePattern(normalizedOptions.routeBase, "search-index.json"),
89
+ entrypoint: runtimePath("pages/search-index.ts"),
90
+ });
91
+ injectRoute({
92
+ pattern: "llms.txt",
93
+ entrypoint: runtimePath("pages/llms.txt.ts"),
94
+ });
95
+ injectRoute({
96
+ pattern: "llms-full.txt",
97
+ entrypoint: runtimePath("pages/llms-full.txt.ts"),
98
+ });
99
+
100
+ updateConfig({
101
+ vite: {
102
+ build: {
103
+ cssMinify: false,
104
+ },
105
+ plugins: [
106
+ tailwindcss(),
107
+ {
108
+ name: "reallysimpledocs-virtual-modules",
109
+ resolveId(id) {
110
+ if (id === "virtual:reallysimpledocs/config") return "\0virtual:reallysimpledocs/config";
111
+ if (id === "virtual:reallysimpledocs/styles.css") return generatedStyles;
112
+ if (id === "virtual:reallysimpledocs/components/SidebarHeader") {
113
+ return "\0virtual:reallysimpledocs/components/SidebarHeader";
114
+ }
115
+ return null;
116
+ },
117
+ load(id) {
118
+ if (id === "\0virtual:reallysimpledocs/config") return `export default ${JSON.stringify(virtualConfig)};`;
119
+ if (id === "\0virtual:reallysimpledocs/components/SidebarHeader") {
120
+ return `export { default } from ${JSON.stringify(resolveUserId(components.SidebarHeader))};`;
121
+ }
122
+ return null;
123
+ },
124
+ },
125
+ ],
126
+ },
127
+ });
128
+ },
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,4 @@
1
+ export { default as DocsLayout } from "../runtime/components/DocsLayout.astro";
2
+ export { default as CommandDialog } from "../runtime/components/CommandDialog.astro";
3
+ export { default as DropdownMenu } from "../runtime/components/DropdownMenu.astro";
4
+ export { default as Sidebar } from "../runtime/components/Sidebar.astro";
@@ -0,0 +1,157 @@
1
+ @theme {
2
+ --color-search-highlight: oklch(90.5% 0.182 98.111);
3
+ }
4
+
5
+ .prose {
6
+ @apply my-6;
7
+
8
+ > h2,
9
+ > h3,
10
+ > h4 {
11
+ @apply sm:text-lg my-6 scroll-m-18 font-semibold tracking-tight;
12
+
13
+ a {
14
+ @apply no-underline;
15
+ }
16
+ }
17
+ > h2 {
18
+ @apply text-xl sm:text-2xl mt-8 lg:mt-12 first:mt-0;
19
+ }
20
+ > h3 {
21
+ @apply text-lg sm:text-xl lg:mt-8;
22
+ }
23
+ p > a,
24
+ li > a,
25
+ dt > a,
26
+ dd > a,
27
+ td > a,
28
+ th > a {
29
+ @apply underline underline-offset-2 decoration-muted-foreground/50 hover:decoration-foreground/50 transition-all;
30
+ }
31
+ code:not(pre > code) {
32
+ @apply relative rounded-md bg-muted px-[0.3rem] py-[0.2rem] font-mono text-[13px];
33
+ }
34
+ pre {
35
+ @apply rounded-lg;
36
+ }
37
+ pre > code {
38
+ @apply block min-w-full w-max px-4 py-3 text-[13px];
39
+ }
40
+ pre:has(> code) {
41
+ @apply relative max-w-full overflow-x-auto;
42
+ }
43
+ pre:has(> code) > button {
44
+ @apply flex items-center justify-center size-6 absolute right-2.5 top-2.5 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] rounded-md hover:bg-accent dark:hover:bg-accent/50 text-muted-foreground hover:text-foreground [&_svg]:size-4;
45
+ }
46
+ blockquote {
47
+ @apply border-l-4 border-border pl-4;
48
+ }
49
+ ol {
50
+ @apply list-decimal ml-6;
51
+ }
52
+ ul {
53
+ @apply list-disc ml-6;
54
+ }
55
+ ol,
56
+ ul {
57
+ li {
58
+ @apply mt-2;
59
+
60
+ ol,
61
+ ul {
62
+ @apply mt-0 mb-0;
63
+ }
64
+ }
65
+ }
66
+ dl {
67
+ @apply border-b pb-3 my-6;
68
+ dt {
69
+ @apply border-t pt-3 mt-3 first:mt-0;
70
+ }
71
+ dd {
72
+ @apply mt-2;
73
+ }
74
+ dl {
75
+ @apply mt-4 border-b-0 pb-0 pl-8 my-0;
76
+ }
77
+ }
78
+ b,
79
+ strong {
80
+ @apply font-semibold;
81
+ }
82
+ > p,
83
+ blockquote,
84
+ ul,
85
+ ol,
86
+ dl,
87
+ pre {
88
+ @apply my-6;
89
+ }
90
+ > *:first-child {
91
+ @apply !mt-0;
92
+ }
93
+ > *:last-child {
94
+ @apply !mb-0;
95
+ }
96
+ table {
97
+ @apply w-full caption-bottom text-sm;
98
+ thead {
99
+ @apply [&_tr]:border-b;
100
+ }
101
+ tbody {
102
+ @apply [&_tr:last-child]:border-0;
103
+ }
104
+ tfoot {
105
+ @apply bg-muted/50 border-t font-medium [&>tr]:last:border-b-0;
106
+ }
107
+ tr {
108
+ @apply hover:bg-muted/50 border-b transition-colors;
109
+ }
110
+ th {
111
+ @apply text-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
112
+ }
113
+ td {
114
+ @apply p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px];
115
+ }
116
+ caption {
117
+ @apply text-muted-foreground mt-4 text-sm;
118
+ }
119
+ }
120
+ }
121
+
122
+ .shiki {
123
+ @apply bg-muted dark:bg-muted/50;
124
+ color: var(--shiki-light);
125
+ }
126
+
127
+ .shiki span {
128
+ color: var(--shiki-light);
129
+ }
130
+
131
+ html.dark .shiki {
132
+ color: var(--shiki-dark);
133
+ }
134
+
135
+ html.dark .shiki span {
136
+ color: var(--shiki-dark);
137
+ }
138
+
139
+ #command-search > .command {
140
+ @apply sm:max-w-2xl;
141
+ }
142
+
143
+ .command [data-rsd-search-result] {
144
+ @apply flex-col items-start gap-1;
145
+ }
146
+
147
+ .command [data-rsd-search-title] {
148
+ @apply font-medium;
149
+ }
150
+
151
+ .command [data-rsd-search-snippet] {
152
+ @apply text-muted-foreground line-clamp-2 text-xs font-normal leading-snug;
153
+ }
154
+
155
+ .command mark {
156
+ @apply bg-search-highlight/50 text-foreground rounded-sm px-0.5 dark:bg-search-highlight/25;
157
+ }
@@ -0,0 +1 @@
1
+ /* Custom CSS overrides (loaded last). */
@@ -0,0 +1 @@
1
+ @source "../runtime/**/*.{astro,js,ts}";
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m4.714 15.956 4.718-2.648.079-.23-.08-.128h-.23l-.79-.048-2.695-.073-2.337-.097-2.265-.122-.57-.121-.535-.704.055-.353.48-.321.685.06 1.518.104 2.277.157 1.651.098 2.447.255h.389l.054-.158-.133-.097-.103-.098-2.356-1.596-2.55-1.688-1.336-.972-.722-.491L2 6.223l-.158-1.008.655-.722.88.06.225.061.893.686 1.906 1.476 2.49 1.833.364.304.146-.104.018-.072-.164-.274-1.354-2.446-1.445-2.49-.644-1.032-.17-.619a2.972 2.972 0 0 1-.103-.729L6.287.133 6.7 0l.995.134.42.364.619 1.415L9.735 4.14l1.555 3.03.455.898.243.832.09.255h.159V9.01l.127-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.583.28.48.685-.067.444-.286 1.851-.558 2.903-.365 1.942h.213l.243-.242.983-1.306 1.652-2.064.728-.82.85-.904.547-.431h1.032l.759 1.129-.34 1.166-1.063 1.347-.88 1.142-1.263 1.7-.79 1.36.074.11.188-.02 2.853-.606 1.542-.28 1.84-.315.832.388.09.395-.327.807-1.967.486-2.307.462-3.436.813-.043.03.049.061 1.548.146.662.036h1.62l3.018.225.79.522.473.638-.08.485-1.213.62-1.64-.389-3.825-.91-1.31-.329h-.183v.11l1.093 1.068 2.003 1.81 2.508 2.33.127.578-.321.455-.34-.049-2.204-1.657-.85-.747-1.925-1.62h-.127v.17l.443.649 2.343 3.521.122 1.08-.17.353-.607.213-.668-.122-1.372-1.924-1.415-2.168-1.141-1.943-.14.08-.674 7.254-.316.37-.728.28-.607-.461-.322-.747.322-1.476.388-1.924.316-1.53.285-1.9.17-.632-.012-.042-.14.018-1.432 1.967-2.18 2.945-1.724 1.845-.413.164-.716-.37.066-.662.401-.589 2.386-3.036 1.439-1.882.929-1.086-.006-.158h-.055L4.138 18.56l-1.13.146-.485-.456.06-.746.231-.243 1.907-1.312Z" fill="currentColor"></path></svg>
@@ -0,0 +1 @@
1
+ <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Cursor</title><path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" fill="currentColor"/></svg>
@@ -0,0 +1 @@
1
+ <svg stroke-linejoin="round" viewBox="0 0 22 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.5 2.25H2.5C1.80964 2.25 1.25 2.80964 1.25 3.5V12.5C1.25 13.1904 1.80964 13.75 2.5 13.75H19.5C20.1904 13.75 20.75 13.1904 20.75 12.5V3.5C20.75 2.80964 20.1904 2.25 19.5 2.25ZM2.5 1C1.11929 1 0 2.11929 0 3.5V12.5C0 13.8807 1.11929 15 2.5 15H19.5C20.8807 15 22 13.8807 22 12.5V3.5C22 2.11929 20.8807 1 19.5 1H2.5ZM3 4.5H4H4.25H4.6899L4.98715 4.82428L7 7.02011L9.01285 4.82428L9.3101 4.5H9.75H10H11V5.5V11.5H9V7.79807L7.73715 9.17572L7 9.97989L6.26285 9.17572L5 7.79807V11.5H3V5.5V4.5ZM15 8V4.5H17V8H19.5L17 10.5L16 11.5L15 10.5L12.5 8H15Z" fill="currentColor"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08-4.778 2.758a.795.795 0 0 0-.393.681zm1.097-2.365 2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5Z" fill="currentColor"></path></svg>
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export { default as reallySimpleDocs } from "./astro/index.js";
@@ -0,0 +1,83 @@
1
+ ---
2
+ import "virtual:reallysimpledocs/styles.css";
3
+ import config from "virtual:reallysimpledocs/config";
4
+ import { getSite } from "../lib/site.js";
5
+
6
+ const { title, description, pagePath = "/" } = Astro.props;
7
+ const site = getSite(config);
8
+ const metaDescription = description || site.description || "";
9
+ const metaTitle = pagePath === "/" ? `${site.title} | ${metaDescription}` : `${title || metaDescription} | ${site.title}`;
10
+ const siteUrl = Astro.site?.toString().replace(/\/$/, "") || site.url || "";
11
+ const absoluteUrl = `${siteUrl}${pagePath}`;
12
+ const assetsBase = config.assetsBase || "/assets";
13
+ const favicon = site.assets?.favicon || site.favicon || "";
14
+ const appleTouchIcon = site.assets?.appleTouchIcon || site.appleTouchIcon || "";
15
+ const socialImage = site.assets?.socialImage || site.socialImage || "";
16
+ const assetPath = (value) => {
17
+ const path = String(value || "");
18
+ if (!path || path.startsWith("http") || path.startsWith("/")) return path;
19
+ return `${assetsBase.replace(/\/$/, "")}/${path}`;
20
+ };
21
+ const absoluteAssetUrl = (value) => {
22
+ const path = assetPath(value);
23
+ return path.startsWith("http") ? path : `${siteUrl}${path}`;
24
+ };
25
+ ---
26
+
27
+ <!doctype html>
28
+ <html lang="en">
29
+ <head>
30
+ <script is:inline>
31
+ (() => {
32
+ try {
33
+ const stored = localStorage.getItem("themeMode");
34
+ if (stored ? stored === "dark" : matchMedia("(prefers-color-scheme: dark)").matches) {
35
+ document.documentElement.classList.add("dark");
36
+ }
37
+ } catch (_) {}
38
+
39
+ const apply = (dark) => {
40
+ document.documentElement.classList.toggle("dark", dark);
41
+ try {
42
+ localStorage.setItem("themeMode", dark ? "dark" : "light");
43
+ } catch (_) {}
44
+ };
45
+
46
+ document.addEventListener("basecoat:theme", (event) => {
47
+ const mode = event.detail?.mode;
48
+ apply(mode === "dark" ? true : mode === "light" ? false : !document.documentElement.classList.contains("dark"));
49
+ });
50
+ })();
51
+ </script>
52
+ <meta charset="utf-8" />
53
+ <meta http-equiv="Content-Language" content="en" />
54
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
55
+ <title>{metaTitle}</title>
56
+ <meta name="description" content={metaDescription} />
57
+ <meta name="title" content={metaTitle} />
58
+ {site.keywords && <meta name="keywords" content={site.keywords.join(",")} />}
59
+ {favicon && <link rel="icon" type="image/svg+xml" href={assetPath(favicon)} />}
60
+ {appleTouchIcon && <link rel="apple-touch-icon" sizes="180x180" href={assetPath(appleTouchIcon)} />}
61
+ <meta property="og:type" content="website" />
62
+ <meta property="og:url" content={absoluteUrl} />
63
+ <meta property="og:title" content={metaTitle} />
64
+ <meta property="og:description" content={metaDescription} />
65
+ {socialImage && <meta property="og:image" content={absoluteAssetUrl(socialImage)} />}
66
+ <meta property="og:site_name" content={site.title} />
67
+ <meta property="og:locale" content="en_US" />
68
+ {site.author?.name && <meta property="og:author" content={site.author.name} />}
69
+ <meta name="twitter:card" content="summary_large_image" />
70
+ <meta name="twitter:url" content={absoluteUrl} />
71
+ <meta name="twitter:title" content={metaTitle} />
72
+ <meta name="twitter:description" content={metaDescription} />
73
+ {socialImage && <meta name="twitter:image" content={absoluteAssetUrl(socialImage)} />}
74
+ {site.author?.x && <meta name="twitter:creator" content={site.author.x} />}
75
+ <script>
76
+ import "basecoat-css/all.min";
77
+ import "../scripts/copy-code.js";
78
+ </script>
79
+ </head>
80
+ <body>
81
+ <slot />
82
+ </body>
83
+ </html>