doccupine 0.0.87 → 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.
Files changed (78) hide show
  1. package/README.md +15 -2
  2. package/dist/index.js +177 -4
  3. package/dist/lib/layout.js +38 -24
  4. package/dist/lib/metadata.d.ts +30 -0
  5. package/dist/lib/metadata.js +98 -1
  6. package/dist/lib/structures.js +0 -2
  7. package/dist/lib/types.d.ts +1 -0
  8. package/dist/templates/app/robots.d.ts +1 -1
  9. package/dist/templates/app/robots.js +28 -1
  10. package/dist/templates/app/sitemap.d.ts +7 -0
  11. package/dist/templates/app/sitemap.js +56 -0
  12. package/dist/templates/app/theme.d.ts +1 -1
  13. package/dist/templates/app/theme.js +84 -19
  14. package/dist/templates/components/Chat.d.ts +1 -1
  15. package/dist/templates/components/Chat.js +26 -27
  16. package/dist/templates/components/SearchModalContent.d.ts +1 -1
  17. package/dist/templates/components/SearchModalContent.js +12 -6
  18. package/dist/templates/components/SideBar.d.ts +1 -1
  19. package/dist/templates/components/SideBar.js +3 -1
  20. package/dist/templates/components/layout/Accordion.d.ts +1 -1
  21. package/dist/templates/components/layout/Accordion.js +2 -1
  22. package/dist/templates/components/layout/ActionBar.d.ts +1 -1
  23. package/dist/templates/components/layout/ActionBar.js +4 -6
  24. package/dist/templates/components/layout/Button.d.ts +1 -1
  25. package/dist/templates/components/layout/Button.js +10 -0
  26. package/dist/templates/components/layout/Callout.d.ts +1 -1
  27. package/dist/templates/components/layout/Callout.js +75 -20
  28. package/dist/templates/components/layout/Card.d.ts +1 -1
  29. package/dist/templates/components/layout/Card.js +2 -1
  30. package/dist/templates/components/layout/CherryThemeProvider.d.ts +1 -1
  31. package/dist/templates/components/layout/CherryThemeProvider.js +6 -12
  32. package/dist/templates/components/layout/ClientThemeProvider.d.ts +1 -1
  33. package/dist/templates/components/layout/ClientThemeProvider.js +45 -40
  34. package/dist/templates/components/layout/Code.d.ts +1 -1
  35. package/dist/templates/components/layout/Code.js +223 -255
  36. package/dist/templates/components/layout/ColorSwatch.d.ts +1 -1
  37. package/dist/templates/components/layout/ColorSwatch.js +2 -2
  38. package/dist/templates/components/layout/Columns.d.ts +1 -1
  39. package/dist/templates/components/layout/Columns.js +1 -1
  40. package/dist/templates/components/layout/DemoTheme.d.ts +1 -1
  41. package/dist/templates/components/layout/DemoTheme.js +65 -167
  42. package/dist/templates/components/layout/DocsComponents.d.ts +1 -1
  43. package/dist/templates/components/layout/DocsComponents.js +13 -19
  44. package/dist/templates/components/layout/Field.d.ts +1 -1
  45. package/dist/templates/components/layout/Field.js +6 -4
  46. package/dist/templates/components/layout/Footer.d.ts +1 -1
  47. package/dist/templates/components/layout/Footer.js +1 -2
  48. package/dist/templates/components/layout/GlobalStyles.d.ts +1 -1
  49. package/dist/templates/components/layout/GlobalStyles.js +63 -10
  50. package/dist/templates/components/layout/Header.d.ts +1 -1
  51. package/dist/templates/components/layout/Header.js +14 -11
  52. package/dist/templates/components/layout/SharedStyles.d.ts +1 -1
  53. package/dist/templates/components/layout/SharedStyles.js +4 -5
  54. package/dist/templates/components/layout/StaticLinks.d.ts +1 -1
  55. package/dist/templates/components/layout/StaticLinks.js +4 -6
  56. package/dist/templates/components/layout/Steps.d.ts +1 -1
  57. package/dist/templates/components/layout/Steps.js +3 -3
  58. package/dist/templates/components/layout/Tabs.d.ts +1 -1
  59. package/dist/templates/components/layout/Tabs.js +5 -2
  60. package/dist/templates/components/layout/ThemeToggle.d.ts +1 -1
  61. package/dist/templates/components/layout/ThemeToggle.js +17 -19
  62. package/dist/templates/components/layout/Typography.d.ts +1 -1
  63. package/dist/templates/components/layout/Typography.js +1 -1
  64. package/dist/templates/components/layout/Update.d.ts +1 -1
  65. package/dist/templates/components/layout/Update.js +4 -3
  66. package/dist/templates/env.example.d.ts +1 -1
  67. package/dist/templates/env.example.js +5 -1
  68. package/dist/templates/mdx/deployment-and-hosting.mdx.d.ts +1 -1
  69. package/dist/templates/mdx/deployment-and-hosting.mdx.js +19 -0
  70. package/dist/templates/mdx/globals.mdx.d.ts +1 -1
  71. package/dist/templates/mdx/globals.mdx.js +3 -1
  72. package/dist/templates/mdx/platform/site-settings.mdx.d.ts +1 -1
  73. package/dist/templates/mdx/platform/site-settings.mdx.js +5 -1
  74. package/dist/templates/package.js +17 -18
  75. package/dist/templates/proxy.js +14 -20
  76. package/dist/templates/utils/config.d.ts +1 -1
  77. package/dist/templates/utils/config.js +1 -0
  78. package/package.json +8 -8
