@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,399 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import Button from "./Button.svelte";
4
+ import Image from "./Image.svelte";
5
+
6
+ type ThemeImage =
7
+ | string
8
+ | { src: string; alt?: string }
9
+ | { dark: string; light: string; alt?: string };
10
+
11
+ type HeroAction = {
12
+ theme?: "brand" | "alt";
13
+ text: string;
14
+ link: string;
15
+ target?: string;
16
+ rel?: string;
17
+ };
18
+
19
+ type Props = {
20
+ name?: string;
21
+ text?: string;
22
+ tagline?: string;
23
+ image?: ThemeImage;
24
+ actions?: HeroAction[];
25
+ /** Snippet placed before the heading block */
26
+ heroBefore?: Snippet;
27
+ /** Snippet replacing heading+tagline entirely */
28
+ heroInfo?: Snippet;
29
+ /** Snippet placed after tagline, before actions */
30
+ heroInfoAfter?: Snippet;
31
+ /** Snippet replacing the image */
32
+ heroImage?: Snippet;
33
+ /** Snippet placed after action buttons */
34
+ heroActionsAfter?: Snippet;
35
+ };
36
+
37
+ let {
38
+ name,
39
+ text,
40
+ tagline,
41
+ image,
42
+ actions,
43
+ heroBefore,
44
+ heroInfo,
45
+ heroInfoAfter,
46
+ heroImage,
47
+ heroActionsAfter,
48
+ }: Props = $props();
49
+
50
+ let hasImage = $derived(!!image || !!heroImage);
51
+ </script>
52
+
53
+ <div class="Hero" class:has-image={hasImage}>
54
+ <div class="container">
55
+ <div class="main">
56
+ {#if heroBefore}
57
+ {@render heroBefore()}
58
+ {/if}
59
+
60
+ {#if heroInfo}
61
+ {@render heroInfo()}
62
+ {:else}
63
+ <h1 class="heading">
64
+ {#if name}
65
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
66
+ <span class="name clip">{@html name}</span>
67
+ {/if}
68
+ {#if text}
69
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
70
+ <span class="text">{@html text}</span>
71
+ {/if}
72
+ </h1>
73
+ {#if tagline}
74
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
75
+ <p class="tagline">{@html tagline}</p>
76
+ {/if}
77
+ {/if}
78
+
79
+ {#if heroInfoAfter}
80
+ {@render heroInfoAfter()}
81
+ {/if}
82
+
83
+ {#if actions?.length}
84
+ <div class="actions">
85
+ {#each actions as action (action.link)}
86
+ <div class="action">
87
+ <Button
88
+ tag="a"
89
+ size="medium"
90
+ theme={action.theme ?? "brand"}
91
+ text={action.text}
92
+ href={action.link}
93
+ target={action.target}
94
+ rel={action.rel}
95
+ />
96
+ </div>
97
+ {/each}
98
+ </div>
99
+ {/if}
100
+
101
+ {#if heroActionsAfter}
102
+ {@render heroActionsAfter()}
103
+ {/if}
104
+ </div>
105
+
106
+ {#if hasImage}
107
+ <div class="image">
108
+ <div class="image-container">
109
+ <div class="image-bg"></div>
110
+ {#if heroImage}
111
+ {@render heroImage()}
112
+ {:else if image}
113
+ <Image class="image-src" {image} />
114
+ {/if}
115
+ </div>
116
+ </div>
117
+ {/if}
118
+ </div>
119
+ </div>
120
+
121
+ <style>
122
+ .Hero {
123
+ padding: 48px 24px;
124
+ }
125
+
126
+ @media (min-width: 640px) {
127
+ .Hero {
128
+ padding: 80px 48px 64px;
129
+ }
130
+ }
131
+
132
+ @media (min-width: 960px) {
133
+ .Hero {
134
+ padding: 80px 64px 64px;
135
+ }
136
+ }
137
+
138
+ .container {
139
+ display: flex;
140
+ flex-direction: column;
141
+ margin: 0 auto;
142
+ max-width: 1152px;
143
+ }
144
+
145
+ @media (min-width: 960px) {
146
+ .container {
147
+ flex-direction: row;
148
+ }
149
+ }
150
+
151
+ .main {
152
+ position: relative;
153
+ z-index: 10;
154
+ order: 2;
155
+ flex-grow: 1;
156
+ flex-shrink: 0;
157
+ }
158
+
159
+ .has-image .container {
160
+ text-align: center;
161
+ }
162
+
163
+ @media (min-width: 960px) {
164
+ .has-image .container {
165
+ text-align: left;
166
+ }
167
+
168
+ .main {
169
+ order: 1;
170
+ width: calc((100% / 3) * 2);
171
+ }
172
+
173
+ .has-image .main {
174
+ max-width: 592px;
175
+ }
176
+ }
177
+
178
+ .heading {
179
+ display: flex;
180
+ flex-direction: column;
181
+ margin: 0;
182
+ border: none;
183
+ padding: 0;
184
+ }
185
+
186
+ .name,
187
+ .text {
188
+ width: fit-content;
189
+ max-width: 392px;
190
+ letter-spacing: -0.4px;
191
+ line-height: 40px;
192
+ font-size: 32px;
193
+ font-weight: 700;
194
+ white-space: pre-wrap;
195
+ }
196
+
197
+ .has-image .name,
198
+ .has-image .text {
199
+ margin: 0 auto;
200
+ }
201
+
202
+ .name {
203
+ background: var(--greg-accent);
204
+ -webkit-background-clip: text;
205
+ background-clip: text;
206
+ -webkit-text-fill-color: transparent;
207
+ }
208
+
209
+ .text {
210
+ color: var(--greg-color);
211
+ }
212
+
213
+ @media (min-width: 640px) {
214
+ .name,
215
+ .text {
216
+ max-width: 576px;
217
+ line-height: 56px;
218
+ font-size: 48px;
219
+ }
220
+ }
221
+
222
+ @media (min-width: 960px) {
223
+ .name,
224
+ .text {
225
+ line-height: 64px;
226
+ font-size: 56px;
227
+ }
228
+
229
+ .has-image .name,
230
+ .has-image .text {
231
+ margin: 0;
232
+ }
233
+ }
234
+
235
+ .tagline {
236
+ padding-top: 8px;
237
+ max-width: 392px;
238
+ line-height: 28px;
239
+ font-size: 18px;
240
+ font-weight: 500;
241
+ white-space: pre-wrap;
242
+ color: var(--greg-menu-section-color);
243
+ margin: 0;
244
+ }
245
+
246
+ .has-image .tagline {
247
+ margin: 0 auto;
248
+ }
249
+
250
+ @media (min-width: 640px) {
251
+ .tagline {
252
+ padding-top: 12px;
253
+ max-width: 576px;
254
+ line-height: 32px;
255
+ font-size: 20px;
256
+ }
257
+ }
258
+
259
+ @media (min-width: 960px) {
260
+ .tagline {
261
+ line-height: 36px;
262
+ font-size: 24px;
263
+ }
264
+
265
+ .has-image .tagline {
266
+ margin: 0;
267
+ }
268
+ }
269
+
270
+ .actions {
271
+ display: flex;
272
+ flex-wrap: wrap;
273
+ margin: -6px;
274
+ padding-top: 24px;
275
+ }
276
+
277
+ .has-image .actions {
278
+ justify-content: center;
279
+ }
280
+
281
+ @media (min-width: 640px) {
282
+ .actions {
283
+ padding-top: 32px;
284
+ }
285
+ }
286
+
287
+ @media (min-width: 960px) {
288
+ .has-image .actions {
289
+ justify-content: flex-start;
290
+ }
291
+ }
292
+
293
+ .action {
294
+ flex-shrink: 0;
295
+ padding: 6px;
296
+ }
297
+
298
+ /* Image */
299
+ .image {
300
+ order: 1;
301
+ margin: -48px -24px -48px;
302
+ }
303
+
304
+ @media (min-width: 640px) {
305
+ .image {
306
+ margin: -80px -24px -48px;
307
+ }
308
+ }
309
+
310
+ @media (min-width: 960px) {
311
+ .image {
312
+ flex-grow: 1;
313
+ order: 2;
314
+ margin: 0;
315
+ min-height: 100%;
316
+ }
317
+ }
318
+
319
+ .image-container {
320
+ position: relative;
321
+ margin: 0 auto;
322
+ width: 320px;
323
+ height: 320px;
324
+ }
325
+
326
+ @media (min-width: 640px) {
327
+ .image-container {
328
+ width: 392px;
329
+ height: 392px;
330
+ }
331
+ }
332
+
333
+ @media (min-width: 960px) {
334
+ .image-container {
335
+ display: flex;
336
+ justify-content: center;
337
+ align-items: center;
338
+ width: 100%;
339
+ height: 100%;
340
+ transform: translate(-32px, -32px);
341
+ }
342
+ }
343
+
344
+ .image-bg {
345
+ position: absolute;
346
+ top: 50%;
347
+ left: 50%;
348
+ border-radius: 50%;
349
+ width: 192px;
350
+ height: 192px;
351
+ background: radial-gradient(
352
+ circle,
353
+ var(--greg-accent),
354
+ transparent 70%
355
+ );
356
+ filter: blur(48px);
357
+ transform: translate(-50%, -50%);
358
+ }
359
+
360
+ @media (min-width: 640px) {
361
+ .image-bg {
362
+ width: 256px;
363
+ height: 256px;
364
+ }
365
+ }
366
+
367
+ @media (min-width: 960px) {
368
+ .image-bg {
369
+ width: 480px;
370
+ height: 480px;
371
+ }
372
+ }
373
+
374
+ :global(.image-src) {
375
+ position: absolute;
376
+ top: 50%;
377
+ left: 50%;
378
+ max-width: 192px;
379
+ max-height: 192px;
380
+ width: 100%;
381
+ height: 100%;
382
+ object-fit: contain;
383
+ transform: translate(-50%, -50%);
384
+ }
385
+
386
+ @media (min-width: 640px) {
387
+ :global(.image-src) {
388
+ max-width: 256px;
389
+ max-height: 256px;
390
+ }
391
+ }
392
+
393
+ @media (min-width: 960px) {
394
+ :global(.image-src) {
395
+ max-width: 320px;
396
+ max-height: 320px;
397
+ }
398
+ }
399
+ </style>
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ThemeableImage — same shape as VitePress:
4
+ * - string → simple src
5
+ * - { src: string; alt?: string } → simple src + alt
6
+ * - { dark: Image; light: Image; alt?: string } → theme-aware pair
7
+ */
8
+ type SimpleImage =
9
+ | string
10
+ | {
11
+ src: string;
12
+ alt?: string;
13
+ width?: number | string;
14
+ height?: number | string;
15
+ };
16
+ type ThemeImage = { dark: SimpleImage; light: SimpleImage; alt?: string };
17
+ type GregImage = SimpleImage | ThemeImage;
18
+
19
+ type Props = {
20
+ image: GregImage;
21
+ alt?: string;
22
+ class?: string;
23
+ width?: number | string;
24
+ height?: number | string;
25
+ };
26
+
27
+ let { image, alt, class: cls = "", width, height }: Props = $props();
28
+
29
+ function isThemePair(img: GregImage): img is ThemeImage {
30
+ return typeof img === "object" && "dark" in img && "light" in img;
31
+ }
32
+
33
+ function getSrc(img: SimpleImage): string {
34
+ return typeof img === "string" ? img : img.src;
35
+ }
36
+
37
+ function getAlt(img: SimpleImage, fallbackAlt?: string): string {
38
+ if (typeof img === "string") return fallbackAlt ?? "";
39
+ return img.alt ?? fallbackAlt ?? "";
40
+ }
41
+
42
+ function getWidth(
43
+ img: SimpleImage,
44
+ fallback?: number | string,
45
+ ): number | string | undefined {
46
+ if (typeof img === "object" && img.width) return img.width;
47
+ return fallback;
48
+ }
49
+
50
+ function getHeight(
51
+ img: SimpleImage,
52
+ fallback?: number | string,
53
+ ): number | string | undefined {
54
+ if (typeof img === "object" && img.height) return img.height;
55
+ return fallback;
56
+ }
57
+ </script>
58
+
59
+ {#if image}
60
+ {#if isThemePair(image)}
61
+ <img
62
+ class="Image dark {cls}"
63
+ src={getSrc(image.dark)}
64
+ alt={getAlt(image.dark, image.alt ?? alt)}
65
+ width={getWidth(image.dark, width)}
66
+ height={getHeight(image.dark, height)}
67
+ />
68
+ <img
69
+ class="Image light {cls}"
70
+ src={getSrc(image.light)}
71
+ alt={getAlt(image.light, image.alt ?? alt)}
72
+ width={getWidth(image.light, width)}
73
+ height={getHeight(image.light, height)}
74
+ />
75
+ {:else}
76
+ <img
77
+ class="Image {cls}"
78
+ src={getSrc(image)}
79
+ alt={getAlt(image, alt)}
80
+ width={getWidth(image, width)}
81
+ height={getHeight(image, height)}
82
+ />
83
+ {/if}
84
+ {/if}
85
+
86
+ <style>
87
+ /* Default: light variant visible, dark variant hidden. */
88
+ :global(.Image.dark) {
89
+ display: none;
90
+ }
91
+
92
+ /* Greg runtime theme switcher is authoritative. */
93
+ :global(.greg[data-theme="light"] .Image.dark) {
94
+ display: none;
95
+ }
96
+
97
+ :global(.greg[data-theme="light"] .Image.light) {
98
+ display: initial;
99
+ }
100
+
101
+ :global(.greg[data-theme="dark"] .Image.light) {
102
+ display: none;
103
+ }
104
+
105
+ :global(.greg[data-theme="dark"] .Image.dark) {
106
+ display: initial;
107
+ }
108
+
109
+ /* Fallback outside Greg container. */
110
+ @media (prefers-color-scheme: dark) {
111
+ :global(:not(.greg) .Image.light) {
112
+ display: none;
113
+ }
114
+
115
+ :global(:not(.greg) .Image.dark) {
116
+ display: initial;
117
+ }
118
+ }
119
+
120
+ /* Legacy explicit .dark class on <html>. */
121
+ :global(html.dark .Image.light) {
122
+ display: none;
123
+ }
124
+
125
+ :global(html.dark .Image.dark) {
126
+ display: initial;
127
+ }
128
+ </style>
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { ExternalLink } from "@lucide/svelte";
4
+
5
+ type Props = {
6
+ tag?: string;
7
+ href?: string;
8
+ noIcon?: boolean;
9
+ target?: string;
10
+ rel?: string;
11
+ class?: string;
12
+ children?: Snippet;
13
+ };
14
+
15
+ let {
16
+ tag,
17
+ href,
18
+ noIcon = false,
19
+ target,
20
+ rel,
21
+ class: cls = "",
22
+ children,
23
+ }: Props = $props();
24
+
25
+ const EXTERNAL_RE = /^(?:[a-z][a-z\d+\-.]*:|\/\/)/i;
26
+
27
+ let resolvedTag = $derived(tag ?? (href ? "a" : "span"));
28
+ let isExternal = $derived(
29
+ (!!href && EXTERNAL_RE.test(href)) || target === "_blank",
30
+ );
31
+ let resolvedTarget = $derived(
32
+ target ?? (isExternal ? "_blank" : undefined),
33
+ );
34
+ let resolvedRel = $derived(rel ?? (isExternal ? "noreferrer" : undefined));
35
+ let classes = $derived(
36
+ [
37
+ "Link",
38
+ href ? "link" : "",
39
+ isExternal && !noIcon ? "external-link-icon" : "",
40
+ noIcon ? "no-icon" : "",
41
+ cls,
42
+ ]
43
+ .filter(Boolean)
44
+ .join(" "),
45
+ );
46
+ </script>
47
+
48
+ {#if resolvedTag === "a"}
49
+ <a class={classes} href={href} target={resolvedTarget} rel={resolvedRel}>
50
+ {#if children}
51
+ {@render children()}
52
+ {/if}
53
+ {#if isExternal && !noIcon}
54
+ <span class="external-icon" aria-hidden="true">
55
+ <ExternalLink size={11} strokeWidth={2} />
56
+ </span>
57
+ {/if}
58
+ </a>
59
+ {:else}
60
+ <svelte:element this={resolvedTag} class={classes}>
61
+ {#if children}
62
+ {@render children()}
63
+ {/if}
64
+ </svelte:element>
65
+ {/if}
66
+
67
+ <style>
68
+ .Link {
69
+ color: inherit;
70
+ text-decoration: none;
71
+ }
72
+
73
+ .Link.link {
74
+ color: var(--greg-accent);
75
+ text-underline-offset: 2px;
76
+ transition:
77
+ color 0.25s,
78
+ opacity 0.25s;
79
+ }
80
+
81
+ .Link.link:hover {
82
+ color: color-mix(in srgb, var(--greg-accent) 80%, white);
83
+ }
84
+
85
+ .external-icon {
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ margin-left: 3px;
90
+ vertical-align: middle;
91
+ line-height: 1;
92
+ }
93
+
94
+ .external-icon :global(svg) {
95
+ stroke: currentColor;
96
+ }
97
+
98
+ .Link.no-icon .external-icon {
99
+ display: none;
100
+ }
101
+
102
+ :global(.greg[data-external-link-icon="false"]) .Link.external-link-icon .external-icon {
103
+ display: none;
104
+ }
105
+ </style>
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+
4
+ type SocialLinkIcon = string | { svg: string };
5
+
6
+ type Props = {
7
+ icon: SocialLinkIcon;
8
+ link: string;
9
+ ariaLabel?: string;
10
+ me?: boolean;
11
+ };
12
+
13
+ let { icon, link, ariaLabel, me = true }: Props = $props();
14
+
15
+ let el: HTMLAnchorElement | undefined = $state();
16
+
17
+ // If the CSS mask for a named icon isn't loaded, fall back to Iconify API
18
+ onMount(() => {
19
+ const span = el?.children[0];
20
+ if (
21
+ span instanceof HTMLElement &&
22
+ span.className.startsWith("social-icon-") &&
23
+ (getComputedStyle(span).maskImage ||
24
+ (getComputedStyle(span) as any).webkitMaskImage) === "none"
25
+ ) {
26
+ const name = span.className.replace("social-icon-", "");
27
+ span.style.setProperty(
28
+ "--icon",
29
+ `url('https://api.iconify.design/simple-icons/${name}.svg')`,
30
+ );
31
+ }
32
+ });
33
+
34
+ let svgHtml = $derived.by(() => {
35
+ if (typeof icon === "object") return icon.svg;
36
+ return `<span class="social-icon-${icon}"></span>`;
37
+ });
38
+
39
+ let label = $derived(ariaLabel ?? (typeof icon === "string" ? icon : ""));
40
+ let relAttr = $derived(me ? "me noopener" : "noopener");
41
+ </script>
42
+
43
+ <!-- eslint-disable-next-line svelte/no-at-html-tags -->
44
+ <a
45
+ bind:this={el}
46
+ class="SocialLink no-icon"
47
+ href={link}
48
+ aria-label={label}
49
+ target="_blank"
50
+ rel={relAttr}>{@html svgHtml}</a
51
+ >
52
+
53
+ <style>
54
+ .SocialLink {
55
+ display: flex;
56
+ justify-content: center;
57
+ align-items: center;
58
+ width: 36px;
59
+ height: 36px;
60
+ color: var(--greg-menu-section-color);
61
+ transition: color 0.5s;
62
+ text-decoration: none;
63
+ }
64
+
65
+ .SocialLink:hover {
66
+ color: var(--greg-color);
67
+ transition: color 0.25s;
68
+ }
69
+
70
+ .SocialLink :global(svg),
71
+ .SocialLink :global([class^="social-icon-"]) {
72
+ width: 20px;
73
+ height: 20px;
74
+ fill: currentColor;
75
+ }
76
+
77
+ /* Named icon support via CSS mask (same mechanism as VitePress vpi-social-*) */
78
+ .SocialLink :global([class^="social-icon-"]) {
79
+ display: inline-block;
80
+ background: currentColor;
81
+ mask: var(--icon) center / contain no-repeat;
82
+ -webkit-mask: var(--icon) center / contain no-repeat;
83
+ }
84
+ </style>