doccupine 0.0.88 → 0.0.89
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 -2
- package/dist/index.js +79 -4
- package/dist/lib/layout.js +38 -24
- package/dist/lib/metadata.d.ts +30 -0
- package/dist/lib/metadata.js +98 -1
- package/dist/templates/app/theme.d.ts +1 -1
- package/dist/templates/app/theme.js +84 -19
- package/dist/templates/components/Chat.d.ts +1 -1
- package/dist/templates/components/Chat.js +26 -27
- package/dist/templates/components/SearchModalContent.d.ts +1 -1
- package/dist/templates/components/SearchModalContent.js +12 -6
- package/dist/templates/components/SideBar.d.ts +1 -1
- package/dist/templates/components/SideBar.js +3 -1
- package/dist/templates/components/layout/Accordion.d.ts +1 -1
- package/dist/templates/components/layout/Accordion.js +2 -1
- package/dist/templates/components/layout/ActionBar.d.ts +1 -1
- package/dist/templates/components/layout/ActionBar.js +4 -6
- package/dist/templates/components/layout/Button.d.ts +1 -1
- package/dist/templates/components/layout/Button.js +10 -0
- package/dist/templates/components/layout/Callout.d.ts +1 -1
- package/dist/templates/components/layout/Callout.js +75 -20
- package/dist/templates/components/layout/Card.d.ts +1 -1
- package/dist/templates/components/layout/Card.js +2 -1
- package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/CherryThemeProvider.js +6 -12
- package/dist/templates/components/layout/ClientThemeProvider.d.ts +1 -1
- package/dist/templates/components/layout/ClientThemeProvider.js +45 -40
- package/dist/templates/components/layout/Code.d.ts +1 -1
- package/dist/templates/components/layout/Code.js +223 -255
- package/dist/templates/components/layout/ColorSwatch.d.ts +1 -1
- package/dist/templates/components/layout/ColorSwatch.js +2 -2
- package/dist/templates/components/layout/Columns.d.ts +1 -1
- package/dist/templates/components/layout/Columns.js +1 -1
- package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
- package/dist/templates/components/layout/DemoTheme.js +65 -167
- package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
- package/dist/templates/components/layout/DocsComponents.js +13 -19
- package/dist/templates/components/layout/Field.d.ts +1 -1
- package/dist/templates/components/layout/Field.js +6 -4
- package/dist/templates/components/layout/Footer.d.ts +1 -1
- package/dist/templates/components/layout/Footer.js +1 -2
- package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
- package/dist/templates/components/layout/GlobalStyles.js +63 -10
- package/dist/templates/components/layout/Header.d.ts +1 -1
- package/dist/templates/components/layout/Header.js +14 -11
- package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
- package/dist/templates/components/layout/SharedStyles.js +4 -5
- package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
- package/dist/templates/components/layout/StaticLinks.js +4 -6
- package/dist/templates/components/layout/Steps.d.ts +1 -1
- package/dist/templates/components/layout/Steps.js +3 -3
- package/dist/templates/components/layout/Tabs.d.ts +1 -1
- package/dist/templates/components/layout/Tabs.js +5 -2
- package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
- package/dist/templates/components/layout/ThemeToggle.js +17 -19
- package/dist/templates/components/layout/Typography.d.ts +1 -1
- package/dist/templates/components/layout/Typography.js +1 -1
- package/dist/templates/components/layout/Update.d.ts +1 -1
- package/dist/templates/components/layout/Update.js +4 -3
- package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
- package/dist/templates/mdx/platform/site-settings.mdx.js +5 -1
- package/dist/templates/package.js +0 -1
- package/dist/templates/proxy.js +14 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ Place these JSON files in your project root (where you run `doccupine`). They ar
|
|
|
129
129
|
| File | Purpose |
|
|
130
130
|
| ----------------- | --------------------------------------------------------------------------------------------------------- |
|
|
131
131
|
| `doccupine.json` | CLI config (watchDir, outputDir, port). Auto-generated on first run. |
|
|
132
|
-
| `config.json` | Site metadata: `name`, `description`, `icon`, `image` URL
|
|
132
|
+
| `config.json` | Site metadata: `name`, `description`, `icon`, `image`, `url` (public site URL for sitemap/robots) |
|
|
133
133
|
| `theme.json` | Theme overrides for [cherry-styled-components](https://github.com/cherry-design-system/styled-components) |
|
|
134
134
|
| `navigation.json` | Manual navigation structure (overrides auto-generated) |
|
|
135
135
|
| `links.json` | Static header/footer links |
|
|
@@ -139,7 +139,20 @@ Place these JSON files in your project root (where you run `doccupine`). They ar
|
|
|
139
139
|
|
|
140
140
|
## Public Directory
|
|
141
141
|
|
|
142
|
-
Place static assets (images, favicons,
|
|
142
|
+
Place static assets (images, favicons, etc.) in a `public/` directory at your project root. Doccupine copies it to the generated Next.js app on startup and watches for changes, so added, modified, or deleted files are synced automatically.
|
|
143
|
+
|
|
144
|
+
## Sitemap and robots.txt
|
|
145
|
+
|
|
146
|
+
Doccupine generates `robots.ts` automatically for every site. When you set a `url` in `config.json`, it also generates `sitemap.ts` covering every page (across all sections) and links the sitemap from `robots.txt`.
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"name": "My Docs",
|
|
151
|
+
"url": "https://docs.example.com"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
You can override the URL at deploy time by setting the `NEXT_PUBLIC_SITE_URL` environment variable. When no URL is configured (neither in `config.json` nor via env), the sitemap is skipped and `robots.txt` is emitted without a sitemap reference.
|
|
143
156
|
|
|
144
157
|
## AI Chat Setup
|
|
145
158
|
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { appStructure, startingDocsStructure } from "./lib/structures.js";
|
|
|
10
10
|
import { layoutTemplate } from "./lib/layout.js";
|
|
11
11
|
import { ConfigManager } from "./lib/config-manager.js";
|
|
12
12
|
import { findAvailablePort, generateSlug, getFullSlug, escapeTemplateContent, } from "./lib/utils.js";
|
|
13
|
-
import { generateMetadataBlock, generateRuntimeOnlyMetadataBlock, } from "./lib/metadata.js";
|
|
13
|
+
import { generateMetadataBlock, generateRuntimeOnlyMetadataBlock, generateJsonLdScript, } from "./lib/metadata.js";
|
|
14
14
|
import { nextConfigTemplate } from "./templates/next.config.js";
|
|
15
15
|
import { proxyTemplate } from "./templates/proxy.js";
|
|
16
16
|
import { robotsTemplate } from "./templates/app/robots.js";
|
|
@@ -786,6 +786,20 @@ export default function SectionIndex() {
|
|
|
786
786
|
description: fm.description,
|
|
787
787
|
icon: fm.icon,
|
|
788
788
|
image: fm.image,
|
|
789
|
+
canonicalPath: mdxFile.slug,
|
|
790
|
+
});
|
|
791
|
+
const jsonLd = generateJsonLdScript({
|
|
792
|
+
kind: "article",
|
|
793
|
+
canonicalPath: mdxFile.slug,
|
|
794
|
+
title: fm.title,
|
|
795
|
+
description: fm.description,
|
|
796
|
+
date: typeof fm.date === "string" ? fm.date : undefined,
|
|
797
|
+
updated: typeof fm.updated === "string"
|
|
798
|
+
? fm.updated
|
|
799
|
+
: typeof fm.date === "string"
|
|
800
|
+
? fm.date
|
|
801
|
+
: undefined,
|
|
802
|
+
image: fm.image,
|
|
789
803
|
});
|
|
790
804
|
const pageContent = `import { Metadata } from "next";
|
|
791
805
|
import { Docs } from "@/components/Docs";
|
|
@@ -795,8 +809,21 @@ const content = \`${escapeTemplateContent(mdxFile.content)}\`;
|
|
|
795
809
|
|
|
796
810
|
${metadataBlock}
|
|
797
811
|
|
|
812
|
+
// Doc pages have no per-request data: theme resolves client-side via the
|
|
813
|
+
// "dark" class on <html> (set before paint by the theme-init blocking
|
|
814
|
+
// script). Static rendering lets every response come from the edge cache.
|
|
815
|
+
export const dynamic = "force-static";
|
|
816
|
+
export const revalidate = false;
|
|
817
|
+
|
|
798
818
|
export default function Page() {
|
|
799
|
-
|
|
819
|
+
${jsonLd.declarations}
|
|
820
|
+
|
|
821
|
+
return (
|
|
822
|
+
<>
|
|
823
|
+
${jsonLd.element}
|
|
824
|
+
<Docs content={content} />
|
|
825
|
+
</>
|
|
826
|
+
);
|
|
800
827
|
}
|
|
801
828
|
`;
|
|
802
829
|
const pagePath = path.join(this.outputDir, "app", mdxFile.slug, "page.tsx");
|
|
@@ -818,6 +845,10 @@ export default function Page() {
|
|
|
818
845
|
icon: frontmatter.icon,
|
|
819
846
|
image: frontmatter.image,
|
|
820
847
|
name: frontmatter.name,
|
|
848
|
+
date: typeof frontmatter.date === "string" ? frontmatter.date : undefined,
|
|
849
|
+
updated: typeof frontmatter.updated === "string"
|
|
850
|
+
? frontmatter.updated
|
|
851
|
+
: undefined,
|
|
821
852
|
};
|
|
822
853
|
break;
|
|
823
854
|
}
|
|
@@ -831,8 +862,18 @@ export default function Page() {
|
|
|
831
862
|
description: indexMDX.description || undefined,
|
|
832
863
|
icon: indexMDX.icon,
|
|
833
864
|
image: indexMDX.image,
|
|
865
|
+
canonicalPath: "",
|
|
834
866
|
})
|
|
835
867
|
: generateRuntimeOnlyMetadataBlock();
|
|
868
|
+
const homeJsonLd = generateJsonLdScript({
|
|
869
|
+
kind: "homepage",
|
|
870
|
+
canonicalPath: "",
|
|
871
|
+
title: indexMDX?.title,
|
|
872
|
+
description: indexMDX?.description || undefined,
|
|
873
|
+
date: indexMDX?.date,
|
|
874
|
+
updated: indexMDX?.updated ?? indexMDX?.date,
|
|
875
|
+
image: indexMDX?.image,
|
|
876
|
+
});
|
|
836
877
|
const indexContent = `import { Metadata } from "next";
|
|
837
878
|
import { Docs } from "@/components/Docs";
|
|
838
879
|
import { config } from "@/utils/config";
|
|
@@ -841,8 +882,18 @@ ${indexMDX ? `const content = \`${escapeTemplateContent(indexMDX.content)}\`;` :
|
|
|
841
882
|
|
|
842
883
|
${metadataBlock}
|
|
843
884
|
|
|
885
|
+
export const dynamic = "force-static";
|
|
886
|
+
export const revalidate = false;
|
|
887
|
+
|
|
844
888
|
export default function Home() {
|
|
845
|
-
|
|
889
|
+
${homeJsonLd.declarations}
|
|
890
|
+
|
|
891
|
+
return (
|
|
892
|
+
<>
|
|
893
|
+
${homeJsonLd.element}
|
|
894
|
+
<Docs content={content} />
|
|
895
|
+
</>
|
|
896
|
+
);
|
|
846
897
|
}
|
|
847
898
|
`;
|
|
848
899
|
await fs.writeFile(path.join(this.outputDir, "app", "page.tsx"), indexContent, "utf8");
|
|
@@ -856,6 +907,20 @@ export default function Home() {
|
|
|
856
907
|
description: frontmatter.description || undefined,
|
|
857
908
|
icon: frontmatter.icon,
|
|
858
909
|
image: frontmatter.image,
|
|
910
|
+
canonicalPath: sectionSlug,
|
|
911
|
+
});
|
|
912
|
+
const sectionJsonLd = generateJsonLdScript({
|
|
913
|
+
kind: "article",
|
|
914
|
+
canonicalPath: sectionSlug,
|
|
915
|
+
title: frontmatter.title,
|
|
916
|
+
description: frontmatter.description,
|
|
917
|
+
date: typeof frontmatter.date === "string" ? frontmatter.date : undefined,
|
|
918
|
+
updated: typeof frontmatter.updated === "string"
|
|
919
|
+
? frontmatter.updated
|
|
920
|
+
: typeof frontmatter.date === "string"
|
|
921
|
+
? frontmatter.date
|
|
922
|
+
: undefined,
|
|
923
|
+
image: frontmatter.image,
|
|
859
924
|
});
|
|
860
925
|
const indexContent = `import { Metadata } from "next";
|
|
861
926
|
import { Docs } from "@/components/Docs";
|
|
@@ -865,8 +930,18 @@ const content = \`${escapeTemplateContent(mdxContent)}\`;
|
|
|
865
930
|
|
|
866
931
|
${metadataBlock}
|
|
867
932
|
|
|
933
|
+
export const dynamic = "force-static";
|
|
934
|
+
export const revalidate = false;
|
|
935
|
+
|
|
868
936
|
export default function Page() {
|
|
869
|
-
|
|
937
|
+
${sectionJsonLd.declarations}
|
|
938
|
+
|
|
939
|
+
return (
|
|
940
|
+
<>
|
|
941
|
+
${sectionJsonLd.element}
|
|
942
|
+
<Docs content={content} />
|
|
943
|
+
</>
|
|
944
|
+
);
|
|
870
945
|
}
|
|
871
946
|
`;
|
|
872
947
|
const pagePath = path.join(this.outputDir, "app", sectionSlug, "page.tsx");
|
package/dist/lib/layout.js
CHANGED
|
@@ -48,9 +48,8 @@ ${a} >`
|
|
|
48
48
|
return `import type { Metadata } from "next";
|
|
49
49
|
${isGoogleFont(fontConfig) ? `import { ${fontConfig.googleFont.fontName} } from "next/font/google";` : isLocalFont(fontConfig) ? 'import localFont from "next/font/local";' : 'import { Inter } from "next/font/google";'}
|
|
50
50
|
import dynamic from "next/dynamic";
|
|
51
|
-
import Script from "next/script";
|
|
52
51
|
import { StyledComponentsRegistry } from "cherry-styled-components";
|
|
53
|
-
import { theme
|
|
52
|
+
import { theme } from "@/app/theme";
|
|
54
53
|
import { CherryThemeProvider } from "@/components/layout/CherryThemeProvider";
|
|
55
54
|
import { ChtProvider } from "@/components/Chat";
|
|
56
55
|
import { SearchProvider } from "@/components/SearchDocs";
|
|
@@ -90,7 +89,18 @@ ${isGoogleFont(fontConfig)
|
|
|
90
89
|
});`
|
|
91
90
|
: 'const font = Inter({ subsets: ["latin"] });'}
|
|
92
91
|
|
|
92
|
+
function resolveSiteUrl(): URL | undefined {
|
|
93
|
+
const raw = process.env.NEXT_PUBLIC_SITE_URL ?? config.url;
|
|
94
|
+
if (!raw || typeof raw !== "string") return undefined;
|
|
95
|
+
try {
|
|
96
|
+
return new URL(raw);
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
93
102
|
export const metadata: Metadata = {
|
|
103
|
+
metadataBase: resolveSiteUrl(),
|
|
94
104
|
title: config.name || "${DEFAULT_SITE_NAME}",
|
|
95
105
|
description:
|
|
96
106
|
config.description ||
|
|
@@ -118,24 +128,26 @@ ${hasSections
|
|
|
118
128
|
const pages: PagesProps[] = doccupinePages;
|
|
119
129
|
|
|
120
130
|
return (
|
|
121
|
-
<html lang="en">
|
|
131
|
+
<html lang="en" suppressHydrationWarning>
|
|
122
132
|
<head>
|
|
123
|
-
{/*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
{/* Resolves dark mode before first paint by adding the "dark" class
|
|
134
|
+
to <html> when needed. CSS variables in GlobalStyles flip values
|
|
135
|
+
on :root vs :root.dark, so the right palette renders without a
|
|
136
|
+
React roundtrip. Inlined as a plain <script> (not next/script) so
|
|
137
|
+
it ships in the SSR HTML and runs synchronously before paint —
|
|
138
|
+
next/script with beforeInteractive is async in App Router and
|
|
139
|
+
would still show a flash. suppressHydrationWarning on <html>
|
|
140
|
+
tells React the class/colorScheme attributes are intentionally
|
|
141
|
+
different between server (no class) and client (after script). */}
|
|
142
|
+
<script
|
|
131
143
|
dangerouslySetInnerHTML={{
|
|
132
|
-
__html: \`(function(){try{var c=document.cookie.split(";").
|
|
144
|
+
__html: \`(function(){try{var c=document.cookie.split(";").map(function(s){return s.trim();}).find(function(s){return s.indexOf("theme=")===0;});var v=c?c.split("=")[1]:null;var d=v?v==="dark":(window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches);if(!v){document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";}if(d){document.documentElement.classList.add("dark");document.documentElement.style.colorScheme="dark";}else{document.documentElement.style.colorScheme="light";}}catch(e){}})();\`,
|
|
133
145
|
}}
|
|
134
146
|
/>
|
|
135
147
|
</head>
|
|
136
148
|
<body className={font.className}>
|
|
137
149
|
<StyledComponentsRegistry>
|
|
138
|
-
${analyticsEnabled ? " <PostHogProvider>\n" : ""}${a} <CherryThemeProvider theme={theme}
|
|
150
|
+
${analyticsEnabled ? " <PostHogProvider>\n" : ""}${a} <CherryThemeProvider theme={theme}>
|
|
139
151
|
${a} ${chtOpen}
|
|
140
152
|
${a} <SearchProvider pages={pages} sections={doccupineSections}>
|
|
141
153
|
${a} <Header>
|
|
@@ -181,24 +193,26 @@ ${analyticsEnabled ? " </PostHogProvider>\n" : ""} </StyledCompo
|
|
|
181
193
|
const defaultResults = transformPagesToGroupedStructure(defaultPages);
|
|
182
194
|
|
|
183
195
|
return (
|
|
184
|
-
<html lang="en">
|
|
196
|
+
<html lang="en" suppressHydrationWarning>
|
|
185
197
|
<head>
|
|
186
|
-
{/*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
{/* Resolves dark mode before first paint by adding the "dark" class
|
|
199
|
+
to <html> when needed. CSS variables in GlobalStyles flip values
|
|
200
|
+
on :root vs :root.dark, so the right palette renders without a
|
|
201
|
+
React roundtrip. Inlined as a plain <script> (not next/script) so
|
|
202
|
+
it ships in the SSR HTML and runs synchronously before paint —
|
|
203
|
+
next/script with beforeInteractive is async in App Router and
|
|
204
|
+
would still show a flash. suppressHydrationWarning on <html>
|
|
205
|
+
tells React the class/colorScheme attributes are intentionally
|
|
206
|
+
different between server (no class) and client (after script). */}
|
|
207
|
+
<script
|
|
194
208
|
dangerouslySetInnerHTML={{
|
|
195
|
-
__html: \`(function(){try{var c=document.cookie.split(";").
|
|
209
|
+
__html: \`(function(){try{var c=document.cookie.split(";").map(function(s){return s.trim();}).find(function(s){return s.indexOf("theme=")===0;});var v=c?c.split("=")[1]:null;var d=v?v==="dark":(window.matchMedia&&window.matchMedia("(prefers-color-scheme:dark)").matches);if(!v){document.cookie="theme="+(d?"dark":"light")+";path=/;max-age=31536000;SameSite=Lax";}if(d){document.documentElement.classList.add("dark");document.documentElement.style.colorScheme="dark";}else{document.documentElement.style.colorScheme="light";}}catch(e){}})();\`,
|
|
196
210
|
}}
|
|
197
211
|
/>
|
|
198
212
|
</head>
|
|
199
213
|
<body className={font.className}>
|
|
200
214
|
<StyledComponentsRegistry>
|
|
201
|
-
${analyticsEnabled ? " <PostHogProvider>\n" : ""}${a} <CherryThemeProvider theme={theme}
|
|
215
|
+
${analyticsEnabled ? " <PostHogProvider>\n" : ""}${a} <CherryThemeProvider theme={theme}>
|
|
202
216
|
${a} ${chtOpen}
|
|
203
217
|
${a} <SearchProvider pages={pages}>
|
|
204
218
|
${a} <Header />
|
package/dist/lib/metadata.d.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
export type JsonLdKind = "article" | "homepage";
|
|
2
|
+
export interface JsonLdOptions {
|
|
3
|
+
kind: JsonLdKind;
|
|
4
|
+
/** Page slug ("" for homepage). Used to build the absolute URL with config.url. */
|
|
5
|
+
canonicalPath: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
date?: string;
|
|
9
|
+
/** Optional last-modified date; defaults to date when present. */
|
|
10
|
+
updated?: string;
|
|
11
|
+
image?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns a code snippet that:
|
|
15
|
+
* - declares a `jsonLd` object at runtime using the site's config,
|
|
16
|
+
* - and renders a single `<script type="application/ld+json">` element.
|
|
17
|
+
*
|
|
18
|
+
* The snippet is meant to be inlined inside a generated page component.
|
|
19
|
+
* It emits a TechArticle on every doc page; the homepage additionally
|
|
20
|
+
* emits a graph that includes an Organization entity for entity recognition.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateJsonLdScript(opts: JsonLdOptions): {
|
|
23
|
+
declarations: string;
|
|
24
|
+
element: string;
|
|
25
|
+
};
|
|
1
26
|
export interface MetadataOptions {
|
|
2
27
|
title?: string;
|
|
3
28
|
titleFallback: string;
|
|
@@ -6,6 +31,11 @@ export interface MetadataOptions {
|
|
|
6
31
|
description?: string;
|
|
7
32
|
icon?: string;
|
|
8
33
|
image?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Canonical path for this page, e.g. "" for the homepage or "components".
|
|
36
|
+
* Resolves against `metadataBase` defined in the root layout.
|
|
37
|
+
*/
|
|
38
|
+
canonicalPath?: string;
|
|
9
39
|
}
|
|
10
40
|
export declare function generateMetadataBlock(opts: MetadataOptions): string;
|
|
11
41
|
export declare function generateRuntimeOnlyMetadataBlock(): string;
|
package/dist/lib/metadata.js
CHANGED
|
@@ -1,4 +1,91 @@
|
|
|
1
1
|
import { DEFAULT_FAVICON, DEFAULT_META_DESCRIPTION, DEFAULT_OG_IMAGE, DEFAULT_SITE_NAME, } from "./constants.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a code snippet that:
|
|
4
|
+
* - declares a `jsonLd` object at runtime using the site's config,
|
|
5
|
+
* - and renders a single `<script type="application/ld+json">` element.
|
|
6
|
+
*
|
|
7
|
+
* The snippet is meant to be inlined inside a generated page component.
|
|
8
|
+
* It emits a TechArticle on every doc page; the homepage additionally
|
|
9
|
+
* emits a graph that includes an Organization entity for entity recognition.
|
|
10
|
+
*/
|
|
11
|
+
export function generateJsonLdScript(opts) {
|
|
12
|
+
const safePath = opts.canonicalPath.replace(/^\/+/, "");
|
|
13
|
+
const titleLiteral = JSON.stringify(opts.title ?? DEFAULT_SITE_NAME);
|
|
14
|
+
const descLiteral = JSON.stringify(opts.description ?? DEFAULT_META_DESCRIPTION);
|
|
15
|
+
const dateLiteral = opts.date ? JSON.stringify(opts.date) : "undefined";
|
|
16
|
+
const updatedLiteral = opts.updated
|
|
17
|
+
? JSON.stringify(opts.updated)
|
|
18
|
+
: opts.date
|
|
19
|
+
? JSON.stringify(opts.date)
|
|
20
|
+
: "undefined";
|
|
21
|
+
const defaultFaviconLiteral = JSON.stringify(DEFAULT_FAVICON);
|
|
22
|
+
const faviconLine = opts.image
|
|
23
|
+
? `const faviconUrl =
|
|
24
|
+
${JSON.stringify(opts.image)} ||
|
|
25
|
+
config.icon ||
|
|
26
|
+
${defaultFaviconLiteral};`
|
|
27
|
+
: `const faviconUrl = config.icon || ${defaultFaviconLiteral};`;
|
|
28
|
+
// Indent 10 + "description: ".length(13) + ",".length(1) = 24. Prettier
|
|
29
|
+
// wraps the value to the next line (indent 12) once total exceeds 80.
|
|
30
|
+
const descriptionLine = descLiteral.length > 56
|
|
31
|
+
? `description:
|
|
32
|
+
${descLiteral},`
|
|
33
|
+
: `description: ${descLiteral},`;
|
|
34
|
+
const pathLiteral = JSON.stringify(safePath);
|
|
35
|
+
const homepageGraph = opts.kind === "homepage"
|
|
36
|
+
? `,
|
|
37
|
+
{
|
|
38
|
+
"@type": "Organization",
|
|
39
|
+
name: siteName,
|
|
40
|
+
url: baseUrl ?? undefined,
|
|
41
|
+
logo: faviconUrl,
|
|
42
|
+
}`
|
|
43
|
+
: "";
|
|
44
|
+
const declarations = `const __jsonLdBaseUrl = (() => {
|
|
45
|
+
const raw =
|
|
46
|
+
typeof process !== "undefined"
|
|
47
|
+
? process.env.NEXT_PUBLIC_SITE_URL
|
|
48
|
+
: undefined;
|
|
49
|
+
const fromEnv = typeof raw === "string" && raw.trim() !== "" ? raw : null;
|
|
50
|
+
const fromConfig =
|
|
51
|
+
typeof config.url === "string" && config.url.trim() !== ""
|
|
52
|
+
? config.url
|
|
53
|
+
: null;
|
|
54
|
+
return (fromEnv ?? fromConfig)?.replace(/\\/$/, "") ?? null;
|
|
55
|
+
})();
|
|
56
|
+
const __jsonLd = (() => {
|
|
57
|
+
const baseUrl = __jsonLdBaseUrl;
|
|
58
|
+
const path = ${pathLiteral};
|
|
59
|
+
const url = baseUrl ? (path ? \`\${baseUrl}/\${path}\` : baseUrl) : undefined;
|
|
60
|
+
const siteName = config.name || ${JSON.stringify(DEFAULT_SITE_NAME)};
|
|
61
|
+
${faviconLine}
|
|
62
|
+
return {
|
|
63
|
+
"@context": "https://schema.org",
|
|
64
|
+
"@graph": [
|
|
65
|
+
{
|
|
66
|
+
"@type": "TechArticle",
|
|
67
|
+
headline: ${titleLiteral},
|
|
68
|
+
${descriptionLine}
|
|
69
|
+
url,
|
|
70
|
+
mainEntityOfPage: url,
|
|
71
|
+
datePublished: ${dateLiteral},
|
|
72
|
+
dateModified: ${updatedLiteral},
|
|
73
|
+
author: { "@type": "Organization", name: siteName },
|
|
74
|
+
publisher: {
|
|
75
|
+
"@type": "Organization",
|
|
76
|
+
name: siteName,
|
|
77
|
+
logo: { "@type": "ImageObject", url: faviconUrl },
|
|
78
|
+
},
|
|
79
|
+
}${homepageGraph},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
})();`;
|
|
83
|
+
const element = `<script
|
|
84
|
+
type="application/ld+json"
|
|
85
|
+
dangerouslySetInnerHTML={{ __html: JSON.stringify(__jsonLd) }}
|
|
86
|
+
/>`;
|
|
87
|
+
return { declarations, element };
|
|
88
|
+
}
|
|
2
89
|
function buildFieldExpression(staticValue, configKey, defaultValue) {
|
|
3
90
|
if (staticValue)
|
|
4
91
|
return staticValue;
|
|
@@ -18,15 +105,24 @@ function buildTitleExpression(opts) {
|
|
|
18
105
|
: `\${config.name ? config.name + " -" : "${DEFAULT_SITE_NAME} -"}`;
|
|
19
106
|
return `${prefix} ${title}`;
|
|
20
107
|
}
|
|
108
|
+
function buildCanonicalLine(canonicalPath) {
|
|
109
|
+
if (canonicalPath === undefined)
|
|
110
|
+
return "";
|
|
111
|
+
const safePath = canonicalPath.replace(/^\/+/, "");
|
|
112
|
+
// Relative path resolves against `metadataBase` set in the root layout.
|
|
113
|
+
// Empty string means the homepage canonical equals the base URL itself.
|
|
114
|
+
return `\n alternates: { canonical: ${JSON.stringify("/" + safePath)} },`;
|
|
115
|
+
}
|
|
21
116
|
export function generateMetadataBlock(opts) {
|
|
22
117
|
const title = buildTitleExpression(opts);
|
|
23
118
|
const desc = buildFieldExpression(opts.description, "description", DEFAULT_META_DESCRIPTION);
|
|
24
119
|
const icon = buildFieldExpression(opts.icon, "icon", DEFAULT_FAVICON);
|
|
25
120
|
const image = buildFieldExpression(opts.image, "image", DEFAULT_OG_IMAGE);
|
|
121
|
+
const canonical = buildCanonicalLine(opts.canonicalPath);
|
|
26
122
|
return `export const metadata: Metadata = {
|
|
27
123
|
title: \`${title}\`,
|
|
28
124
|
description: \`${desc}\`,
|
|
29
|
-
icons: \`${icon}
|
|
125
|
+
icons: \`${icon}\`,${canonical}
|
|
30
126
|
openGraph: {
|
|
31
127
|
title: \`${title}\`,
|
|
32
128
|
description: \`${desc}\`,
|
|
@@ -43,6 +139,7 @@ export function generateRuntimeOnlyMetadataBlock() {
|
|
|
43
139
|
title: \`${title}\`,
|
|
44
140
|
description: \`${desc}\`,
|
|
45
141
|
icons: \`${icon}\`,
|
|
142
|
+
alternates: { canonical: "/" },
|
|
46
143
|
openGraph: {
|
|
47
144
|
title: \`${title}\`,
|
|
48
145
|
description: \`${desc}\`,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const SIDEBAR_WIDTH = 280;
|
|
2
2
|
export declare const CHAT_WIDTH = 420;
|
|
3
|
-
export declare const themeTemplate = "\"use client\";\nimport customThemeJson from \"@/theme.json\";\n\ninterface CustomTheme {\n default?: Partial<Colors>;\n dark?: Partial<Colors>;\n}\n\nconst customTheme = customThemeJson as CustomTheme;\n\nconst breakpoints: Breakpoints = {\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200,\n xxl: 1440,\n xxxl: 1920,\n};\n\nexport function mq(minWidth: keyof Breakpoints) {\n return `@media screen and (min-width: ${breakpoints[minWidth]}px)`;\n}\n\nconst spacing: Spacing = {\n maxWidth: { xs: \"1280px\", xxxl: \"1440px\" },\n padding: { xs: \"20px\", lg: \"40px\" },\n radius: { xs: \"6px\", lg: \"12px\", xl: \"30px\" },\n gridGap: { xs: \"20px\", lg: \"40px\" },\n};\n\nconst colors: Colors = {\n primaryLight: \"#d1d5db\",\n primary: \"#556272\",\n primaryDark: \"#374151\",\n secondaryLight: \"#c4b5fd\",\n secondary: \"#8b5cf6\",\n secondaryDark: \"#5b21b6\",\n tertiaryLight: \"#86efac\",\n tertiary: \"#22c55e\",\n tertiaryDark: \"#15803d\",\n grayLight: \"#e5e7eb\",\n gray: \"#9ca3af\",\n grayDark: \"#4b5563\",\n success: \"#84cc16\",\n error: \"#ef4444\",\n warning: \"#eab308\",\n info: \"#06b6d4\",\n dark: \"#000000\",\n light: \"#ffffff\",\n ...(customTheme.default ? (customTheme.default as Partial<Colors>) : {}),\n};\n\nconst colorsDark: Colors = {\n primaryLight: \"#9ca3af\",\n primary: \"#6b7280\",\n primaryDark: \"#374151\",\n secondaryLight: \"#ddd6fe\",\n secondary: \"#a78bfa\",\n secondaryDark: \"#7c3aed\",\n tertiaryLight: \"#6ee7b7\",\n tertiary: \"#10b981\",\n tertiaryDark: \"#065f46\",\n grayLight: \"#1a1a1a\",\n gray: \"#454444\",\n grayDark: \"#808080\",\n success: \"#84cc16\",\n error: \"#ef4444\",\n warning: \"#eab308\",\n info: \"#06b6d4\",\n dark: \"#ffffff\",\n light: \"#000000\",\n ...(customTheme.dark ? (customTheme.dark as Partial<Colors>) : {}),\n};\n\nconst shadows: Shadows = {\n xs: \"0px 4px 4px 0px rgba(18, 18, 18, 0.04), 0px 1px 3px 0px rgba(39, 41, 45, 0.02)\",\n sm: \"0px 4px 4px 0px rgba(18, 18, 18, 0.08), 0px 1px 3px 0px rgba(39, 41, 45, 0.04)\",\n md: \"0px 8px 8px 0px rgba(18, 18, 18, 0.16), 0px 2px 3px 0px rgba(39, 41, 45, 0.06)\",\n lg: \"0px 16px 24px 0px rgba(18, 18, 18, 0.20), 0px 2px 3px 0px rgba(39, 41, 45, 0.08)\",\n xl: \"0px 24px 32px 0px rgba(18, 18, 18, 0.24), 0px 2px 3px 0px rgba(39, 41, 45, 0.12)\",\n};\n\nconst shadowsDark: Shadows = {\n xs: \"0px 4px 4px 0px rgba(255, 255, 255, 0.04), 0px 1px 3px 0px rgba(255, 255, 255, 0.02)\",\n sm: \"0px 4px 4px 0px rgba(255, 255, 255, 0.08), 0px 1px 3px 0px rgba(255, 255, 255, 0.04)\",\n md: \"0px 8px 8px 0px rgba(255, 255, 255, 0.16), 0px 2px 3px 0px rgba(255, 255, 255, 0.06)\",\n lg: \"0px 16px 24px 0px rgba(255, 255, 255, 0.20), 0px 2px 3px 0px rgba(255, 255, 255, 0.08)\",\n xl: \"0px 24px 32px 0px rgba(255, 255, 255, 0.24), 0px 2px 3px 0px rgba(255, 255, 255, 0.12)\",\n};\n\nconst fonts: Fonts = {\n text: \"Inter\",\n head: \"Inter\",\n mono: \"Roboto Mono, monospace\",\n};\n\nconst fontSizes: FontSizes = {\n hero1: { xs: \"72px\", lg: \"128px\" },\n hero2: { xs: \"60px\", lg: \"96px\" },\n hero3: { xs: \"36px\", lg: \"72px\" },\n\n h1: { xs: \"40px\", lg: \"60px\" },\n h2: { xs: \"30px\", lg: \"36px\" },\n h3: { xs: \"28px\", lg: \"30px\" },\n h4: { xs: \"24px\", lg: \"26px\" },\n h5: { xs: \"18px\", lg: \"20px\" },\n h6: { xs: \"16px\", lg: \"18px\" },\n\n text: { xs: \"14px\", lg: \"16px\" },\n strong: { xs: \"14px\", lg: \"16px\" },\n small: { xs: \"12px\", lg: \"14px\" },\n\n blockquote: { xs: \"16px\", lg: \"18px\" },\n code: { xs: \"14px\", lg: \"16px\" },\n\n button: { xs: \"16px\", lg: \"16px\" },\n buttonBig: { xs: \"18px\", lg: \"18px\" },\n buttonSmall: { xs: \"14px\", lg: \"14px\" },\n\n input: { xs: \"16px\", lg: \"16px\" },\n inputBig: { xs: \"18px\", lg: \"18px\" },\n inputSmall: { xs: \"14px\", lg: \"14px\" },\n};\n\nconst lineHeights: LineHeights = {\n hero1: { xs: \"1.1\", lg: \"1.1\" },\n hero2: { xs: \"1.1\", lg: \"1.1\" },\n hero3: { xs: \"1.17\", lg: \"1.1\" },\n\n h1: { xs: \"1\", lg: \"1.07\" },\n h2: { xs: \"1.2\", lg: \"1.2\" },\n h3: { xs: \"1.3\", lg: \"1.5\" },\n h4: { xs: \"1.3\", lg: \"1.5\" },\n h5: { xs: \"1.6\", lg: \"1.5\" },\n h6: { xs: \"1.6\", lg: \"1.6\" },\n\n text: { xs: \"1.7\", lg: \"1.7\" },\n strong: { xs: \"1.7\", lg: \"1.7\" },\n small: { xs: \"1.7\", lg: \"1.7\" },\n\n blockquote: { xs: \"1.7\", lg: \"1.7\" },\n code: { xs: \"1.7\", lg: \"1.7\" },\n\n button: { xs: \"1\", lg: \"1\" },\n buttonBig: { xs: \"1\", lg: \"1\" },\n buttonSmall: { xs: \"1\", lg: \"1\" },\n\n input: { xs: \"1\", lg: \"1\" },\n inputBig: { xs: \"1\", lg: \"1\" },\n inputSmall: { xs: \"1\", lg: \"1\" },\n};\n\nexport const theme: Theme = {\n breakpoints,\n spacing,\n colors,\n shadows,\n fonts,\n fontSizes,\n lineHeights,\n isDark: false,\n};\n\nexport const themeDark: Theme = {\n breakpoints,\n spacing,\n colors: colorsDark,\n shadows: shadowsDark,\n fonts,\n fontSizes,\n lineHeights,\n isDark: true,\n};\n\ninterface Breakpoints<TNumber = number> {\n xs: TNumber;\n sm: TNumber;\n md: TNumber;\n lg: TNumber;\n xl: TNumber;\n xxl: TNumber;\n xxxl: TNumber;\n}\n\ninterface Spacing<TString = string> {\n maxWidth: { xs: TString; xxxl: TString };\n padding: { xs: TString; lg: TString };\n radius: { xs: TString; lg: TString; xl: TString };\n gridGap: { xs: TString; lg: TString };\n}\n\ninterface Colors<TString = string> {\n primaryLight: TString;\n primary: TString;\n primaryDark: TString;\n\n secondaryLight: TString;\n secondary: TString;\n secondaryDark: TString;\n\n tertiaryLight: TString;\n tertiary: TString;\n tertiaryDark: TString;\n\n grayLight: TString;\n gray: TString;\n grayDark: TString;\n\n success: TString;\n error: TString;\n warning: TString;\n info: TString;\n\n dark: TString;\n light: TString;\n}\n\ninterface Shadows<TString = string> {\n xs: TString;\n sm: TString;\n md: TString;\n lg: TString;\n xl: TString;\n}\n\ninterface Fonts<TString = string> {\n head: TString;\n text: TString;\n mono: TString;\n}\n\ninterface FontSizes<TString = string> {\n hero1: { xs: TString; lg: TString };\n hero2: { xs: TString; lg: TString };\n hero3: { xs: TString; lg: TString };\n\n h1: { xs: TString; lg: TString };\n h2: { xs: TString; lg: TString };\n h3: { xs: TString; lg: TString };\n h4: { xs: TString; lg: TString };\n h5: { xs: TString; lg: TString };\n h6: { xs: TString; lg: TString };\n\n text: { xs: TString; lg: TString };\n strong: { xs: TString; lg: TString };\n small: { xs: TString; lg: TString };\n\n blockquote: { xs: TString; lg: TString };\n code: { xs: TString; lg: TString };\n\n button: { xs: TString; lg: TString };\n buttonBig: { xs: TString; lg: TString };\n buttonSmall: { xs: TString; lg: TString };\n\n input: { xs: TString; lg: TString };\n inputBig: { xs: TString; lg: TString };\n inputSmall: { xs: TString; lg: TString };\n}\n\ninterface LineHeights<TString = string> {\n hero1: { xs: TString; lg: TString };\n hero2: { xs: TString; lg: TString };\n hero3: { xs: TString; lg: TString };\n\n h1: { xs: TString; lg: TString };\n h2: { xs: TString; lg: TString };\n h3: { xs: TString; lg: TString };\n h4: { xs: TString; lg: TString };\n h5: { xs: TString; lg: TString };\n h6: { xs: TString; lg: TString };\n\n text: { xs: TString; lg: TString };\n strong: { xs: TString; lg: TString };\n small: { xs: TString; lg: TString };\n\n blockquote: { xs: TString; lg: TString };\n code: { xs: TString; lg: TString };\n\n button: { xs: TString; lg: TString };\n buttonBig: { xs: TString; lg: TString };\n buttonSmall: { xs: TString; lg: TString };\n\n input: { xs: TString; lg: TString };\n inputBig: { xs: TString; lg: TString };\n inputSmall: { xs: TString; lg: TString };\n}\n\nexport interface Theme {\n breakpoints: Breakpoints;\n spacing: Spacing;\n colors: Colors;\n shadows: Shadows;\n fonts: Fonts;\n fontSizes: FontSizes;\n lineHeights: LineHeights;\n isDark: boolean;\n}\n";
|
|
3
|
+
export declare const themeTemplate = "\"use client\";\nimport customThemeJson from \"@/theme.json\";\n\n// Users can only override the brand palette via theme.json \u2014 semantic tokens\n// (accent, surface, etc.) are derived in GlobalStyles from the brand colors\n// and are not directly overridable.\ntype CustomColors = Omit<\n Colors,\n \"accent\" | \"accentStrong\" | \"accentMuted\" | \"surface\"\n>;\n\ninterface CustomTheme {\n default?: Partial<CustomColors>;\n dark?: Partial<CustomColors>;\n}\n\nconst customTheme = customThemeJson as CustomTheme;\n\nconst breakpoints: Breakpoints = {\n xs: 0,\n sm: 576,\n md: 768,\n lg: 992,\n xl: 1200,\n xxl: 1440,\n xxxl: 1920,\n};\n\nexport function mq(minWidth: keyof Breakpoints) {\n return `@media screen and (min-width: ${breakpoints[minWidth]}px)`;\n}\n\nconst spacing: Spacing = {\n maxWidth: { xs: \"1280px\", xxxl: \"1440px\" },\n padding: { xs: \"20px\", lg: \"40px\" },\n radius: { xs: \"6px\", lg: \"12px\", xl: \"30px\" },\n gridGap: { xs: \"20px\", lg: \"40px\" },\n};\n\n// Resolved hex palettes for each mode. GlobalStyles emits these as CSS\n// custom properties on :root and :root.dark. Components never read these\n// directly \u2014 they read `theme.colors.*` which resolves to var(--color-\u2026)\n// and lets the browser pick the right value based on the active class.\nexport const colorsLight: CustomColors = {\n primaryLight: \"#d1d5db\",\n primary: \"#556272\",\n primaryDark: \"#374151\",\n secondaryLight: \"#c4b5fd\",\n secondary: \"#8b5cf6\",\n secondaryDark: \"#5b21b6\",\n tertiaryLight: \"#86efac\",\n tertiary: \"#22c55e\",\n tertiaryDark: \"#15803d\",\n grayLight: \"#e5e7eb\",\n gray: \"#9ca3af\",\n grayDark: \"#4b5563\",\n success: \"#84cc16\",\n error: \"#ef4444\",\n warning: \"#eab308\",\n info: \"#06b6d4\",\n dark: \"#000000\",\n light: \"#ffffff\",\n ...(customTheme.default\n ? (customTheme.default as Partial<CustomColors>)\n : {}),\n};\n\nexport const colorsDark: CustomColors = {\n primaryLight: \"#9ca3af\",\n primary: \"#6b7280\",\n primaryDark: \"#374151\",\n secondaryLight: \"#ddd6fe\",\n secondary: \"#a78bfa\",\n secondaryDark: \"#7c3aed\",\n tertiaryLight: \"#6ee7b7\",\n tertiary: \"#10b981\",\n tertiaryDark: \"#065f46\",\n grayLight: \"#1a1a1a\",\n gray: \"#454444\",\n grayDark: \"#808080\",\n success: \"#84cc16\",\n error: \"#ef4444\",\n warning: \"#eab308\",\n info: \"#06b6d4\",\n dark: \"#ffffff\",\n light: \"#000000\",\n ...(customTheme.dark ? (customTheme.dark as Partial<CustomColors>) : {}),\n};\n\nexport const shadowsLight: Shadows = {\n xs: \"0px 4px 4px 0px rgba(18, 18, 18, 0.04), 0px 1px 3px 0px rgba(39, 41, 45, 0.02)\",\n sm: \"0px 4px 4px 0px rgba(18, 18, 18, 0.08), 0px 1px 3px 0px rgba(39, 41, 45, 0.04)\",\n md: \"0px 8px 8px 0px rgba(18, 18, 18, 0.16), 0px 2px 3px 0px rgba(39, 41, 45, 0.06)\",\n lg: \"0px 16px 24px 0px rgba(18, 18, 18, 0.20), 0px 2px 3px 0px rgba(39, 41, 45, 0.08)\",\n xl: \"0px 24px 32px 0px rgba(18, 18, 18, 0.24), 0px 2px 3px 0px rgba(39, 41, 45, 0.12)\",\n};\n\nexport const shadowsDark: Shadows = {\n xs: \"0px 4px 4px 0px rgba(255, 255, 255, 0.04), 0px 1px 3px 0px rgba(255, 255, 255, 0.02)\",\n sm: \"0px 4px 4px 0px rgba(255, 255, 255, 0.08), 0px 1px 3px 0px rgba(255, 255, 255, 0.04)\",\n md: \"0px 8px 8px 0px rgba(255, 255, 255, 0.16), 0px 2px 3px 0px rgba(255, 255, 255, 0.06)\",\n lg: \"0px 16px 24px 0px rgba(255, 255, 255, 0.20), 0px 2px 3px 0px rgba(255, 255, 255, 0.08)\",\n xl: \"0px 24px 32px 0px rgba(255, 255, 255, 0.24), 0px 2px 3px 0px rgba(255, 255, 255, 0.12)\",\n};\n\nconst fonts: Fonts = {\n text: \"Inter\",\n head: \"Inter\",\n mono: \"Roboto Mono, monospace\",\n};\n\nconst fontSizes: FontSizes = {\n hero1: { xs: \"72px\", lg: \"128px\" },\n hero2: { xs: \"60px\", lg: \"96px\" },\n hero3: { xs: \"36px\", lg: \"72px\" },\n\n h1: { xs: \"40px\", lg: \"60px\" },\n h2: { xs: \"30px\", lg: \"36px\" },\n h3: { xs: \"28px\", lg: \"30px\" },\n h4: { xs: \"24px\", lg: \"26px\" },\n h5: { xs: \"18px\", lg: \"20px\" },\n h6: { xs: \"16px\", lg: \"18px\" },\n\n text: { xs: \"14px\", lg: \"16px\" },\n strong: { xs: \"14px\", lg: \"16px\" },\n small: { xs: \"12px\", lg: \"14px\" },\n\n blockquote: { xs: \"16px\", lg: \"18px\" },\n code: { xs: \"14px\", lg: \"16px\" },\n\n button: { xs: \"16px\", lg: \"16px\" },\n buttonBig: { xs: \"18px\", lg: \"18px\" },\n buttonSmall: { xs: \"14px\", lg: \"14px\" },\n\n input: { xs: \"16px\", lg: \"16px\" },\n inputBig: { xs: \"18px\", lg: \"18px\" },\n inputSmall: { xs: \"14px\", lg: \"14px\" },\n};\n\nconst lineHeights: LineHeights = {\n hero1: { xs: \"1.1\", lg: \"1.1\" },\n hero2: { xs: \"1.1\", lg: \"1.1\" },\n hero3: { xs: \"1.17\", lg: \"1.1\" },\n\n h1: { xs: \"1\", lg: \"1.07\" },\n h2: { xs: \"1.2\", lg: \"1.2\" },\n h3: { xs: \"1.3\", lg: \"1.5\" },\n h4: { xs: \"1.3\", lg: \"1.5\" },\n h5: { xs: \"1.6\", lg: \"1.5\" },\n h6: { xs: \"1.6\", lg: \"1.6\" },\n\n text: { xs: \"1.7\", lg: \"1.7\" },\n strong: { xs: \"1.7\", lg: \"1.7\" },\n small: { xs: \"1.7\", lg: \"1.7\" },\n\n blockquote: { xs: \"1.7\", lg: \"1.7\" },\n code: { xs: \"1.7\", lg: \"1.7\" },\n\n button: { xs: \"1\", lg: \"1\" },\n buttonBig: { xs: \"1\", lg: \"1\" },\n buttonSmall: { xs: \"1\", lg: \"1\" },\n\n input: { xs: \"1\", lg: \"1\" },\n inputBig: { xs: \"1\", lg: \"1\" },\n inputSmall: { xs: \"1\", lg: \"1\" },\n};\n\n// Single theme object exported to consumers. Every color/shadow value is a\n// CSS custom-property reference; the browser resolves it against :root or\n// :root.dark depending on the active class. Components access these exactly\n// like before \u2014 `theme.colors.primary`, `theme.shadows.sm` \u2014 but the values\n// flip without React re-rendering.\nconst colors: Colors = {\n // Brand palette \u2014 directly customizable via theme.json's \"default\" / \"dark\".\n primaryLight: \"var(--color-primaryLight)\",\n primary: \"var(--color-primary)\",\n primaryDark: \"var(--color-primaryDark)\",\n secondaryLight: \"var(--color-secondaryLight)\",\n secondary: \"var(--color-secondary)\",\n secondaryDark: \"var(--color-secondaryDark)\",\n tertiaryLight: \"var(--color-tertiaryLight)\",\n tertiary: \"var(--color-tertiary)\",\n tertiaryDark: \"var(--color-tertiaryDark)\",\n grayLight: \"var(--color-grayLight)\",\n gray: \"var(--color-gray)\",\n grayDark: \"var(--color-grayDark)\",\n success: \"var(--color-success)\",\n error: \"var(--color-error)\",\n warning: \"var(--color-warning)\",\n info: \"var(--color-info)\",\n dark: \"var(--color-dark)\",\n light: \"var(--color-light)\",\n\n // Semantic tokens \u2014 derived in GlobalStyles to capture the most common\n // mode-aware swaps that components used to express via `theme.isDark ? A : B`.\n // They follow the same single-noun convention as the brand palette.\n accent: \"var(--color-accent)\",\n accentStrong: \"var(--color-accentStrong)\",\n accentMuted: \"var(--color-accentMuted)\",\n surface: \"var(--color-surface)\",\n};\n\nconst shadows: Shadows = {\n xs: \"var(--shadow-xs)\",\n sm: \"var(--shadow-sm)\",\n md: \"var(--shadow-md)\",\n lg: \"var(--shadow-lg)\",\n xl: \"var(--shadow-xl)\",\n};\n\nexport const theme: Theme = {\n breakpoints,\n spacing,\n colors,\n shadows,\n fonts,\n fontSizes,\n lineHeights,\n // Stub for type compatibility with cherry-styled-components' Theme. Mode\n // switching lives entirely in CSS vars flipped by :root.dark, so our own\n // code never branches on this. Cherry's internal `isDark ? \u2026 : \u2026` branches\n // (e.g. buttonStyles' filled-button text) are handled where they surface\n // by re-pinning to a mode-agnostic semantic token \u2014 see components/layout/Button.tsx.\n isDark: false,\n};\n\ninterface Breakpoints<TNumber = number> {\n xs: TNumber;\n sm: TNumber;\n md: TNumber;\n lg: TNumber;\n xl: TNumber;\n xxl: TNumber;\n xxxl: TNumber;\n}\n\ninterface Spacing<TString = string> {\n maxWidth: { xs: TString; xxxl: TString };\n padding: { xs: TString; lg: TString };\n radius: { xs: TString; lg: TString; xl: TString };\n gridGap: { xs: TString; lg: TString };\n}\n\n// Matches cherry-styled-components' Colors shape for the brand keys, plus a\n// handful of semantic tokens derived from the brand palette by GlobalStyles.\n// The brand keys (primaryLight\u2026light) are customizable via theme.json; the\n// semantic keys (accent*, surface) are derived and not overridable \u2014 see\n// CustomColors above.\ninterface Colors<TString = string> {\n primaryLight: TString;\n primary: TString;\n primaryDark: TString;\n\n secondaryLight: TString;\n secondary: TString;\n secondaryDark: TString;\n\n tertiaryLight: TString;\n tertiary: TString;\n tertiaryDark: TString;\n\n grayLight: TString;\n gray: TString;\n grayDark: TString;\n\n success: TString;\n error: TString;\n warning: TString;\n info: TString;\n\n dark: TString;\n light: TString;\n\n /** High-contrast accent text. Was: isDark ? primaryLight : primaryDark. */\n accent: TString;\n /** Slightly stronger accent (lightened/darkened by ~10%). */\n accentStrong: TString;\n /** Muted body text. Was: isDark ? grayDark : primary. */\n accentMuted: TString;\n /** Elevated surface color, white-ish in both modes. Was: isDark ? dark : light. */\n surface: TString;\n}\n\ninterface Shadows<TString = string> {\n xs: TString;\n sm: TString;\n md: TString;\n lg: TString;\n xl: TString;\n}\n\ninterface Fonts<TString = string> {\n head: TString;\n text: TString;\n mono: TString;\n}\n\ninterface FontSizes<TString = string> {\n hero1: { xs: TString; lg: TString };\n hero2: { xs: TString; lg: TString };\n hero3: { xs: TString; lg: TString };\n\n h1: { xs: TString; lg: TString };\n h2: { xs: TString; lg: TString };\n h3: { xs: TString; lg: TString };\n h4: { xs: TString; lg: TString };\n h5: { xs: TString; lg: TString };\n h6: { xs: TString; lg: TString };\n\n text: { xs: TString; lg: TString };\n strong: { xs: TString; lg: TString };\n small: { xs: TString; lg: TString };\n\n blockquote: { xs: TString; lg: TString };\n code: { xs: TString; lg: TString };\n\n button: { xs: TString; lg: TString };\n buttonBig: { xs: TString; lg: TString };\n buttonSmall: { xs: TString; lg: TString };\n\n input: { xs: TString; lg: TString };\n inputBig: { xs: TString; lg: TString };\n inputSmall: { xs: TString; lg: TString };\n}\n\ninterface LineHeights<TString = string> {\n hero1: { xs: TString; lg: TString };\n hero2: { xs: TString; lg: TString };\n hero3: { xs: TString; lg: TString };\n\n h1: { xs: TString; lg: TString };\n h2: { xs: TString; lg: TString };\n h3: { xs: TString; lg: TString };\n h4: { xs: TString; lg: TString };\n h5: { xs: TString; lg: TString };\n h6: { xs: TString; lg: TString };\n\n text: { xs: TString; lg: TString };\n strong: { xs: TString; lg: TString };\n small: { xs: TString; lg: TString };\n\n blockquote: { xs: TString; lg: TString };\n code: { xs: TString; lg: TString };\n\n button: { xs: TString; lg: TString };\n buttonBig: { xs: TString; lg: TString };\n buttonSmall: { xs: TString; lg: TString };\n\n input: { xs: TString; lg: TString };\n inputBig: { xs: TString; lg: TString };\n inputSmall: { xs: TString; lg: TString };\n}\n\nexport interface Theme {\n breakpoints: Breakpoints;\n spacing: Spacing;\n colors: Colors;\n shadows: Shadows;\n fonts: Fonts;\n fontSizes: FontSizes;\n lineHeights: LineHeights;\n isDark: boolean;\n}\n";
|