@@ -1,13 +1,63 @@
1
1
  export const globalStylesTemplate = `"use client";
2
2
  import { createGlobalStyle } from "styled-components";
3
+ import {
4
+ colorsLight,
5
+ colorsDark,
6
+ shadowsLight,
7
+ shadowsDark,
8
+ } from "@/app/theme";
9
+
10
+ // Build "name: value;" lines for every entry in a record. Used to emit
11
+ // :root and :root.dark blocks from the resolved hex objects in theme.ts.
12
+ // Custom theme.json overrides flow through automatically because they are
13
+ // already merged into colorsLight / colorsDark.
14
+ function toCssVars<T extends object>(prefix: string, record: T): string {
15
+ return Object.entries(record)
16
+ .map(([k, v]) => \` --\${prefix}-\${k}: \${v};\`)
17
+ .join("\\n");
18
+ }
19
+
20
+ const lightVars = [
21
+ toCssVars("color", colorsLight),
22
+ toCssVars("shadow", shadowsLight),
23
+ // Semantic tokens — derived from the brand palette per mode. See the
24
+ // SemanticColors interface in theme.ts for the intent of each.
25
+ // These reference the palette via var() (not baked hex) so that any
26
+ // runtime override of --color-primary* (e.g. the DemoTheme presets)
27
+ // cascades into the semantic tokens automatically.
28
+ \` --color-accent: var(--color-primaryDark);\`,
29
+ // accentStrong = accent shifted ~10% toward black (light mode) or white (dark
30
+ // mode). color-mix in sRGB is not identical to polished's HSL darken/lighten
31
+ // but the visual difference at 10% on a UI accent is imperceptible.
32
+ \` --color-accentStrong: color-mix(in srgb, var(--color-primaryDark) 90%, black);\`,
33
+ \` --color-accentMuted: var(--color-primary);\`,
34
+ \` --color-surface: var(--color-light);\`,
35
+ ].join("\\n");
36
+
37
+ const darkVars = [
38
+ toCssVars("color", colorsDark),
39
+ toCssVars("shadow", shadowsDark),
40
+ \` --color-accent: var(--color-primaryLight);\`,
41
+ \` --color-accentStrong: color-mix(in srgb, var(--color-primaryLight) 90%, white);\`,
42
+ \` --color-accentMuted: var(--color-grayDark);\`,
43
+ \` --color-surface: var(--color-dark);\`,
44
+ ].join("\\n");
3
45
 
4
46
  const GlobalStyles = createGlobalStyle\`
47
+ :root {
48
+ \${lightVars}
49
+ }
50
+
51
+ :root.dark {
52
+ \${darkVars}
53
+ }
54
+
5
55
  html,
6
56
  body {
7
57
  margin: 0;
8
58
  padding: 0;
9
59
  min-height: 100%;
10
- background-color: \${({ theme }) => theme.colors.light};
60
+ background-color: var(--color-light);
11
61
  scroll-padding-top: 80px;
12
62
  }
13
63
 
@@ -33,7 +83,7 @@ body {
33
83
 
34
84
  hr {
35
85
  border: none;
36
- border-bottom: solid 1px \${({ theme }) => theme.colors.grayLight};
86
+ border-bottom: solid 1px var(--color-grayLight);
37
87
  margin: 10px 0;
38
88
  }
39
89
 
@@ -61,11 +111,11 @@ ul li,
61
111
  ol li {
62
112
  margin: 0;
63
113
  padding: 0;
64
- color: \${({ theme }) => theme.colors.dark};
114
+ color: var(--color-dark);
65
115
  }
66
116
 
67
117
  a {
68
- color: \${({ theme }) => (theme.isDark ? theme.colors.dark : theme.colors.primary)};
118
+ color: var(--color-primary);
69
119
  }
70
120
 
71
121
  ol,
@@ -102,13 +152,16 @@ b {
102
152
  width: 100%;
103
153
  }
104
154
 
105
- .light-only {
106
- \${({ theme }) => theme.isDark && "display: none !important;"}
155
+ /* Mode-conditional visibility helpers. ThemeToggle uses these to swap Sun
156
+ and Moon icons; Header uses them to swap light/dark logos. Pure CSS so
157
+ the swap happens via the active <html> class without JS or re-render. */
158
+ :root.dark .light-only {
159
+ display: none !important;
107
160
  }
108
-
109
- .dark-only {
110
- \${({ theme }) => !theme.isDark && "display: none !important;"}
111
- }\`;
161
+ :root:not(.dark) .dark-only {
162
+ display: none !important;
163
+ }
164
+ \`;
112
165
 
113
166
  export { GlobalStyles };
114
167
  `;
