@dominikcz/greg 0.9.27

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 (183) hide show
  1. package/README.md +397 -0
  2. package/bin/greg.js +241 -0
  3. package/bin/init.js +351 -0
  4. package/bin/templates/docs/getting-started.md +47 -0
  5. package/bin/templates/docs/index.md +11 -0
  6. package/bin/templates/greg.config.js +39 -0
  7. package/bin/templates/greg.config.ts +38 -0
  8. package/bin/templates/index.html +16 -0
  9. package/bin/templates/src/App.svelte +5 -0
  10. package/bin/templates/src/app.css +20 -0
  11. package/bin/templates/src/main.js +9 -0
  12. package/bin/templates/svelte.config.js +1 -0
  13. package/bin/templates/tsconfig.json +21 -0
  14. package/bin/templates/vite.config.js +23 -0
  15. package/docs/__partials/markdown/examples/basic.md +4 -0
  16. package/docs/__partials/markdown/examples/diff.md +10 -0
  17. package/docs/__partials/markdown/examples/focus.md +5 -0
  18. package/docs/__partials/markdown/examples/language-title.md +3 -0
  19. package/docs/__partials/markdown/examples/line-highlighting.md +5 -0
  20. package/docs/__partials/markdown/examples/line-numbers.md +5 -0
  21. package/docs/__partials/note.md +4 -0
  22. package/docs/guide/__shared-warning.md +4 -0
  23. package/docs/guide/asset-handling.md +88 -0
  24. package/docs/guide/deploying.md +162 -0
  25. package/docs/guide/getting-started.md +334 -0
  26. package/docs/guide/index.md +23 -0
  27. package/docs/guide/localization.md +290 -0
  28. package/docs/guide/markdown/code.md +95 -0
  29. package/docs/guide/markdown/components-and-mermaid.md +43 -0
  30. package/docs/guide/markdown/containers.md +110 -0
  31. package/docs/guide/markdown/header-anchors.md +34 -0
  32. package/docs/guide/markdown/includes.md +84 -0
  33. package/docs/guide/markdown/index.md +20 -0
  34. package/docs/guide/markdown/inline-attributes.md +21 -0
  35. package/docs/guide/markdown/links-and-toc.md +64 -0
  36. package/docs/guide/markdown/math.md +54 -0
  37. package/docs/guide/markdown/syntax-highlighting.md +75 -0
  38. package/docs/guide/routing.md +150 -0
  39. package/docs/guide/using-svelte.md +88 -0
  40. package/docs/guide/versioning.md +281 -0
  41. package/docs/incompatibilities.md +48 -0
  42. package/docs/index.md +43 -0
  43. package/docs/reference/badge.md +100 -0
  44. package/docs/reference/carbon-ads.md +46 -0
  45. package/docs/reference/code-group.md +126 -0
  46. package/docs/reference/home-page.md +232 -0
  47. package/docs/reference/index.md +18 -0
  48. package/docs/reference/markdowndocs.md +275 -0
  49. package/docs/reference/outline.md +79 -0
  50. package/docs/reference/search.md +263 -0
  51. package/docs/reference/steps.md +200 -0
  52. package/docs/reference/team-page.md +189 -0
  53. package/docs/reference/theme.md +150 -0
  54. package/fakeDocsGenerator/generate_docs.js +310 -0
  55. package/package.json +92 -0
  56. package/scripts/build-versions.js +609 -0
  57. package/scripts/generate-static.js +79 -0
  58. package/scripts/render-markdown.js +420 -0
  59. package/src/lib/MarkdownDocs/AiChat.svelte +936 -0
  60. package/src/lib/MarkdownDocs/BackToTop.svelte +68 -0
  61. package/src/lib/MarkdownDocs/Breadcrumb.svelte +68 -0
  62. package/src/lib/MarkdownDocs/DocsNavigation.svelte +149 -0
  63. package/src/lib/MarkdownDocs/DocsSiteHeader.svelte +758 -0
  64. package/src/lib/MarkdownDocs/DocsVersionSwitcher.svelte +103 -0
  65. package/src/lib/MarkdownDocs/MarkdownDocs.svelte +2115 -0
  66. package/src/lib/MarkdownDocs/MarkdownRenderer.svelte +487 -0
  67. package/src/lib/MarkdownDocs/Outline.svelte +238 -0
  68. package/src/lib/MarkdownDocs/PrevNext.svelte +115 -0
  69. package/src/lib/MarkdownDocs/SearchModal.svelte +1241 -0
  70. package/src/lib/MarkdownDocs/TreeView.svelte +32 -0
  71. package/src/lib/MarkdownDocs/TreeViewItem.svelte +219 -0
  72. package/src/lib/MarkdownDocs/VersionOutdatedNotice.svelte +72 -0
  73. package/src/lib/MarkdownDocs/__tests__/codeDirectives.test.js +54 -0
  74. package/src/lib/MarkdownDocs/__tests__/common.test.js +41 -0
  75. package/src/lib/MarkdownDocs/__tests__/docsExamplesLint.test.js +77 -0
  76. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/__partial-basic.md +3 -0
  77. package/src/lib/MarkdownDocs/__tests__/fixtures/docs/markdown/snippet.js +9 -0
  78. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/part.md +11 -0
  79. package/src/lib/MarkdownDocs/__tests__/fixtures/includes/wrapper.md +5 -0
  80. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.js +8 -0
  81. package/src/lib/MarkdownDocs/__tests__/fixtures/snippets/sample.md +5 -0
  82. package/src/lib/MarkdownDocs/__tests__/helpers.js +67 -0
  83. package/src/lib/MarkdownDocs/__tests__/localeUtils.test.js +204 -0
  84. package/src/lib/MarkdownDocs/__tests__/markdown.test.js +704 -0
  85. package/src/lib/MarkdownDocs/__tests__/markdownRendererRuntime.test.js +65 -0
  86. package/src/lib/MarkdownDocs/__tests__/searchIndexBuilder.test.js +117 -0
  87. package/src/lib/MarkdownDocs/__tests__/sqliteStore.test.js +202 -0
  88. package/src/lib/MarkdownDocs/__tests__/useRouter.test.js +16 -0
  89. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.js +14 -0
  90. package/src/lib/MarkdownDocs/ai/adapters/customAdapter.ts +43 -0
  91. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.js +81 -0
  92. package/src/lib/MarkdownDocs/ai/adapters/ollamaAdapter.ts +116 -0
  93. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.js +92 -0
  94. package/src/lib/MarkdownDocs/ai/adapters/openaiAdapter.ts +137 -0
  95. package/src/lib/MarkdownDocs/ai/aiProvider.ts +31 -0
  96. package/src/lib/MarkdownDocs/ai/characters.js +52 -0
  97. package/src/lib/MarkdownDocs/ai/characters.ts +69 -0
  98. package/src/lib/MarkdownDocs/ai/chunkStore.ts +25 -0
  99. package/src/lib/MarkdownDocs/ai/chunker.js +85 -0
  100. package/src/lib/MarkdownDocs/ai/chunker.ts +135 -0
  101. package/src/lib/MarkdownDocs/ai/docLinker.js +26 -0
  102. package/src/lib/MarkdownDocs/ai/docLinker.ts +36 -0
  103. package/src/lib/MarkdownDocs/ai/promptBuilder.js +33 -0
  104. package/src/lib/MarkdownDocs/ai/promptBuilder.ts +53 -0
  105. package/src/lib/MarkdownDocs/ai/ragPipeline.js +54 -0
  106. package/src/lib/MarkdownDocs/ai/ragPipeline.ts +106 -0
  107. package/src/lib/MarkdownDocs/ai/stores/memoryStore.js +88 -0
  108. package/src/lib/MarkdownDocs/ai/stores/memoryStore.ts +112 -0
  109. package/src/lib/MarkdownDocs/ai/stores/sqliteStore.ts +372 -0
  110. package/src/lib/MarkdownDocs/ai/types.ts +71 -0
  111. package/src/lib/MarkdownDocs/aiServer.js +288 -0
  112. package/src/lib/MarkdownDocs/codeDirectives.js +191 -0
  113. package/src/lib/MarkdownDocs/codeFenceInfo.js +45 -0
  114. package/src/lib/MarkdownDocs/codeGroup.ts +46 -0
  115. package/src/lib/MarkdownDocs/common.ts +47 -0
  116. package/src/lib/MarkdownDocs/docsUtils.js +281 -0
  117. package/src/lib/MarkdownDocs/index.plugins.js +22 -0
  118. package/src/lib/MarkdownDocs/layouts/LayoutDoc.svelte +8 -0
  119. package/src/lib/MarkdownDocs/layouts/LayoutHome.svelte +58 -0
  120. package/src/lib/MarkdownDocs/layouts/LayoutPage.svelte +9 -0
  121. package/src/lib/MarkdownDocs/loadGregConfig.js +82 -0
  122. package/src/lib/MarkdownDocs/localeUtils.ts +682 -0
  123. package/src/lib/MarkdownDocs/markdownRendererRuntime.ts +314 -0
  124. package/src/lib/MarkdownDocs/mermaidThemes.js +319 -0
  125. package/src/lib/MarkdownDocs/navigationUtils.js +22 -0
  126. package/src/lib/MarkdownDocs/rehypeCodeGroup.js +326 -0
  127. package/src/lib/MarkdownDocs/rehypeCodeTitle.js +96 -0
  128. package/src/lib/MarkdownDocs/rehypeToc.js +170 -0
  129. package/src/lib/MarkdownDocs/remarkCodeMeta.js +22 -0
  130. package/src/lib/MarkdownDocs/remarkContainers.js +329 -0
  131. package/src/lib/MarkdownDocs/remarkCustomAnchors.js +42 -0
  132. package/src/lib/MarkdownDocs/remarkEscapeSvelte.js +33 -0
  133. package/src/lib/MarkdownDocs/remarkGlobalComponents.js +65 -0
  134. package/src/lib/MarkdownDocs/remarkImports.js +461 -0
  135. package/src/lib/MarkdownDocs/remarkImportsBrowser.js +349 -0
  136. package/src/lib/MarkdownDocs/remarkInlineAttrs.js +95 -0
  137. package/src/lib/MarkdownDocs/remarkMathToHtml.js +138 -0
  138. package/src/lib/MarkdownDocs/searchIndexBuilder.js +497 -0
  139. package/src/lib/MarkdownDocs/searchServer.js +263 -0
  140. package/src/lib/MarkdownDocs/treeViewTypes.ts +11 -0
  141. package/src/lib/MarkdownDocs/useRouter.svelte.ts +114 -0
  142. package/src/lib/MarkdownDocs/useSplitter.svelte.ts +33 -0
  143. package/src/lib/MarkdownDocs/versioningDefaults.js +20 -0
  144. package/src/lib/MarkdownDocs/vitePluginAiServer.js +204 -0
  145. package/src/lib/MarkdownDocs/vitePluginCopyDocs.js +153 -0
  146. package/src/lib/MarkdownDocs/vitePluginFrontmatter.js +109 -0
  147. package/src/lib/MarkdownDocs/vitePluginGregConfig.js +108 -0
  148. package/src/lib/MarkdownDocs/vitePluginSearchIndex.js +57 -0
  149. package/src/lib/MarkdownDocs/vitePluginSearchServer.js +190 -0
  150. package/src/lib/components/Badge.svelte +59 -0
  151. package/src/lib/components/Button.svelte +138 -0
  152. package/src/lib/components/CarbonAds.svelte +99 -0
  153. package/src/lib/components/CodeGroup.svelte +102 -0
  154. package/src/lib/components/Feature.svelte +209 -0
  155. package/src/lib/components/Features.svelte +123 -0
  156. package/src/lib/components/Hero.svelte +399 -0
  157. package/src/lib/components/Image.svelte +128 -0
  158. package/src/lib/components/Link.svelte +105 -0
  159. package/src/lib/components/SocialLink.svelte +84 -0
  160. package/src/lib/components/SocialLinks.svelte +33 -0
  161. package/src/lib/components/Steps.svelte +143 -0
  162. package/src/lib/components/TeamMember.svelte +273 -0
  163. package/src/lib/components/TeamMembers.svelte +81 -0
  164. package/src/lib/components/TeamPage.svelte +65 -0
  165. package/src/lib/components/TeamPageSection.svelte +108 -0
  166. package/src/lib/components/TeamPageTitle.svelte +89 -0
  167. package/src/lib/components/index.js +24 -0
  168. package/src/lib/portal/context.js +12 -0
  169. package/src/lib/portal/index.js +3 -0
  170. package/src/lib/portal/portal.svelte +14 -0
  171. package/src/lib/portal/slot.svelte +8 -0
  172. package/src/lib/scss/__code.scss +128 -0
  173. package/src/lib/scss/__containers.scss +99 -0
  174. package/src/lib/scss/__markdown.scss +447 -0
  175. package/src/lib/scss/__scrollbar.scss +60 -0
  176. package/src/lib/scss/__steps.scss +100 -0
  177. package/src/lib/scss/__theme.scss +238 -0
  178. package/src/lib/scss/__toc.scss +55 -0
  179. package/src/lib/scss/__utilities.scss +7 -0
  180. package/src/lib/scss/greg.scss +9 -0
  181. package/src/lib/spinner/spinner.svelte +42 -0
  182. package/svelte.config.js +146 -0
  183. package/types/index.d.ts +456 -0