@@ -1 +1 @@
1
- export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport Link from \"next/link\";\nimport { rgba } from \"polished\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useOnClickOutside } from \"@/components/ClickOutside\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const theme = useTheme() as Theme;\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n theme.isDark ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n )\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>&#8984;K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
1
+ export declare const headerTemplate = "\"use client\";\nimport React from \"react\";\nimport { useCallback, useContext, useRef, useState } from \"react\";\nimport styled, { css } from \"styled-components\";\nimport Link from \"next/link\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { useOnClickOutside } from \"@/components/ClickOutside\";\nimport { Search } from \"lucide-react\";\nimport { Logo } from \"@/components/layout/Pictograms\";\nimport { ChatContext, ChatButtonCTA } from \"@/components/Chat\";\nimport {\n SearchContext,\n SearchKbd,\n StyledSearchButton,\n} from \"@/components/SearchDocs\";\nimport themeJson from \"@/theme.json\";\n\nconst customThemeJson = themeJson as typeof themeJson & {\n logo?: { dark: string; light: string };\n};\n\nconst StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>`\n position: sticky;\n top: 0;\n margin: 0;\n z-index: 1000;\n width: 100%;\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n ${mq(\"lg\")} {\n padding-bottom: 16px;\n padding-top: 16px;\n }\n `}\n\n &::before,\n &::after {\n display: block;\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n background: ${({ theme }) => theme.colors.light};\n z-index: -2;\n }\n\n &::after {\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 5%, transparent)`};\n z-index: -1;\n }\n\n & .logo {\n display: flex;\n\n & svg,\n & img {\n margin: auto;\n height: auto;\n width: fit-content;\n min-width: fit-content;\n max-width: 182px;\n max-height: 30px;\n\n & path[fill] {\n fill: ${({ theme }) => theme.colors.primary};\n }\n }\n }\n`;\n\nconst StyledHeaderInner = styled.div<{ $hasChildren: boolean }>`\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-wrap: wrap;\n padding: 16px 0 0 20px;\n\n ${({ $hasChildren }) =>\n !$hasChildren &&\n css`\n padding-bottom: 16px;\n `}\n\n ${mq(\"lg\")} {\n flex-wrap: nowrap;\n padding: 0 20px;\n }\n`;\n\nconst StyledLeftWrapper = styled.div`\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: fit-content;\n padding-right: 20px;\n\n ${mq(\"lg\")} {\n padding-right: 0;\n }\n`;\n\ninterface HeaderProps {\n children?: React.ReactNode;\n}\n\nfunction Header({ children }: HeaderProps) {\n const [isOptionActive, setIsOptionActive] = useState(false);\n const [isLangActive, setIsLangActive] = useState(false);\n\n const wrapperRef = useRef<HTMLSpanElement>(null);\n const elmRef = useRef<HTMLDivElement>(null);\n const langRef = useRef<HTMLSpanElement>(null);\n const closeMenu = useCallback(() => {\n setIsOptionActive(false);\n setIsLangActive(false);\n }, []);\n\n useOnClickOutside(\n [elmRef, wrapperRef],\n isOptionActive ? closeMenu : () => {},\n );\n useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});\n const { isChatActive } = useContext(ChatContext);\n const { openSearch } = useContext(SearchContext);\n\n return (\n <StyledHeader $hasChildren={children ? true : false} id=\"header\">\n <StyledHeaderInner $hasChildren={children ? true : false}>\n <Link href=\"/\" className=\"logo\" aria-label=\"Logo\">\n {customThemeJson.logo ? (\n <>\n {/* Both logos render; .light-only and .dark-only classes in\n GlobalStyles hide the inactive one based on the \"dark\" class\n on <html>. Avoids a JS-driven swap so no flash on first load. */}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"light-only\"\n src={customThemeJson.logo.light}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n className=\"dark-only\"\n src={customThemeJson.logo.dark}\n alt=\"Logo\"\n width=\"100\"\n height=\"100\"\n />\n </>\n ) : (\n <Logo />\n )}\n </Link>\n {children}\n <StyledLeftWrapper>\n <StyledSearchButton onClick={openSearch} aria-label=\"Search docs\">\n <Search size={14} />\n <SearchKbd>&#8984;K</SearchKbd>\n </StyledSearchButton>\n {isChatActive && <ChatButtonCTA />}\n </StyledLeftWrapper>\n </StyledHeaderInner>\n </StyledHeader>\n );\n}\n\nexport { Header };\n";
@@ -1,9 +1,8 @@
1
1
  export const headerTemplate = `"use client";
2
2
  import React from "react";
3
3
  import { useCallback, useContext, useRef, useState } from "react";
4
- import styled, { css, useTheme } from "styled-components";
4
+ import styled, { css } from "styled-components";
5
5
  import Link from "next/link";
6
- import { rgba } from "polished";
7
6
  import { mq, Theme } from "@/app/theme";
8
7
  import { useOnClickOutside } from "@/components/ClickOutside";
9
8
  import { Search } from "lucide-react";
@@ -52,7 +51,8 @@ const StyledHeader = styled.header<{ theme: Theme; $hasChildren: boolean }>\`
52
51
  }
53
52
 
54
53
  &::after {
55
- background: \${({ theme }) => rgba(theme.colors.primaryLight, 0.05)};
54
+ background: \${({ theme }) =>
55
+ \`color-mix(in srgb, \${theme.colors.primaryLight} 5%, transparent)\`};
56
56
  z-index: -1;
57
57
  }
58
58
 
@@ -127,7 +127,6 @@ function Header({ children }: HeaderProps) {
127
127
  isOptionActive ? closeMenu : () => {},
128
128
  );
129
129
  useOnClickOutside([langRef, wrapperRef], isLangActive ? closeMenu : () => {});
130
- const theme = useTheme() as Theme;
131
130
  const { isChatActive } = useContext(ChatContext);
132
131
  const { openSearch } = useContext(SearchContext);
133
132
 
@@ -136,23 +135,27 @@ function Header({ children }: HeaderProps) {
136
135
  <StyledHeaderInner $hasChildren={children ? true : false}>
137
136
  <Link href="/" className="logo" aria-label="Logo">
138
137
  {customThemeJson.logo ? (
139
- theme.isDark ? (
140
- // eslint-disable-next-line @next/next/no-img-element
138
+ <>
139
+ {/* Both logos render; .light-only and .dark-only classes in
140
+ GlobalStyles hide the inactive one based on the "dark" class
141
+ on <html>. Avoids a JS-driven swap so no flash on first load. */}
142
+ {/* eslint-disable-next-line @next/next/no-img-element */}
141
143
  <img
142
- src={customThemeJson.logo.dark}
144
+ className="light-only"
145
+ src={customThemeJson.logo.light}
143
146
  alt="Logo"
144
147
  width="100"
145
148
  height="100"
146
149
  />
147
- ) : (
148
- // eslint-disable-next-line @next/next/no-img-element
150
+ {/* eslint-disable-next-line @next/next/no-img-element */}
149
151
  <img
150
- src={customThemeJson.logo.light}
152
+ className="dark-only"
153
+ src={customThemeJson.logo.dark}
151
154
  alt="Logo"
152
155
  width="100"
153
156
  height="100"
154
157
  />
155
- )
158
+ </>
156
159
  ) : (
157
160
  <Logo />
158
161
  )}
@@ -1 +1 @@
1
- export declare const sharedStyledTemplate = "\"use client\";\nimport { mq, styledSmall, styledText, Theme } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
1
+ export declare const sharedStyledTemplate = "\"use client\";\nimport { styledSmall, styledText } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\nimport styled, { css } from \"styled-components\";\n\nexport const interactiveStyles = css<{ theme: Theme }>`\n transition: all 0.3s ease;\n border: solid 1px transparent;\n box-shadow: 0 0 0 0px ${({ theme }) => theme.colors.primary};\n\n &:hover {\n border-color: ${({ theme }) => theme.colors.primary};\n }\n\n &:focus {\n border-color: ${({ theme }) => theme.colors.primary};\n box-shadow: 0 0 0 4px ${({ theme }) => theme.colors.primaryLight};\n }\n\n &:active {\n box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primaryLight};\n }\n`;\n\nexport const styledAnchor = css<{ theme: Theme }>`\n & a:not([class]):not(:has(img)) {\n color: inherit;\n transition: all 0.3s ease;\n text-decoration: none;\n box-shadow: 0 2px 0 0 ${({ theme }) => theme.colors.primary};\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n box-shadow: 0 1px 0 0 ${({ theme }) => theme.colors.primary};\n }\n }\n`;\n\nexport const stylesLists = css<{ theme: Theme }>`\n & ul,\n & ol {\n & li {\n & > .code-wrapper {\n margin: 10px 0;\n }\n }\n }\n\n & ul {\n list-style: none;\n padding: 0;\n margin: 0;\n\n & li {\n text-indent: 0;\n display: block;\n position: relative;\n padding: 0 0 0 15px;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n min-height: 23px;\n\n ${mq(\"lg\")} {\n min-height: 27px;\n }\n\n &::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n position: absolute;\n top: 8px;\n left: 2px;\n\n ${mq(\"lg\")} {\n top: 10px;\n }\n }\n }\n }\n\n & ol {\n padding: 0;\n margin: 0;\n\n & ul {\n padding-left: 15px;\n }\n\n & > li {\n position: relative;\n padding: 0;\n counter-increment: item;\n margin: 0;\n ${({ theme }) => styledText(theme)};\n\n &::before {\n content: counter(item) \".\";\n display: inline-block;\n margin: 0 4px 0 0;\n font-weight: 700;\n color: ${({ theme }) => theme.colors.primary};\n min-width: max-content;\n }\n }\n }\n`;\n\nexport const styledTable = css<{ theme: Theme }>`\n & .table-wrapper {\n overflow-x: auto;\n width: 100%;\n }\n\n & table {\n margin: 0;\n padding: 0;\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n\n & tr {\n margin: 0;\n padding: 0;\n }\n\n & th {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n ${({ theme }) => styledSmall(theme)};\n font-weight: 600;\n color: ${({ theme }) => theme.colors.dark};\n }\n\n & td {\n border-bottom: solid 1px ${({ theme }) => theme.colors.grayLight};\n padding: 10px 10px 10px 0;\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledSmall(theme)};\n }\n }\n`;\n\nexport const StyledSmallButton = styled.button<{ theme: Theme }>`\n ${interactiveStyles};\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n color: ${({ theme }) => theme.colors.accent};\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n padding: 6px 8px;\n font-size: 12px;\n font-family: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-right: -6px;\n\n & svg.lucide {\n color: inherit;\n }\n`;\n";
@@ -1,5 +1,6 @@
1
1
  export const sharedStyledTemplate = `"use client";
2
- import { mq, styledSmall, styledText, Theme } from "cherry-styled-components";
2
+ import { styledSmall, styledText } from "cherry-styled-components";
3
+ import { mq, Theme } from "@/app/theme";
3
4
  import styled, { css } from "styled-components";
4
5
 
5
6
  export const interactiveStyles = css<{ theme: Theme }>\`
@@ -29,8 +30,7 @@ export const styledAnchor = css<{ theme: Theme }>\`
29
30
  box-shadow: 0 2px 0 0 \${({ theme }) => theme.colors.primary};
30
31
 
31
32
  &:hover {
32
- color: \${({ theme }) =>
33
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
33
+ color: \${({ theme }) => theme.colors.accent};
34
34
  box-shadow: 0 1px 0 0 \${({ theme }) => theme.colors.primary};
35
35
  }
36
36
  }
@@ -148,8 +148,7 @@ export const StyledSmallButton = styled.button<{ theme: Theme }>\`
148
148
  \${interactiveStyles};
149
149
  background: \${({ theme }) => theme.colors.light};
150
150
  border: solid 1px \${({ theme }) => theme.colors.grayLight};
151
- color: \${({ theme }) =>
152
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
151
+ color: \${({ theme }) => theme.colors.accent};
153
152
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
154
153
  padding: 6px 8px;
155
154
  font-size: 12px;
@@ -1 +1 @@
1
- export declare const staticLinksTemplate = "\"use client\";\nimport styled, { css } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { useContext } from \"react\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen: boolean }>`\n display: flex;\n margin: 0 auto;\n transition: all 0.3s ease;\n padding: 0 20px;\n margin-bottom: 20px;\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 20px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 20px 300px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n max-width: 640px;\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n width: 100%;\n margin: 0 auto;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
1
+ export declare const staticLinksTemplate = "\"use client\";\nimport styled, { css } from \"styled-components\";\nimport { useContext } from \"react\";\nimport { ChatContext } from \"@/components/Chat\";\nimport { mq, Theme } from \"@/app/theme\";\nimport { interactiveStyles } from \"@/components/layout/SharedStyled\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport linksData from \"@/links.json\";\n\ninterface LinkProps {\n title: string;\n url: string;\n icon?: string;\n}\n\nconst links = linksData as LinkProps[];\n\nconst StyledStaticLinks = styled.div<{ theme: Theme; $isChatOpen: boolean }>`\n display: flex;\n margin: 0 auto;\n transition: all 0.3s ease;\n padding: 0 20px;\n margin-bottom: 20px;\n\n ${mq(\"lg\")} {\n margin: 0;\n padding: 0 300px 20px 300px;\n\n ${({ $isChatOpen }) =>\n $isChatOpen &&\n css`\n padding: 0 440px 20px 300px;\n `}\n }\n`;\n\nconst StyledStaticLinksContent = styled.div`\n max-width: 640px;\n margin: auto 0;\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n width: 100%;\n margin: 0 auto;\n`;\n\nconst StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>`\n position: relative;\n text-decoration: none;\n font-size: ${({ theme }) => theme.fontSizes.small.lg};\n line-height: 1;\n color: ${({ theme }) => theme.colors.accent};\n padding: 0;\n display: flex;\n gap: 6px;\n transition: all 0.3s ease;\n font-weight: 600;\n white-space: nowrap;\n min-width: fit-content;\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 10%, transparent)`};\n padding: 6px 8px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n ${interactiveStyles};\n\n ${({ $hasIcon }) =>\n $hasIcon &&\n css`\n padding-left: 30px;\n `}\n\n & * {\n margin: auto 0;\n }\n\n & svg {\n position: absolute;\n top: 50%;\n left: 8px;\n transform: translateY(-50%);\n }\n\n &:hover {\n color: ${({ theme }) => theme.colors.accent};\n }\n`;\n\nfunction StaticLinks() {\n const { isOpen } = useContext(ChatContext);\n\n if (links.length === 0) {\n return null;\n }\n\n return (\n <>\n <StyledStaticLinks $isChatOpen={isOpen}>\n <StyledStaticLinksContent>\n {links.map((link, index) => (\n <StyledLink\n key={index}\n href={link.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n $hasIcon={link.icon ? true : false}\n >\n {link.icon && <Icon name={link.icon} size={16} />}\n <span>{link.title}</span>\n </StyledLink>\n ))}\n </StyledStaticLinksContent>\n </StyledStaticLinks>\n </>\n );\n}\n\nexport { StaticLinks };\n";
@@ -1,7 +1,6 @@
1
1
  import { SIDEBAR_WIDTH, CHAT_WIDTH } from "../../app/theme.js";
2
2
  export const staticLinksTemplate = `"use client";
3
3
  import styled, { css } from "styled-components";
4
- import { rgba } from "polished";
5
4
  import { useContext } from "react";
6
5
  import { ChatContext } from "@/components/Chat";
7
6
  import { mq, Theme } from "@/app/theme";
@@ -51,8 +50,7 @@ const StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>\`
51
50
  text-decoration: none;
52
51
  font-size: \${({ theme }) => theme.fontSizes.small.lg};
53
52
  line-height: 1;
54
- color: \${({ theme }) =>
55
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
53
+ color: \${({ theme }) => theme.colors.accent};
56
54
  padding: 0;
57
55
  display: flex;
58
56
  gap: 6px;
@@ -60,7 +58,8 @@ const StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>\`
60
58
  font-weight: 600;
61
59
  white-space: nowrap;
62
60
  min-width: fit-content;
63
- background: \${({ theme }) => rgba(theme.colors.primaryLight, 0.1)};
61
+ background: \${({ theme }) =>
62
+ \`color-mix(in srgb, \${theme.colors.primaryLight} 10%, transparent)\`};
64
63
  padding: 6px 8px;
65
64
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
66
65
  \${interactiveStyles};
@@ -83,8 +82,7 @@ const StyledLink = styled.a<{ theme: Theme; $hasIcon?: boolean }>\`
83
82
  }
84
83
 
85
84
  &:hover {
86
- color: \${({ theme }) =>
87
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
85
+ color: \${({ theme }) => theme.colors.accent};
88
86
  }
89
87
  \`;
90
88
 
@@ -1 +1 @@
1
- export declare const stepsTemplate = "\"use client\";\nimport React from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst StyledStepsContainer = styled.div<{ theme: Theme }>`\n position: relative;\n width: 100%;\n`;\n\nconst StyledStep = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 0 0 20px 52px;\n margin: 0;\n position: relative;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n\n &::after {\n content: \"\";\n position: absolute;\n left: 16px;\n top: 0;\n width: 1px;\n height: 100%;\n background: ${({ theme }) => theme.colors.primary};\n background: linear-gradient(\n 180deg,\n ${({ theme }) => theme.colors.primary},\n ${({ theme }) => rgba(theme.colors.primary, 0)}\n );\n border-radius: 4px;\n }\n`;\n\nconst StepNumber = styled.div<{ theme: Theme }>`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.light};\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n margin-bottom: 12px;\n ${({ theme }) => styledText(theme)};\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1;\n`;\n\nconst StyledStepTitle = styled.h3<{ theme: Theme }>`\n margin: 0 0 10px 0;\n padding: 2px 0 0 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n display: flex;\n align-items: center;\n gap: 10px;\n`;\n\nconst StepContent = styled.div<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)};\n\n & > .code-wrapper {\n margin: 10px 0;\n }\n`;\n\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string;\n children: React.ReactNode;\n icon?: IconProps;\n}\n\nfunction Step(_props: StepProps) {\n return null;\n}\n\ninterface StepsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nfunction Steps({ children }: StepsProps) {\n const theme = useTheme() as Theme;\n\n const steps = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<StepProps> =>\n React.isValidElement(child),\n );\n\n return (\n <StyledStepsContainer theme={theme}>\n {steps.map((step, index) => {\n const { title, children: stepContent, icon } = step.props;\n\n return (\n <StyledStep key={index} theme={theme}>\n <StepNumber theme={theme}>{index + 1}</StepNumber>\n <StyledStepTitle theme={theme}>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n {title}\n </StyledStepTitle>\n <StepContent theme={theme}>{stepContent}</StepContent>\n </StyledStep>\n );\n })}\n </StyledStepsContainer>\n );\n}\n\nexport { Steps, Step };\n";
1
+ export declare const stepsTemplate = "\"use client\";\nimport React from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText } from \"cherry-styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst StyledStepsContainer = styled.div<{ theme: Theme }>`\n position: relative;\n width: 100%;\n`;\n\nconst StyledStep = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 0 0 20px 52px;\n margin: 0;\n position: relative;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n\n &::after {\n content: \"\";\n position: absolute;\n left: 16px;\n top: 0;\n width: 1px;\n height: 100%;\n background: ${({ theme }) => theme.colors.primary};\n background: linear-gradient(\n 180deg,\n ${({ theme }) => theme.colors.primary},\n transparent\n );\n border-radius: 4px;\n }\n`;\n\nconst StepNumber = styled.div<{ theme: Theme }>`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.light};\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n margin-bottom: 12px;\n ${({ theme }) => styledText(theme)};\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1;\n`;\n\nconst StyledStepTitle = styled.h3<{ theme: Theme }>`\n margin: 0 0 10px 0;\n padding: 2px 0 0 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n display: flex;\n align-items: center;\n gap: 10px;\n`;\n\nconst StepContent = styled.div<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)};\n\n & > .code-wrapper {\n margin: 10px 0;\n }\n`;\n\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string;\n children: React.ReactNode;\n icon?: IconProps;\n}\n\nfunction Step(_props: StepProps) {\n return null;\n}\n\ninterface StepsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nfunction Steps({ children }: StepsProps) {\n const theme = useTheme() as Theme;\n\n const steps = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<StepProps> =>\n React.isValidElement(child),\n );\n\n return (\n <StyledStepsContainer theme={theme}>\n {steps.map((step, index) => {\n const { title, children: stepContent, icon } = step.props;\n\n return (\n <StyledStep key={index} theme={theme}>\n <StepNumber theme={theme}>{index + 1}</StepNumber>\n <StyledStepTitle theme={theme}>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n {title}\n </StyledStepTitle>\n <StepContent theme={theme}>{stepContent}</StepContent>\n </StyledStep>\n );\n })}\n </StyledStepsContainer>\n );\n}\n\nexport { Steps, Step };\n";
@@ -1,8 +1,8 @@
1
1
  export const stepsTemplate = `"use client";
2
2
  import React from "react";
3
3
  import styled, { useTheme } from "styled-components";
4
- import { styledText, Theme } from "cherry-styled-components";
5
- import { rgba } from "polished";
4
+ import { styledText } from "cherry-styled-components";
5
+ import { Theme } from "@/app/theme";
6
6
  import { Icon, IconProps } from "@/components/layout/Icon";
7
7
 
8
8
  const StyledStepsContainer = styled.div<{ theme: Theme }>\`
@@ -30,7 +30,7 @@ const StyledStep = styled.div<{ theme: Theme }>\`
30
30
  background: linear-gradient(
31
31
  180deg,
32
32
  \${({ theme }) => theme.colors.primary},
33
- \${({ theme }) => rgba(theme.colors.primary, 0)}
33
+ transparent
34
34
  );
35
35
  border-radius: 4px;
36
36
  }
@@ -1 +1 @@
1
- export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport { rgba } from \"polished\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\ninterface TabContentProps {\n title: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: ${rgba(theme.colors.primaryLight, 0.1)};\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
1
+ export declare const tabsTemplate = "\"use client\";\nimport { Theme } from \"@/app/theme\";\nimport { styledText } from \"cherry-styled-components\";\nimport React, { useState, ReactNode } from \"react\";\nimport styled, { css } from \"styled-components\";\ninterface TabContentProps {\n title: string;\n children: ReactNode;\n}\ninterface TabsProps {\n children: React.ReactElement<TabContentProps>[];\n}\nconst TabsContainer = styled.div`\n width: 100%;\n margin: 0 auto;\n`;\nconst TabsList = styled.div<{ theme: Theme }>`\n display: flex;\n overflow: hidden;\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n background-color: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n overflow-x: auto;\n`;\nconst TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>`\n flex: 1;\n padding: 12px 20px;\n border: none;\n background: ${({ theme }) => theme.colors.light};\n cursor: pointer;\n transition: all 0.2s ease;\n border-bottom: 3px solid transparent;\n min-width: fit-content;\n ${({ theme }) => styledText(theme)};\n color: ${({ theme }) => theme.colors.dark};\n font-weight: 600;\n ${({ theme, $isActive }) =>\n $isActive &&\n css`\n color: ${theme.colors.primary};\n border-bottom: 3px solid ${theme.colors.primary};\n `}\n position: relative;\n &:hover {\n ${({ theme, $isActive }) =>\n !$isActive &&\n css`\n color: ${theme.colors.primary};\n background-color: color-mix(\n in srgb,\n ${theme.colors.primaryLight} 10%,\n transparent\n );\n `}\n }\n &:focus {\n outline: none;\n }\n &:not(:last-child) {\n border-right: 1px solid ${({ theme }) => theme.colors.grayLight};\n }\n`;\nconst TabPanel = styled.div<{ theme: Theme }>`\n background-color: ${({ theme }) => theme.colors.light};\n padding: 20px;\n border-radius: 0 0 ${({ theme }) => theme.spacing.radius.lg}\n ${({ theme }) => theme.spacing.radius.lg};\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)}\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n border-top: none;\n display: flex;\n flex-direction: column;\n gap: 20px;\n flex-wrap: wrap;\n flex: 1;\n`;\nconst TabContent: React.FC<TabContentProps> = ({ children }) => {\n return <>{children}</>;\n};\nconst Tabs: React.FC<TabsProps> = ({ children }) => {\n const [activeTab, setActiveTab] = useState(0);\n const tabs = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<TabContentProps> =>\n Boolean(\n React.isValidElement(child) &&\n child.props &&\n typeof child.props === \"object\" &&\n \"title\" in child.props &&\n typeof child.props.title === \"string\" &&\n child.props.title.trim() !== \"\",\n ),\n );\n return (\n <TabsContainer>\n <TabsList>\n {tabs.map((tab, index) => (\n <TabButton\n key={index}\n $isActive={activeTab === index}\n onClick={() => setActiveTab(index)}\n type=\"button\"\n >\n {tab.props.title}\n </TabButton>\n ))}\n </TabsList>\n <TabPanel>{tabs[activeTab]?.props.children}</TabPanel>\n </TabsContainer>\n );\n};\nexport { Tabs, TabContent };\n";
@@ -1,7 +1,6 @@
1
1
  export const tabsTemplate = `"use client";