@@ -0,0 +1,758 @@
1
+ <script lang="ts">
2
+ import { Languages, Sun, Moon } from "@lucide/svelte";
3
+ import SocialLink from "../components/SocialLink.svelte";
4
+ import DocsVersionSwitcher from "./DocsVersionSwitcher.svelte";
5
+ import { withBase } from "./common";
6
+
7
+ type VersionOption = {
8
+ version: string;
9
+ title: string;
10
+ };
11
+
12
+ type NavItem = {
13
+ text: string;
14
+ link?: string;
15
+ target?: string;
16
+ items?: NavItem[];
17
+ };
18
+
19
+ type Props = {
20
+ srcDir: string;
21
+ siteTitle?: string | false;
22
+ logo?:
23
+ | string
24
+ | { src: string; alt?: string }
25
+ | { light: string; dark: string; alt?: string };
26
+ socialLinks?: { icon: string | { svg: string }; link: string; ariaLabel?: string }[];
27
+ mainTitle?: string;
28
+ version?: string;
29
+ nav?: NavItem[];
30
+ locales?: { key: string; label: string; link: string; active: boolean }[];
31
+ langMenuLabel?: string;
32
+ theme: "light" | "dark";
33
+ darkModeSwitchLabel?: string;
34
+ lightModeSwitchTitle?: string;
35
+ darkModeSwitchTitle?: string;
36
+ searchButtonLabel?: string;
37
+ showSearch?: boolean;
38
+ versionOptions?: VersionOption[];
39
+ activeVersion?: string | null;
40
+ versionMenuLabel?: string;
41
+ versionStatusText?: string;
42
+ onVersionChange?: (version: string) => void;
43
+ onThemeChange: (t: "light" | "dark") => void;
44
+ navigate: (path: string) => void;
45
+ navigateHome?: (path: string) => void;
46
+ onOpenSearch: () => void;
47
+ };
48
+
49
+ const EXTERNAL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/{2})/i;
50
+ let openDropdownIdx: number | null = $state(null);
51
+
52
+ type FlatItem =
53
+ | { kind: "divider" }
54
+ | { kind: "header"; item: NavItem; depth: number }
55
+ | { kind: "link"; item: NavItem; depth: number };
56
+
57
+ function flatItems(items: NavItem[], depth = 0): FlatItem[] {
58
+ const out: FlatItem[] = [];
59
+ for (const it of items) {
60
+ if (it.items?.length) {
61
+ if (depth === 0 && out.length > 0)
62
+ out.push({ kind: "divider" });
63
+ out.push({ kind: "header", item: it, depth });
64
+ out.push(...flatItems(it.items, depth + 1));
65
+ } else {
66
+ out.push({ kind: "link", item: it, depth });
67
+ }
68
+ }
69
+ return out;
70
+ }
71
+
72
+ let {
73
+ srcDir,
74
+ siteTitle,
75
+ logo,
76
+ socialLinks = [],
77
+ mainTitle = "Greg",
78
+ version = "",
79
+ nav = [],
80
+ locales = [],
81
+ langMenuLabel = "Change language",
82
+ theme,
83
+ darkModeSwitchLabel = "Appearance",
84
+ lightModeSwitchTitle = "Switch to light theme",
85
+ darkModeSwitchTitle = "Switch to dark theme",
86
+ searchButtonLabel = "Search...",
87
+ showSearch = true,
88
+ versionOptions = [],
89
+ activeVersion = null,
90
+ versionMenuLabel = "Version",
91
+ versionStatusText = "",
92
+ onVersionChange = () => {},
93
+ onThemeChange,
94
+ navigate,
95
+ navigateHome = navigate,
96
+ onOpenSearch,
97
+ }: Props = $props();
98
+
99
+ function resolveLogoSrc(
100
+ img:
101
+ | string
102
+ | { src: string; alt?: string }
103
+ | { light: string; dark: string; alt?: string }
104
+ | undefined,
105
+ t: "light" | "dark",
106
+ ): string {
107
+ if (!img) return withBase(t === "dark" ? "/favicon-dark.svg" : "/favicon-light.svg");
108
+ if (typeof img === "string") return img;
109
+ if ("src" in img) return img.src;
110
+ return t === "dark" ? img.dark : img.light;
111
+ }
112
+
113
+ const resolvedTitle = $derived(siteTitle === undefined ? mainTitle : siteTitle);
114
+ const logoSrc = $derived(resolveLogoSrc(logo, theme));
115
+ const logoAlt = $derived.by(() => {
116
+ if (logo && typeof logo === "object" && "alt" in logo && logo.alt) return logo.alt;
117
+ if (siteTitle === false) return mainTitle;
118
+ return (resolvedTitle as string) ?? mainTitle;
119
+ });
120
+
121
+ let activeLocaleLink = $derived(
122
+ locales.find((locale) => locale.active)?.link ?? locales[0]?.link ?? '',
123
+ );
124
+ </script>
125
+
126
+ <svelte:window
127
+ onclick={() => {
128
+ openDropdownIdx = null;
129
+ }}
130
+ />
131
+ <header class="site-header">
132
+ <div class="header-left">
133
+ <a
134
+ href={withBase(srcDir || "/")}
135
+ class="site-title"
136
+ aria-label={logoAlt}
137
+ onclick={(e) => {
138
+ e.preventDefault();
139
+ navigateHome(srcDir || "/");
140
+ }}
141
+ >
142
+ <span
143
+ class="site-logo"
144
+ role="img"
145
+ aria-label={logoAlt}
146
+ style={`background-image: url(${logoSrc})`}
147
+ ></span>
148
+ {#if resolvedTitle !== false}
149
+ {resolvedTitle}
150
+ {/if}
151
+ </a>
152
+ {#if version}
153
+ <span class="version-badge">v{version}</span>
154
+ {/if}
155
+ </div>
156
+ <nav class="header-nav" aria-label="Main navigation">
157
+ {#each nav as item, i}
158
+ {#if item.items?.length}
159
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
160
+ <div
161
+ class="nav-dropdown"
162
+ class:open={openDropdownIdx === i}
163
+ onclick={(e) => e.stopPropagation()}
164
+ onkeydown={(e) => e.stopPropagation()}
165
+ >
166
+ <button
167
+ class="nav-link nav-dropdown-trigger"
168
+ type="button"
169
+ onclick={() => {
170
+ openDropdownIdx = openDropdownIdx === i ? null : i;
171
+ }}
172
+ aria-expanded={openDropdownIdx === i}
173
+ aria-haspopup="menu"
174
+ >
175
+ {item.text}
176
+ <svg
177
+ class="dropdown-chevron"
178
+ xmlns="http://www.w3.org/2000/svg"
179
+ viewBox="0 0 24 24"
180
+ fill="none"
181
+ stroke="currentColor"
182
+ stroke-width="2.5"
183
+ aria-hidden="true"
184
+ ><polyline points="6 9 12 15 18 9" /></svg
185
+ >
186
+ </button>
187
+ {#if openDropdownIdx === i}
188
+ <div class="nav-dropdown-menu" role="menu">
189
+ {#each flatItems(item.items!) as ri}
190
+ {#if ri.kind === "divider"}
191
+ <hr class="nav-dropdown-divider" />
192
+ {:else if ri.kind === "header"}
193
+ {#if ri.item.link}
194
+ <a
195
+ href={ri.item.link ? withBase(ri.item.link) : "#"}
196
+ class="nav-dropdown-group"
197
+ class:nav-dropdown-group--nested={ri.depth >
198
+ 0}
199
+ role="menuitem"
200
+ target={ri.item.target ??
201
+ (EXTERNAL_RE.test(ri.item.link)
202
+ ? "_blank"
203
+ : undefined)}
204
+ rel={EXTERNAL_RE.test(ri.item.link)
205
+ ? "noopener noreferrer"
206
+ : undefined}
207
+ onclick={(e) => {
208
+ openDropdownIdx = null;
209
+ if (
210
+ !EXTERNAL_RE.test(
211
+ ri.item.link!,
212
+ ) &&
213
+ !ri.item.target
214
+ ) {
215
+ e.preventDefault();
216
+ navigate(ri.item.link!);
217
+ }
218
+ }}>{ri.item.text}</a
219
+ >
220
+ {:else}
221
+ <p
222
+ class="nav-dropdown-group"
223
+ class:nav-dropdown-group--nested={ri.depth >
224
+ 0}
225
+ >
226
+ {ri.item.text}
227
+ </p>
228
+ {/if}
229
+ {:else}
230
+ <a
231
+ href={ri.item.link ? withBase(ri.item.link) : "#"}
232
+ class="nav-dropdown-item"
233
+ class:nav-dropdown-item--nested={ri.depth >
234
+ 0}
235
+ role="menuitem"
236
+ target={ri.item.target ??
237
+ (ri.item.link &&
238
+ EXTERNAL_RE.test(ri.item.link)
239
+ ? "_blank"
240
+ : undefined)}
241
+ rel={ri.item.link &&
242
+ EXTERNAL_RE.test(ri.item.link)
243
+ ? "noopener noreferrer"
244
+ : undefined}
245
+ onclick={(e) => {
246
+ openDropdownIdx = null;
247
+ if (
248
+ ri.item.link &&
249
+ !EXTERNAL_RE.test(
250
+ ri.item.link,
251
+ ) &&
252
+ !ri.item.target
253
+ ) {
254
+ e.preventDefault();
255
+ navigate(ri.item.link);
256
+ }
257
+ }}>{ri.item.text}</a
258
+ >
259
+ {/if}
260
+ {/each}
261
+ </div>
262
+ {/if}
263
+ </div>
264
+ {:else}
265
+ <a
266
+ href={item.link ? withBase(item.link) : "#"}
267
+ class="nav-link"
268
+ target={item.target ??
269
+ (item.link && EXTERNAL_RE.test(item.link)
270
+ ? "_blank"
271
+ : undefined)}
272
+ rel={item.link && EXTERNAL_RE.test(item.link)
273
+ ? "noopener noreferrer"
274
+ : undefined}
275
+ onclick={(e) => {
276
+ if (
277
+ item.link &&
278
+ !EXTERNAL_RE.test(item.link) &&
279
+ !item.target
280
+ ) {
281
+ e.preventDefault();
282
+ navigate(item.link);
283
+ }
284
+ }}>{item.text}</a
285
+ >
286
+ {/if}
287
+ {/each}
288
+ </nav>
289
+ <div class="header-right">
290
+ {#if versionOptions.length > 0}
291
+ <DocsVersionSwitcher
292
+ versions={versionOptions}
293
+ {activeVersion}
294
+ label={versionMenuLabel}
295
+ onChange={onVersionChange}
296
+ />
297
+ {:else if versionStatusText}
298
+ <span class="version-status" role="status" aria-live="polite">{versionStatusText}</span>
299
+ {/if}
300
+ {#if socialLinks.length}
301
+ <div class="header-social-links" aria-label="Social links">
302
+ {#each socialLinks as social (social.link)}
303
+ <SocialLink
304
+ icon={social.icon}
305
+ link={social.link}
306
+ ariaLabel={social.ariaLabel}
307
+ me={false}
308
+ />
309
+ {/each}
310
+ </div>
311
+ {/if}
312
+ {#if locales.length > 1}
313
+ <label class="locale-switcher" aria-label={langMenuLabel}>
314
+ <Languages size={15} aria-hidden="true" />
315
+ <select
316
+ class="locale-select"
317
+ value={activeLocaleLink}
318
+ onchange={(e) => {
319
+ const selected = (e.currentTarget as HTMLSelectElement).value;
320
+ if (!selected || selected === activeLocaleLink) return;
321
+ if (EXTERNAL_RE.test(selected)) {
322
+ window.location.href = selected;
323
+ return;
324
+ }
325
+ navigate(selected);
326
+ }}
327
+ >
328
+ {#each locales as locale}
329
+ <option value={locale.link}>{locale.label}</option>
330
+ {/each}
331
+ </select>
332
+ </label>
333
+ {/if}
334
+ <div class="theme-group" role="group" aria-label={darkModeSwitchLabel}>
335
+ <button
336
+ class="theme-btn"
337
+ class:active={theme === "light"}
338
+ onclick={() => onThemeChange("light")}
339
+ type="button"
340
+ aria-label={lightModeSwitchTitle}
341
+ title={lightModeSwitchTitle}
342
+ aria-pressed={theme === "light"}><Sun size={15} /></button
343
+ >
344
+ <button
345
+ class="theme-btn"
346
+ class:active={theme === "dark"}
347
+ onclick={() => onThemeChange("dark")}
348
+ type="button"
349
+ aria-label={darkModeSwitchTitle}
350
+ title={darkModeSwitchTitle}
351
+ aria-pressed={theme === "dark"}><Moon size={15} /></button
352
+ >
353
+ </div>
354
+ {#if showSearch}
355
+ <button class="search-trigger" onclick={onOpenSearch} type="button">
356
+ <svg
357
+ class="search-trigger-icon"
358
+ xmlns="http://www.w3.org/2000/svg"
359
+ viewBox="0 0 24 24"
360
+ fill="none"
361
+ stroke="currentColor"
362
+ stroke-width="2"
363
+ >
364
+ <circle cx="11" cy="11" r="8" /><line
365
+ x1="21"
366
+ y1="21"
367
+ x2="16.65"
368
+ y2="16.65"
369
+ />
370
+ </svg>
371
+ <span class="search-trigger-label">{searchButtonLabel}</span>
372
+ <span class="search-trigger-hint"><kbd>Ctrl</kbd><kbd>K</kbd></span>
373
+ </button>
374
+ {/if}
375
+ </div>
376
+ </header>
377
+
378
+ <style lang="scss">
379
+ .site-header {
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: space-between;
383
+ height: var(--greg-header-height);
384
+ padding: 0 1.25rem;
385
+ background-color: var(--greg-header-background);
386
+ border-bottom: 1px solid var(--greg-border-color);
387
+ flex-shrink: 0;
388
+ z-index: 10;
389
+ gap: 1rem;
390
+ }
391
+
392
+ .header-left {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 0.6rem;
396
+ flex-shrink: 0;
397
+ }
398
+
399
+ .site-title {
400
+ display: flex;
401
+ align-items: center;
402
+ gap: 0.5rem;
403
+ font-size: 1rem;
404
+ font-weight: 700;
405
+ color: var(--greg-color);
406
+ text-decoration: none;
407
+ white-space: nowrap;
408
+ letter-spacing: -0.01em;
409
+
410
+ .site-logo {
411
+ display: inline-block;
412
+ width: 22px;
413
+ height: 22px;
414
+ flex-shrink: 0;
415
+ background-position: center;
416
+ background-repeat: no-repeat;
417
+ background-size: contain;
418
+ }
419
+
420
+ &:hover {
421
+ color: var(--greg-accent);
422
+ }
423
+ }
424
+
425
+ .version-badge {
426
+ font-size: 0.7rem;
427
+ font-weight: 500;
428
+ color: var(--greg-menu-section-color);
429
+ background: var(--greg-menu-background);
430
+ border: 1px solid var(--greg-border-color);
431
+ border-radius: 999px;
432
+ padding: 0.1rem 0.5rem;
433
+ }
434
+
435
+ .header-nav {
436
+ display: flex;
437
+ align-items: center;
438
+ gap: 0.25rem;
439
+ flex: 1;
440
+ justify-content: flex-end;
441
+ }
442
+
443
+ .nav-link {
444
+ padding: 0.3rem 0.6rem;
445
+ border-radius: 5px;
446
+ font-size: 0.875rem;
447
+ font-weight: 500;
448
+ color: var(--greg-color);
449
+ text-decoration: none;
450
+ white-space: nowrap;
451
+ transition:
452
+ background 0.15s,
453
+ color 0.15s;
454
+
455
+ &:hover {
456
+ background: var(--greg-menu-hover-background);
457
+ color: var(--greg-accent);
458
+ }
459
+ }
460
+
461
+ .nav-dropdown {
462
+ position: relative;
463
+ }
464
+
465
+ .nav-dropdown-trigger {
466
+ display: flex;
467
+ align-items: center;
468
+ gap: 0.25rem;
469
+ cursor: pointer;
470
+ border: none;
471
+ background: none;
472
+ font-family: inherit;
473
+
474
+ .dropdown-chevron {
475
+ width: 12px;
476
+ height: 12px;
477
+ transition: transform 0.15s;
478
+ }
479
+
480
+ &[aria-expanded="true"] .dropdown-chevron {
481
+ transform: rotate(180deg);
482
+ }
483
+ }
484
+
485
+ .nav-dropdown-menu {
486
+ position: absolute;
487
+ top: calc(100% + 0.35rem);
488
+ left: 0;
489
+ min-width: 10rem;
490
+ background: var(--greg-header-background);
491
+ border: 1px solid var(--greg-border-color);
492
+ border-radius: 6px;
493
+ padding: 0.25rem;
494
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
495
+ z-index: 100;
496
+ display: flex;
497
+ flex-direction: column;
498
+ gap: 0.05rem;
499
+ }
500
+
501
+ .nav-dropdown-item {
502
+ padding: 0.4rem 0.75rem;
503
+ border-radius: 4px;
504
+ font-size: 0.875rem;
505
+ color: var(--greg-color);
506
+ text-decoration: none;
507
+ white-space: nowrap;
508
+ transition:
509
+ background 0.15s,
510
+ color 0.15s;
511
+
512
+ &:hover {
513
+ background: var(--greg-menu-hover-background);
514
+ color: var(--greg-accent);
515
+ }
516
+ }
517
+
518
+ :global(.nav-dropdown-item--nested) {
519
+ padding-left: 1.5rem;
520
+ }
521
+
522
+ .nav-dropdown-group {
523
+ margin: 0;
524
+ padding: 0.35rem 0.75rem 0.15rem;
525
+ font-size: 0.72rem;
526
+ font-weight: 600;
527
+ letter-spacing: 0.03em;
528
+ text-transform: uppercase;
529
+ color: var(--greg-menu-section-color);
530
+ white-space: nowrap;
531
+ text-decoration: none;
532
+ }
533
+
534
+ :global(a.nav-dropdown-group) {
535
+ border-radius: 4px;
536
+ transition:
537
+ background 0.15s,
538
+ color 0.15s;
539
+ cursor: pointer;
540
+ display: block;
541
+ &:hover {
542
+ background: var(--greg-menu-hover-background);
543
+ color: var(--greg-accent);
544
+ }
545
+ }
546
+
547
+ :global(.nav-dropdown-group--nested) {
548
+ padding-left: 1.5rem;
549
+ font-size: 0.68rem;
550
+ }
551
+
552
+ .nav-dropdown-divider {
553
+ border: none;
554
+ border-top: 1px solid var(--greg-border-color);
555
+ margin: 0.25rem 0;
556
+ }
557
+
558
+ .header-right {
559
+ display: flex;
560
+ align-items: center;
561
+ gap: 0.75rem;
562
+ justify-content: flex-end;
563
+ }
564
+
565
+ .header-social-links {
566
+ display: inline-flex;
567
+ align-items: center;
568
+ gap: 0.15rem;
569
+ }
570
+
571
+ .locale-switcher {
572
+ display: inline-flex;
573
+ align-items: center;
574
+ gap: 0.35rem;
575
+ padding: 0.2rem 0.45rem;
576
+ border: 1px solid var(--greg-border-color);
577
+ border-radius: 6px;
578
+ color: var(--greg-menu-section-color);
579
+ background: var(--greg-menu-background);
580
+ flex-shrink: 0;
581
+ }
582
+
583
+ .version-status {
584
+ font-size: 0.75rem;
585
+ line-height: 1;
586
+ color: var(--greg-menu-section-color);
587
+ padding: 0.22rem 0.45rem;
588
+ border: 1px dashed var(--greg-border-color);
589
+ border-radius: 6px;
590
+ background: var(--greg-menu-background);
591
+ white-space: nowrap;
592
+ }
593
+
594
+ .locale-select {
595
+ background: transparent;
596
+ border: none;
597
+ font-size: 0.8rem;
598
+ color: var(--greg-color);
599
+ font-family: inherit;
600
+ outline: none;
601
+ min-width: 4.5rem;
602
+ cursor: pointer;
603
+ }
604
+
605
+ .theme-group {
606
+ display: flex;
607
+ border: 1px solid var(--greg-border-color);
608
+ border-radius: 6px;
609
+ flex-shrink: 0;
610
+ }
611
+
612
+ .theme-btn {
613
+ display: flex;
614
+ align-items: center;
615
+ justify-content: center;
616
+ background: none;
617
+ border: none;
618
+ border-radius: 0;
619
+ padding: 0.38rem 0.5rem;
620
+ color: var(--greg-menu-section-color);
621
+ cursor: pointer;
622
+ transition:
623
+ background 0.15s,
624
+ color 0.15s;
625
+ outline: none;
626
+
627
+ &:first-child {
628
+ border-radius: 5px 0 0 5px;
629
+ }
630
+ &:last-child {
631
+ border-radius: 0 5px 5px 0;
632
+ }
633
+
634
+ &:focus-visible {
635
+ outline: 2px solid var(--greg-accent);
636
+ outline-offset: 2px;
637
+ }
638
+
639
+ &:hover:not(.active) {
640
+ background: var(--greg-menu-hover-background);
641
+ color: var(--greg-color);
642
+ }
643
+
644
+ &.active {
645
+ background: var(--greg-accent-light);
646
+ color: var(--greg-accent);
647
+ }
648
+ }
649
+
650
+ .search-trigger {
651
+ display: flex;
652
+ align-items: center;
653
+ gap: 0.5rem;
654
+ width: 100%;
655
+ padding: 0.38rem 0.75rem;
656
+ background-color: var(--greg-menu-background);
657
+ border: 1px solid var(--greg-border-color);
658
+ border-radius: 6px;
659
+ color: var(--greg-menu-section-color);
660
+ font-size: 0.875rem;
661
+ cursor: pointer;
662
+ transition:
663
+ border-color 0.15s,
664
+ box-shadow 0.15s;
665
+ font-family: inherit;
666
+
667
+ &:hover {
668
+ border-color: var(--greg-accent);
669
+ color: var(--greg-color);
670
+ }
671
+
672
+ .search-trigger-icon {
673
+ width: 15px;
674
+ height: 15px;
675
+ flex-shrink: 0;
676
+ }
677
+
678
+ .search-trigger-label {
679
+ flex: 1;
680
+ text-align: left;
681
+ }
682
+
683
+ .search-trigger-hint {
684
+ display: flex;
685
+ gap: 0.15rem;
686
+ flex-shrink: 0;
687
+
688
+ kbd {
689
+ background: var(--greg-background);
690
+ border: 1px solid var(--greg-border-color);
691
+ border-radius: 3px;
692
+ padding: 0.05rem 0.3rem;
693
+ font-size: 0.65rem;
694
+ line-height: 1.5;
695
+ font-family: inherit;
696
+ }
697
+ }
698
+ }
699
+
700
+ @media (max-width: 900px) {
701
+ .header-right {
702
+ max-width: none;
703
+ gap: 0.45rem;
704
+ }
705
+
706
+ .locale-switcher {
707
+ padding: 0.2rem 0.35rem;
708
+ }
709
+
710
+ .locale-select {
711
+ min-width: 3.8rem;
712
+ font-size: 0.75rem;
713
+ }
714
+
715
+ .search-trigger {
716
+ width: auto;
717
+ padding: 0.38rem 0.55rem;
718
+ }
719
+
720
+ .search-trigger-hint {
721
+ display: none;
722
+ }
723
+ }
724
+
725
+ @media (max-width: 700px) {
726
+ .site-header {
727
+ padding: 0 0.75rem;
728
+ }
729
+
730
+ .version-badge {
731
+ display: none;
732
+ }
733
+
734
+ .locale-switcher {
735
+ padding: 0.2rem 0.28rem;
736
+ }
737
+
738
+ .locale-switcher :global(svg) {
739
+ display: none;
740
+ }
741
+
742
+ .locale-select {
743
+ min-width: 3.25rem;
744
+ }
745
+
746
+ .theme-btn {
747
+ padding: 0.35rem 0.4rem;
748
+ }
749
+
750
+ .search-trigger {
751
+ padding: 0.38rem 0.45rem;
752
+ }
753
+
754
+ .search-trigger-label {
755
+ display: none;
756
+ }
757
+ }
758
+ </style>