2
2
  import { Theme } from "@/app/theme";
3
3
  import { styledText } from "cherry-styled-components";
4
- import { rgba } from "polished";
5
4
  import React, { useState, ReactNode } from "react";
6
5
  import styled, { css } from "styled-components";
7
6
  interface TabContentProps {
@@ -49,7 +48,11 @@ const TabButton = styled.button<{ theme: Theme; $isActive?: boolean }>\`
49
48
  !$isActive &&
50
49
  css\`
51
50
  color: \${theme.colors.primary};
52
- background-color: \${rgba(theme.colors.primaryLight, 0.1)};
51
+ background-color: color-mix(
52
+ in srgb,
53
+ \${theme.colors.primaryLight} 10%,
54
+ transparent
55
+ );
53
56
  \`}
54
57
  }
55
58
  &:focus {
@@ -1 +1 @@
1
- export declare const themeToggleTemplate = "\"use client\";\nimport { Theme, resetButton } from \"cherry-styled-components\";\nimport styled, { css, useTheme } from \"styled-components\";\nimport { rgba } from \"polished\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { theme as themeLight, themeDark } from \"@/app/theme\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 59px;\n height: 32px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n transition: all 0.3s ease;\n z-index: 1;\n ${({ theme }) =>\n theme.isDark &&\n css`\n transform: translateX(27px);\n `}\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & .lucide-moon-star {\n transform: translateX(-1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n\n & svg[stroke] {\n stroke: ${({ theme }) =>\n theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nfunction ToggleTheme({ $hidden }: { $hidden?: boolean }) {\n const { setTheme } = useThemeOverride();\n const theme = useTheme() as Theme;\n\n return (\n <StyledThemeToggle\n onClick={async () => {\n const nextTheme = theme.isDark ? \"light\" : \"dark\";\n setTheme(nextTheme === \"light\" ? themeLight : themeDark);\n await fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: nextTheme }),\n }).catch((err) => console.error(\"Failed to persist theme:\", err));\n }}\n $hidden={$hidden}\n aria-label=\"Toggle Theme\"\n >\n <Icon name=\"Sun\" className=\"light\" />\n <Icon name=\"MoonStar\" className=\"dark\" />\n </StyledThemeToggle>\n );\n}\n\nfunction ToggleThemeLoading() {\n return (\n <StyledThemeToggle $hidden aria-label=\"Toggle Theme\">\n <Icon name=\"MoonStar\" className=\"dark\" />\n <Icon name=\"Sun\" className=\"light\" />\n </StyledThemeToggle>\n );\n}\n\nexport { ToggleTheme, ToggleThemeLoading };\n";
1
+ export declare const themeToggleTemplate = "\"use client\";\nimport { resetButton } from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\nimport { Icon } from \"@/components/layout/Icon\";\nimport { useThemeOverride } from \"@/components/layout/ClientThemeProvider\";\n\nconst StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>`\n ${resetButton}\n width: 59px;\n height: 32px;\n border-radius: 30px;\n display: flex;\n position: relative;\n margin: auto 0;\n transform: scale(1);\n background: ${({ theme }) => theme.colors.light};\n border: solid 1px ${({ theme }) => theme.colors.grayLight};\n\n &::after {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n transition: all 0.3s ease;\n z-index: 1;\n }\n\n /* Knob position depends on the active mode, signaled by the \"dark\" class\n on <html>. Pure CSS \u2014 no JS conditional needed for the visual swap. */\n :root.dark &::after {\n transform: translateX(27px);\n }\n\n ${({ $hidden }) =>\n $hidden &&\n css`\n display: none;\n `}\n\n & svg {\n width: 16px;\n height: 16px;\n object-fit: contain;\n margin: auto;\n transition: all 0.3s ease;\n position: relative;\n z-index: 2;\n }\n\n & .lucide-sun {\n transform: translateX(1px);\n }\n\n & .lucide-moon-star {\n transform: translateX(-1px);\n }\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.primary};\n }\n\n &:hover {\n transform: scale(1.05);\n color: ${({ theme }) => theme.colors.accent};\n\n & svg[stroke] {\n stroke: ${({ theme }) => theme.colors.accent};\n }\n }\n\n &:active {\n transform: scale(0.97);\n }\n`;\n\nfunction ToggleTheme({ $hidden }: { $hidden?: boolean }) {\n const { mode, setMode } = useThemeOverride();\n\n return (\n <StyledThemeToggle\n onClick={async () => {\n const next = mode === \"dark\" ? \"light\" : \"dark\";\n setMode(next);\n await fetch(\"/api/theme\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ theme: next }),\n }).catch((err) => console.error(\"Failed to persist theme:\", err));\n }}\n $hidden={$hidden}\n aria-label=\"Toggle Theme\"\n >\n <Icon name=\"Sun\" className=\"light\" />\n <Icon name=\"MoonStar\" className=\"dark\" />\n </StyledThemeToggle>\n );\n}\n\nfunction ToggleThemeLoading() {\n return (\n <StyledThemeToggle $hidden aria-label=\"Toggle Theme\">\n <Icon name=\"MoonStar\" className=\"dark\" />\n <Icon name=\"Sun\" className=\"light\" />\n </StyledThemeToggle>\n );\n}\n\nexport { ToggleTheme, ToggleThemeLoading };\n";
@@ -1,9 +1,8 @@
1
1
  export const themeToggleTemplate = `"use client";
2
- import { Theme, resetButton } from "cherry-styled-components";
3
- import styled, { css, useTheme } from "styled-components";
4
- import { rgba } from "polished";
2
+ import { resetButton } from "cherry-styled-components";
3
+ import styled, { css } from "styled-components";
4
+ import { Theme } from "@/app/theme";
5
5
  import { Icon } from "@/components/layout/Icon";
6
- import { theme as themeLight, themeDark } from "@/app/theme";
7
6
  import { useThemeOverride } from "@/components/layout/ClientThemeProvider";
8
7
 
9
8
  const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
@@ -26,14 +25,16 @@ const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
26
25
  width: 24px;
27
26
  height: 24px;
28
27
  border-radius: 50%;
29
- background: \${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};
28
+ background: \${({ theme }) =>
29
+ \`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
30
30
  transition: all 0.3s ease;
31
31
  z-index: 1;
32
- \${({ theme }) =>
33
- theme.isDark &&
34
- css\`
35
- transform: translateX(27px);
36
- \`}
32
+ }
33
+
34
+ /* Knob position depends on the active mode, signaled by the "dark" class
35
+ on <html>. Pure CSS — no JS conditional needed for the visual swap. */
36
+ :root.dark &::after {
37
+ transform: translateX(27px);
37
38
  }
38
39
 
39
40
  \${({ $hidden }) =>
@@ -66,12 +67,10 @@ const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
66
67
 
67
68
  &:hover {
68
69
  transform: scale(1.05);
69
- color: \${({ theme }) =>
70
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
70
+ color: \${({ theme }) => theme.colors.accent};
71
71
 
72
72
  & svg[stroke] {
73
- stroke: \${({ theme }) =>
74
- theme.isDark ? theme.colors.primaryLight : theme.colors.primaryDark};
73
+ stroke: \${({ theme }) => theme.colors.accent};
75
74
  }
76
75
  }
77
76
 
@@ -81,18 +80,17 @@ const StyledThemeToggle = styled.button<{ theme: Theme; $hidden?: boolean }>\`
81
80
  \`;
82
81
 
83
82
  function ToggleTheme({ $hidden }: { $hidden?: boolean }) {
84
- const { setTheme } = useThemeOverride();
85
- const theme = useTheme() as Theme;
83
+ const { mode, setMode } = useThemeOverride();
86
84
 
87
85
  return (
88
86
  <StyledThemeToggle
89
87
  onClick={async () => {
90
- const nextTheme = theme.isDark ? "light" : "dark";
91
- setTheme(nextTheme === "light" ? themeLight : themeDark);
88
+ const next = mode === "dark" ? "light" : "dark";
89
+ setMode(next);
92
90
  await fetch("/api/theme", {
93
91
  method: "POST",
94
92
  headers: { "Content-Type": "application/json" },
95
- body: JSON.stringify({ theme: nextTheme }),
93
+ body: JSON.stringify({ theme: next }),
96
94
  }).catch((err) => console.error("Failed to persist theme:", err));
97
95
  }}
98
96
  $hidden={$hidden}
@@ -1 +1 @@
1
- export declare const typographyTemplate = "\"use client\";\nimport {\n styledH1,\n styledH2,\n styledH3,\n styledH4,\n styledH5,\n styledH6,\n styledText,\n Theme,\n} from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\n\nconst StyledHeading = css`\n font-weight: 900;\n`;\n\nexport const StyledH1 = styled.h1<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH1(theme)}\n`;\n\nexport const StyledH2 = styled.h2<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH2(theme)}\n`;\n\nexport const StyledH3 = styled.h3<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH3(theme)}\n`;\n\nexport const StyledH4 = styled.h4<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH4(theme)}\n`;\n\nexport const StyledH5 = styled.h5<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH5(theme)}\n`;\n\nexport const StyledH6 = styled.h6<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH6(theme)}\n`;\n\nexport const StyledText = styled.p<{ theme: Theme }>`\n ${({ theme }) => styledText(theme)}\n`;\n";
1
+ export declare const typographyTemplate = "\"use client\";\nimport {\n styledH1,\n styledH2,\n styledH3,\n styledH4,\n styledH5,\n styledH6,\n styledText,\n} from \"cherry-styled-components\";\nimport styled, { css } from \"styled-components\";\nimport { Theme } from \"@/app/theme\";\n\nconst StyledHeading = css`\n font-weight: 900;\n`;\n\nexport const StyledH1 = styled.h1<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH1(theme)}\n`;\n\nexport const StyledH2 = styled.h2<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH2(theme)}\n`;\n\nexport const StyledH3 = styled.h3<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH3(theme)}\n`;\n\nexport const StyledH4 = styled.h4<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH4(theme)}\n`;\n\nexport const StyledH5 = styled.h5<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH5(theme)}\n`;\n\nexport const StyledH6 = styled.h6<{ theme: Theme }>`\n ${StyledHeading};\n ${({ theme }) => styledH6(theme)}\n`;\n\nexport const StyledText = styled.p<{ theme: Theme }>`\n ${({ theme }) => styledText(theme)}\n`;\n";
@@ -7,9 +7,9 @@ import {
7
7
  styledH5,
8
8
  styledH6,
9
9
  styledText,
10
- Theme,
11
10
  } from "cherry-styled-components";
12
11
  import styled, { css } from "styled-components";
12
+ import { Theme } from "@/app/theme";
13
13
 
14
14
  const StyledHeading = css\`
15
15
  font-weight: 900;
@@ -1 +1 @@
1
- export declare const updateTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { mq, styledSmall, Theme } from \"cherry-styled-components\";\nimport { rgba } from \"polished\";\n\nconst StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>`\n position: relative;\n display: flex;\n gap: 20px;\n flex-direction: column;\n\n ${mq(\"lg\")} {\n margin: 20px 0;\n flex-direction: row;\n }\n`;\n\nconst StyledUpdateLabel = styled.span<{ theme: Theme }>`\n background: ${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};\n color: ${({ theme }) => theme.colors.primary};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n font-weight: 600;\n ${({ theme }) => styledSmall(theme)};\n`;\n\nconst StyledUpdateDescription = styled.div<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.gray};\n`;\n\nconst StyledUpdateSidebar = styled.div`\n display: flex;\n flex-direction: column;\n gap: 10px;\n\n ${mq(\"lg\")} {\n min-width: 160px;\n }\n`;\n\nconst StyledUpdateChildren = styled.div`\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n`;\n\ninterface UpdateProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n label: string;\n description: string;\n}\n\nfunction Update({ children, label, description }: UpdateProps) {\n return (\n <StyledUpdate>\n <StyledUpdateSidebar>\n <div>\n <StyledUpdateLabel>{label}</StyledUpdateLabel>\n </div>\n <StyledUpdateDescription>{description}</StyledUpdateDescription>\n </StyledUpdateSidebar>\n <StyledUpdateChildren>{children}</StyledUpdateChildren>\n </StyledUpdate>\n );\n}\n\nexport { Update };\n";
1
+ export declare const updateTemplate = "\"use client\";\nimport styled from \"styled-components\";\nimport { styledSmall } from \"cherry-styled-components\";\nimport { mq, Theme } from \"@/app/theme\";\n\nconst StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>`\n position: relative;\n display: flex;\n gap: 20px;\n flex-direction: column;\n\n ${mq(\"lg\")} {\n margin: 20px 0;\n flex-direction: row;\n }\n`;\n\nconst StyledUpdateLabel = styled.span<{ theme: Theme }>`\n background: ${({ theme }) =>\n `color-mix(in srgb, ${theme.colors.primaryLight} 20%, transparent)`};\n color: ${({ theme }) => theme.colors.primary};\n padding: 2px 4px;\n border-radius: ${({ theme }) => theme.spacing.radius.xs};\n font-weight: 600;\n ${({ theme }) => styledSmall(theme)};\n`;\n\nconst StyledUpdateDescription = styled.div<{ theme: Theme }>`\n ${({ theme }) => styledSmall(theme)};\n color: ${({ theme }) => theme.colors.gray};\n`;\n\nconst StyledUpdateSidebar = styled.div`\n display: flex;\n flex-direction: column;\n gap: 10px;\n\n ${mq(\"lg\")} {\n min-width: 160px;\n }\n`;\n\nconst StyledUpdateChildren = styled.div`\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 20px;\n`;\n\ninterface UpdateProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n label: string;\n description: string;\n}\n\nfunction Update({ children, label, description }: UpdateProps) {\n return (\n <StyledUpdate>\n <StyledUpdateSidebar>\n <div>\n <StyledUpdateLabel>{label}</StyledUpdateLabel>\n </div>\n <StyledUpdateDescription>{description}</StyledUpdateDescription>\n </StyledUpdateSidebar>\n <StyledUpdateChildren>{children}</StyledUpdateChildren>\n </StyledUpdate>\n );\n}\n\nexport { Update };\n";
@@ -1,7 +1,7 @@
1
1
  export const updateTemplate = `"use client";
2
2
  import styled from "styled-components";
3
- import { mq, styledSmall, Theme } from "cherry-styled-components";
4
- import { rgba } from "polished";
3
+ import { styledSmall } from "cherry-styled-components";
4
+ import { mq, Theme } from "@/app/theme";
5
5
 
6
6
  const StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>\`
7
7
  position: relative;
@@ -16,7 +16,8 @@ const StyledUpdate = styled.div<{ theme: Theme; $columns?: number }>\`
16
16
  \`;
17
17
 
18
18
  const StyledUpdateLabel = styled.span<{ theme: Theme }>\`
19
- background: \${({ theme }) => rgba(theme.colors.primaryLight, 0.2)};
19
+ background: \${({ theme }) =>
20
+ \`color-mix(in srgb, \${theme.colors.primaryLight} 20%, transparent)\`};
20
21
  color: \${({ theme }) => theme.colors.primary};
21
22
  padding: 2px 4px;
22
23
  border-radius: \${({ theme }) => theme.spacing.radius.xs};
@@ -1 +1 @@
1
- export declare const envExampleTemplate = "# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model\n# See available models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/models\n# Anthropic: https://docs.anthropic.com/claude/docs/models-overview\n# Google: https://ai.google.dev/models/gemini\n# LLM_CHAT_MODEL=\n\n# Optional: Override default embedding model\n# See available embedding models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/guides/embeddings\n# Google: https://ai.google.dev/gemini-api/docs/embeddings\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n";
1
+ export declare const envExampleTemplate = "# Public Site URL\n# Used by sitemap.xml and robots.txt. Overrides `url` in config.json when set.\n# NEXT_PUBLIC_SITE_URL=https://docs.example.com\n\n# LLM Provider Configuration\n# Choose your preferred LLM provider: openai, anthropic, or google\nLLM_PROVIDER=openai\n\n# API Keys (set the one matching your provider)\nOPENAI_API_KEY=your_openai_api_key_here\nANTHROPIC_API_KEY=your_anthropic_api_key_here\nGOOGLE_API_KEY=your_google_api_key_here\n\n# Optional: Override default chat model\n# See available models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/models\n# Anthropic: https://docs.anthropic.com/claude/docs/models-overview\n# Google: https://ai.google.dev/models/gemini\n# LLM_CHAT_MODEL=\n\n# Optional: Override default embedding model\n# See available embedding models at your provider's docs:\n# OpenAI: https://platform.openai.com/docs/guides/embeddings\n# Google: https://ai.google.dev/gemini-api/docs/embeddings\n# Note: Anthropic doesn't provide embeddings, will fallback to OpenAI\n# LLM_EMBEDDING_MODEL=\n\n# Optional: Set temperature (0-1, default: 0)\n# LLM_TEMPERATURE=0\n";
@@ -1,4 +1,8 @@
1
- export const envExampleTemplate = `# LLM Provider Configuration
1
+ export const envExampleTemplate = `# Public Site URL
2
+ # Used by sitemap.xml and robots.txt. Overrides \`url\` in config.json when set.
3
+ # NEXT_PUBLIC_SITE_URL=https://docs.example.com
4
+
5
+ # LLM Provider Configuration
2
6
  # Choose your preferred LLM provider: openai, anthropic, or google
3
7
  LLM_PROVIDER=openai
4
8
 
@@ -1 +1 @@
1
- export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n# Deployment & Hosting\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Troubleshooting\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
1
+ export declare const deploymentAndHostingMdxTemplate = "---\ntitle: \"Deployment & Hosting\"\ndescription: \"Deploy your documentation site with the Doccupine Platform or self-host on any platform that supports Next.js.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 11\n---\n# Deployment & Hosting\nThe fastest way to deploy your documentation is with the [Doccupine Platform](https://www.doccupine.com). If you prefer to manage your own infrastructure, you can self-host the generated Next.js app on any platform.\n\n## Doccupine Platform\n\nSign up at [doccupine.com](https://www.doccupine.com) and connect your repository. Your documentation site is live in minutes - no build configuration, no infrastructure to manage.\n\n<Callout type=\"success\">\n The Doccupine Platform is the recommended way to deploy. It handles builds, hosting, SSL, and updates automatically so you can focus on writing documentation.\n</Callout>\n\n### What you get\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n- **Zero maintenance** - no servers, no build pipelines, no dependency updates\n\n### Getting started\n1. Create an account at [doccupine.com](https://www.doccupine.com).\n2. Connect your GitHub repository.\n3. Your site is deployed automatically.\n\nEvery push to your repository triggers a new deployment. You can customize your site's appearance, domain, and settings from the dashboard. See the [Platform Overview](/platform) for a full walkthrough of the dashboard, editor, and configuration options.\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n<Callout type=\"note\">\n Self-hosting requires you to manage your own build pipeline, hosting, SSL certificates, and AI provider API keys. For a hands-off experience, consider the [Doccupine Platform](https://www.doccupine.com).\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Sitemap and robots.txt\nDoccupine generates `sitemap.xml` and `robots.txt` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.\n\nSet the URL in `config.json`:\n\n```json\n{\n \"url\": \"https://docs.example.com\"\n}\n```\n\nAt deploy time, you can override the value with the `NEXT_PUBLIC_SITE_URL` environment variable - useful for preview deployments or staging environments.\n\n```bash\nNEXT_PUBLIC_SITE_URL=https://staging.example.com\n```\n\nThe generated sitemap includes every page from every [section](/sections), with `lastModified` derived from each page's frontmatter `date` or the file's modification time. When no URL is configured, the sitemap is skipped and `robots.txt` is emitted without a sitemap reference.\n\n### Troubleshooting\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
@@ -55,6 +55,25 @@ Doccupine generates a standard Next.js app, so you can deploy it anywhere that s
55
55
  - **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.
56
56
  - **Node.js server** - run \`next build && next start\` on any server or VPS with Node.js installed.
57
57
 
58
+ ### Sitemap and robots.txt
59
+ Doccupine generates \`sitemap.xml\` and \`robots.txt\` automatically when you set a site URL. This is required for search engine indexing and is strongly recommended for any public deployment.
60
+
61
+ Set the URL in \`config.json\`:
62
+
63
+ \`\`\`json
64
+ {
65
+ "url": "https://docs.example.com"
66
+ }
67
+ \`\`\`
68
+
69
+ At deploy time, you can override the value with the \`NEXT_PUBLIC_SITE_URL\` environment variable - useful for preview deployments or staging environments.
70
+
71
+ \`\`\`bash
72
+ NEXT_PUBLIC_SITE_URL=https://staging.example.com
73
+ \`\`\`
74
+
75
+ The generated sitemap includes every page from every [section](/sections), with \`lastModified\` derived from each page's frontmatter \`date\` or the file's modification time. When no URL is configured, the sitemap is skipped and \`robots.txt\` is emitted without a sitemap reference.
76
+
58
77
  ### Troubleshooting
59
78
  - **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.
60
79
  - **Missing content** - verify your MDX files and assets are in the repository.
@@ -1 +1 @@
1
- export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 1\n---\n# Global Configuration\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\"\n}\n```\n\n## Fields\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n\n## Per-page overrides\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n|---|---|---|\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```\n\n";
1
+ export declare const globalsMdxTemplate = "---\ntitle: \"Globals\"\ndescription: \"Configure global settings for your documentation.\"\ndate: \"2026-02-19\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 1\n---\n# Global Configuration\nUse a `config.json` file to define project\u2011wide metadata for your documentation site. These values are applied to every generated page unless a page overrides them in its own frontmatter.\n\n## config.json\nPlace a `config.json` at your project root (the same folder where you execute `npx doccupine`) to define global metadata for your documentation site.\n\n```json\n{\n \"name\": \"Doccupine\",\n \"description\": \"Doccupine is a free and open-source documentation platform. Write MDX, get a production-ready site with AI chat, built-in components, and an MCP server - in one command.\",\n \"icon\": \"https://docs.doccupine.com/favicon.ico\",\n \"image\": \"https://docs.doccupine.com/preview.png\",\n \"url\": \"https://docs.example.com\"\n}\n```\n\n## Fields\nAll fields are optional. Doccupine uses sensible defaults when a field is not set.\n\n- **name**: The primary name of your documentation website. Displayed in the site title and used in various UI elements.\n- **description**: A concise summary of your project, used in site metadata (e.g., HTML meta description) and social previews when not overridden.\n- **icon**: The favicon for your site. You can provide a full URL or a relative path to an asset in your project.\n- **image**: The Open Graph image used when links to your docs are shared on social platforms. Accepts a full URL or a relative path.\n- **url**: The public URL of your deployed site. Used as the base URL for `sitemap.xml` and `robots.txt`. When omitted, no sitemap is generated. Can be overridden at deploy time with the `NEXT_PUBLIC_SITE_URL` environment variable.\n\n## Per-page overrides\nAny page can override global values by defining the matching key in its frontmatter. When present, the page's value takes precedence over `config.json` for that page only.\n\n| Frontmatter field | Overrides | Effect |\n|---|---|---|\n| **title** | - | Page title in metadata and Open Graph |\n| **description** | `description` | Meta description and Open Graph description |\n| **name** | `name` | Site name shown in the title suffix (e.g. \"Page - My Docs\") |\n| **icon** | `icon` | Favicon for this page |\n| **image** | `image` | Open Graph preview image |\n| **section** | - | Assigns the page to a [section](/sections) |\n| **sectionOrder** | - | Controls section position in the tab bar |\n| **sectionLabel** | - | Renames the default \"Docs\" tab (use on `index.mdx`) |\n\n<Callout type=\"note\">\n If a key is not specified in a page's frontmatter, Doccupine falls back to the corresponding value in `config.json`.\n</Callout>\n\nExample frontmatter in an `.mdx` file:\n\n```text\n---\ntitle: \"My Feature\"\ndescription: \"A focused description just for this page.\"\nname: \"My Product Docs\"\nicon: \"/custom-favicon.ico\"\nimage: \"/custom-preview.png\"\ndate: \"2026-02-19\"\ncategory: \"Guides\"\n---\n```\n\n";