@lupinum/ginko-docs 0.2.2 → 0.2.5

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 (206) hide show
  1. package/README.md +130 -73
  2. package/app/app.config.ts +15 -13
  3. package/app/app.vue +4 -2
  4. package/app/assets/css/prose.css +2203 -149
  5. package/app/assets/css/tailwind.css +60 -38
  6. package/app/assets/css/theme-palettes.css +309 -0
  7. package/app/components/content/DocumentPageShell.vue +1 -1
  8. package/app/components/content/Feedback.vue +12 -11
  9. package/app/components/content/ImageZoomDialog.vue +84 -0
  10. package/app/components/content/PageMarkdownCopy.vue +2 -0
  11. package/app/components/content/imageZoom.ts +25 -0
  12. package/app/components/mdc/MdcAccordion.vue +6 -3
  13. package/app/components/mdc/MdcAccordionItem.vue +7 -16
  14. package/app/components/mdc/MdcApi.vue +159 -0
  15. package/app/components/mdc/MdcAside.vue +6 -10
  16. package/app/components/mdc/MdcCallout.vue +9 -10
  17. package/app/components/mdc/MdcCard.vue +36 -47
  18. package/app/components/mdc/MdcCards.vue +30 -0
  19. package/app/components/mdc/MdcCodeGroup.vue +4 -0
  20. package/app/components/mdc/MdcCodeTree.vue +234 -0
  21. package/app/components/mdc/MdcCollapse.vue +61 -0
  22. package/app/components/mdc/MdcError.vue +8 -1
  23. package/app/components/mdc/MdcExcerpt.vue +26 -0
  24. package/app/components/mdc/MdcFigure.vue +54 -20
  25. package/app/components/mdc/MdcFiles.vue +120 -0
  26. package/app/components/mdc/MdcIdea.vue +2 -1
  27. package/app/components/mdc/MdcInfo.vue +2 -1
  28. package/app/components/mdc/MdcInlineToc.vue +51 -0
  29. package/app/components/{ui/dialog/DialogHeader.vue → mdc/MdcKbd.vue} +2 -4
  30. package/app/components/mdc/MdcNote.vue +2 -1
  31. package/app/components/mdc/MdcQuiz.vue +43 -83
  32. package/app/components/mdc/MdcQuizQuestion.vue +104 -109
  33. package/app/components/mdc/MdcReadMore.vue +57 -77
  34. package/app/components/mdc/MdcSteps.vue +45 -11
  35. package/app/components/mdc/MdcSuccess.vue +8 -1
  36. package/app/components/mdc/MdcTabs.vue +44 -26
  37. package/app/components/mdc/MdcTimeline.vue +4 -1
  38. package/app/components/mdc/MdcTimelineItem.vue +18 -27
  39. package/app/components/mdc/MdcWarning.vue +8 -1
  40. package/app/components/mdc/api.utils.ts +132 -0
  41. package/app/components/mdc/code-tree.utils.ts +22 -0
  42. package/app/components/mdc/quiz.utils.ts +38 -0
  43. package/app/components/prose/ProseBlockquote.vue +17 -1
  44. package/app/components/prose/ProseH1.vue +1 -1
  45. package/app/components/prose/ProseH2.vue +7 -2
  46. package/app/components/prose/ProseH3.vue +7 -2
  47. package/app/components/prose/ProseH4.vue +17 -2
  48. package/app/components/{ui/kbd/KbdGroup.vue → prose/ProseH5.vue} +4 -3
  49. package/app/components/{ui/card/CardTitle.vue → prose/ProseH6.vue} +4 -3
  50. package/app/components/prose/ProseImg.vue +34 -99
  51. package/app/components/prose/ProsePre.vue +63 -100
  52. package/app/components/prose/ProseTable.vue +5 -3
  53. package/app/components/site/ModeToggle.vue +12 -3
  54. package/app/components/site/SiteBanner.vue +43 -2
  55. package/app/components/site/SiteFooter.vue +3 -5
  56. package/app/components/site/SiteHeader.vue +266 -132
  57. package/app/components/site/SiteHeroCode.vue +164 -0
  58. package/app/components/site/SiteLocaleSwitcher.vue +25 -9
  59. package/app/components/site/SiteSocialLinks.vue +31 -0
  60. package/app/components/ui/badge/Badge.vue +1 -1
  61. package/app/components/ui/button/Button.vue +1 -1
  62. package/app/components/ui/collapsible/CollapsibleContent.vue +1 -1
  63. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +14 -4
  64. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +1 -1
  65. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +1 -1
  66. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +1 -1
  67. package/app/components/ui/dropdown-menu/index.ts +0 -9
  68. package/app/components/ui/kbd/Kbd.vue +1 -1
  69. package/app/components/ui/kbd/index.ts +0 -1
  70. package/app/components/ui/scroll-area/ScrollArea.vue +1 -1
  71. package/app/components/ui/scroll-area/ScrollBar.vue +1 -1
  72. package/app/components/ui/separator/Separator.vue +1 -1
  73. package/app/components/ui/sheet/SheetContent.vue +1 -1
  74. package/app/components/ui/sheet/SheetDescription.vue +1 -1
  75. package/app/components/ui/sheet/SheetOverlay.vue +1 -1
  76. package/app/components/ui/sheet/SheetTitle.vue +1 -1
  77. package/app/components/ui/sheet/index.ts +0 -3
  78. package/app/components/ui/tabs/Tabs.vue +1 -1
  79. package/app/components/ui/tabs/TabsList.vue +1 -1
  80. package/app/components/ui/tabs/TabsTrigger.vue +1 -1
  81. package/app/components/ui/tabs/index.ts +0 -1
  82. package/app/composables/site-navigation.utils.ts +69 -12
  83. package/app/composables/useContentRouteAlternates.ts +13 -21
  84. package/app/composables/useLocalizedRouteSwitch.ts +1 -1
  85. package/app/composables/useMetaKey.ts +18 -0
  86. package/app/composables/useProseAppearance.ts +24 -0
  87. package/app/composables/useSiteNavigation.ts +10 -40
  88. package/app/config/site.utils.ts +1 -1
  89. package/app/error.vue +1 -5
  90. package/app/features/docs/components/DocsBreadcrumb.vue +37 -0
  91. package/app/features/docs/components/DocsContributeLinks.vue +28 -18
  92. package/app/features/docs/components/DocsMobileToc.vue +12 -8
  93. package/app/features/docs/components/DocsPageContent.vue +93 -47
  94. package/app/features/docs/components/DocsPageNav.vue +1 -1
  95. package/app/features/docs/components/DocsSidebar.vue +37 -21
  96. package/app/features/docs/components/DocsSidebarItem.vue +53 -100
  97. package/app/features/docs/components/DocsSidebarRow.vue +27 -0
  98. package/app/features/docs/components/DocsSidebarTabs.vue +46 -22
  99. package/app/features/docs/components/DocsToc.vue +80 -34
  100. package/app/features/docs/composables/useDocsNavigation.ts +2 -2
  101. package/app/features/docs/composables/useDocsNavigationData.ts +4 -1
  102. package/app/features/docs/composables/useRevealActive.ts +58 -0
  103. package/app/features/docs/composables/useScrollspy.ts +87 -19
  104. package/app/features/docs/docs-navigation.ts +17 -30
  105. package/app/features/docs/toc-context.ts +6 -0
  106. package/app/features/search/command-center.ts +11 -90
  107. package/app/features/search/components/SiteCommandCenter.vue +58 -38
  108. package/app/features/search/useCommandCenter.ts +48 -48
  109. package/app/layouts/docs.vue +1 -1
  110. package/app/lib/errors.ts +2 -49
  111. package/app/pages/blog/[slug].vue +26 -9
  112. package/app/pages/blog/index.vue +38 -14
  113. package/app/pages/docs/index.vue +10 -14
  114. package/app/pages/index.vue +180 -61
  115. package/app/plugins/ginko-docs-theme.ts +12 -0
  116. package/app/utils/content.ts +11 -0
  117. package/app/utils/file-icons.ts +90 -0
  118. package/app/utils/index.ts +7 -1
  119. package/app/utils/repository.ts +20 -0
  120. package/content.js +68 -50
  121. package/content.ts +51 -32
  122. package/i18n/messages/global/blog.ts +4 -0
  123. package/i18n/messages/global/command.ts +6 -2
  124. package/i18n/messages/global/docs.ts +4 -0
  125. package/i18n/messages/global/nav.ts +6 -3
  126. package/i18n/messages/index.ts +3 -3
  127. package/icon-bundle.ts +9 -56
  128. package/index.d.ts +7 -1
  129. package/modules/feature-routing.ts +15 -0
  130. package/nuxt.config.ts +21 -7
  131. package/package.json +29 -26
  132. package/runtime/server/agent-markdown.ts +0 -2
  133. package/server/api/_ginko-docs/redirects.get.ts +28 -0
  134. package/server/middleware/redirects.ts +22 -0
  135. package/server/routes/[locale]/blog/rss.xml.get.ts +13 -0
  136. package/server/routes/blog/rss.xml.get.ts +5 -0
  137. package/server/utils/blog-feed.ts +49 -0
  138. package/server/utils/feed.ts +70 -0
  139. package/server/utils/redirects.ts +38 -0
  140. package/server/utils/redirects.utils.ts +58 -0
  141. package/shared/theme-palettes.ts +37 -0
  142. package/shared/types/app-config.ts +75 -10
  143. package/tags.ts +47 -59
  144. package/app/components/mdc/MdcAlert.vue +0 -34
  145. package/app/components/mdc/MdcCardGroup.vue +0 -47
  146. package/app/components/mdc/MdcDocImg.vue +0 -25
  147. package/app/components/mdc/MdcField.vue +0 -62
  148. package/app/components/mdc/MdcFieldGroup.vue +0 -18
  149. package/app/components/mdc/MdcPassage.vue +0 -32
  150. package/app/components/mdc/MdcQuizOption.vue +0 -91
  151. package/app/components/mdc/MdcReadMoreGroup.vue +0 -18
  152. package/app/components/mdc/MdcShortcut.vue +0 -46
  153. package/app/components/mdc/MdcStep.vue +0 -42
  154. package/app/components/ui/accordion/Accordion.vue +0 -15
  155. package/app/components/ui/accordion/AccordionContent.vue +0 -23
  156. package/app/components/ui/accordion/AccordionItem.vue +0 -24
  157. package/app/components/ui/accordion/AccordionTrigger.vue +0 -35
  158. package/app/components/ui/accordion/index.ts +0 -4
  159. package/app/components/ui/alert/Alert.vue +0 -21
  160. package/app/components/ui/alert/AlertDescription.vue +0 -22
  161. package/app/components/ui/alert/AlertTitle.vue +0 -22
  162. package/app/components/ui/alert/index.ts +0 -34
  163. package/app/components/ui/card/Card.vue +0 -16
  164. package/app/components/ui/card/CardAction.vue +0 -17
  165. package/app/components/ui/card/CardContent.vue +0 -14
  166. package/app/components/ui/card/CardDescription.vue +0 -14
  167. package/app/components/ui/card/CardFooter.vue +0 -14
  168. package/app/components/ui/card/CardHeader.vue +0 -22
  169. package/app/components/ui/card/card-variants.ts +0 -15
  170. package/app/components/ui/card/index.ts +0 -8
  171. package/app/components/ui/checkbox/Checkbox.vue +0 -35
  172. package/app/components/ui/checkbox/index.ts +0 -1
  173. package/app/components/ui/dialog/Dialog.vue +0 -15
  174. package/app/components/ui/dialog/DialogClose.vue +0 -12
  175. package/app/components/ui/dialog/DialogContent.vue +0 -69
  176. package/app/components/ui/dialog/DialogDescription.vue +0 -21
  177. package/app/components/ui/dialog/DialogFooter.vue +0 -15
  178. package/app/components/ui/dialog/DialogOverlay.vue +0 -26
  179. package/app/components/ui/dialog/DialogPortal.vue +0 -12
  180. package/app/components/ui/dialog/DialogTitle.vue +0 -21
  181. package/app/components/ui/dialog/DialogTrigger.vue +0 -12
  182. package/app/components/ui/dialog/index.ts +0 -10
  183. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +0 -36
  184. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +0 -12
  185. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +0 -25
  186. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +0 -17
  187. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +0 -15
  188. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +0 -29
  189. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +0 -31
  190. package/app/components/ui/input/Input.vue +0 -23
  191. package/app/components/ui/input/index.ts +0 -1
  192. package/app/components/ui/label/Label.vue +0 -14
  193. package/app/components/ui/label/index.ts +0 -1
  194. package/app/components/ui/sheet/SheetClose.vue +0 -12
  195. package/app/components/ui/sheet/SheetFooter.vue +0 -12
  196. package/app/components/ui/sheet/SheetHeader.vue +0 -12
  197. package/app/components/ui/switch/Switch.vue +0 -39
  198. package/app/components/ui/switch/index.ts +0 -1
  199. package/app/components/ui/tabs/TabsContent.vue +0 -21
  200. package/app/components/ui/textarea/Textarea.vue +0 -31
  201. package/app/components/ui/textarea/index.ts +0 -1
  202. package/app/features/docs/composables/useDocsEntryPath.ts +0 -13
  203. package/app/lib/utils.ts +0 -7
  204. package/i18n/messages/source.ts +0 -5
  205. package/runtime/markdown/inline-code-highlight.ts +0 -155
  206. package/runtime/utils.ts +0 -7
@@ -1,5 +1,6 @@
1
1
  @import "tailwindcss";
2
2
  @import "tw-animate-css";
3
+ @import "./theme-palettes.css";
3
4
  @source "../../**/*.{vue,ts}";
4
5
 
5
6
  @custom-variant dark (&:is(.dark *));
@@ -86,6 +87,9 @@
86
87
  --color-chart-3: var(--chart-3);
87
88
  --color-chart-4: var(--chart-4);
88
89
  --color-chart-5: var(--chart-5);
90
+ --color-agent-background: var(--agent-background);
91
+ --color-agent-foreground: var(--agent-foreground);
92
+ --color-agent-muted: var(--agent-muted);
89
93
  --animate-collapsible-down: collapsible-down 0.2s ease-out;
90
94
  --animate-collapsible-up: collapsible-up 0.2s ease-out;
91
95
 
@@ -111,11 +115,22 @@
111
115
  }
112
116
 
113
117
  :root {
118
+ /* Surface elevation for prose components (RFC-001). Declared explicitly:
119
+ Tailwind only emits --shadow-* variables where shadow utilities are used,
120
+ and prose.css consumes this via var(). */
121
+ --shadow-xs: 0 1px 2px 0 oklch(0 0 0 / 0.05);
122
+
114
123
  /* Docs layout (Fumadocs-style shell) */
115
124
  --site-header-height: 3.5rem;
125
+ /* Currently visible banner height; SiteBanner is the single writer. */
126
+ --site-banner-height: 0px;
116
127
  --docs-sidebar-width-expanded: 268px;
117
128
  --docs-sidebar-width: var(--docs-sidebar-width-expanded);
118
129
  --docs-toc-width: 16rem;
130
+ /* Anchor scroll offset: sticky header + banner + breathing room. */
131
+ --content-scroll-margin: calc(
132
+ var(--site-banner-height, 0px) + var(--site-header-height) + 3.5rem
133
+ );
119
134
 
120
135
  --font-body: "Public Sans", system-ui, sans-serif;
121
136
  --font-heading: "Public Sans", system-ui, sans-serif;
@@ -125,19 +140,19 @@
125
140
 
126
141
  --radius: 0.625rem;
127
142
  --background: oklch(1 0 0);
128
- --foreground: oklch(0.13 0.028 261.692);
143
+ --foreground: var(--theme-neutral-950);
129
144
  --card: oklch(1 0 0);
130
- --card-foreground: oklch(0.13 0.028 261.692);
145
+ --card-foreground: var(--theme-neutral-950);
131
146
  --popover: oklch(1 0 0);
132
- --popover-foreground: oklch(0.13 0.028 261.692);
133
- --primary: oklch(0.21 0.034 264.665);
134
- --primary-foreground: oklch(0.985 0.002 247.839);
135
- --secondary: oklch(0.967 0.003 264.542);
136
- --secondary-foreground: oklch(0.21 0.034 264.665);
137
- --muted: oklch(0.967 0.003 264.542);
138
- --muted-foreground: oklch(0.551 0.027 264.364);
139
- --accent: oklch(0.967 0.003 264.542);
140
- --accent-foreground: oklch(0.21 0.034 264.665);
147
+ --popover-foreground: var(--theme-neutral-950);
148
+ --primary: var(--theme-primary-light);
149
+ --primary-foreground: var(--theme-primary-light-foreground);
150
+ --secondary: var(--theme-neutral-100);
151
+ --secondary-foreground: var(--theme-neutral-900);
152
+ --muted: var(--theme-neutral-100);
153
+ --muted-foreground: var(--theme-neutral-500);
154
+ --accent: var(--theme-neutral-100);
155
+ --accent-foreground: var(--theme-neutral-900);
141
156
  --destructive: oklch(0.577 0.245 27.325);
142
157
  --destructive-foreground: oklch(0.985 0.002 247.839);
143
158
  --success: oklch(0.55 0.14 162);
@@ -182,12 +197,15 @@
182
197
  --home-radius-card: 24px;
183
198
  --home-radius-section: 32px;
184
199
  --home-radius-panel: 40px;
185
- --code: oklch(0.985 0.002 247.839);
186
- --code-foreground: oklch(0.13 0.028 261.692);
187
- --code-border: oklch(0.928 0.006 264.531);
188
- --border: oklch(0.928 0.006 264.531);
189
- --input: oklch(0.928 0.006 264.531);
190
- --ring: oklch(0.707 0.022 261.325);
200
+ --code: var(--theme-neutral-50);
201
+ --code-foreground: var(--theme-neutral-950);
202
+ --code-border: var(--theme-neutral-200);
203
+ --border: var(--theme-neutral-200);
204
+ --input: var(--theme-neutral-200);
205
+ --ring: var(--theme-primary-light-ring);
206
+ --agent-background: var(--theme-neutral-900);
207
+ --agent-foreground: var(--theme-neutral-100);
208
+ --agent-muted: var(--theme-neutral-400);
191
209
  --chart-1: var(--accent-blue);
192
210
  --chart-2: var(--accent-mint);
193
211
  --chart-3: var(--accent-yellow);
@@ -196,20 +214,21 @@
196
214
  }
197
215
 
198
216
  .dark {
199
- --background: oklch(0.135 0 0);
200
- --foreground: oklch(0.985 0 0);
201
- --card: oklch(0.19 0 0);
202
- --card-foreground: oklch(0.985 0 0);
203
- --popover: oklch(0.18 0 0);
204
- --popover-foreground: oklch(0.985 0 0);
205
- --primary: oklch(0.92 0 0);
206
- --primary-foreground: oklch(0.18 0 0);
207
- --secondary: oklch(0.245 0 0);
208
- --secondary-foreground: oklch(0.985 0 0);
209
- --muted: oklch(0.245 0 0);
210
- --muted-foreground: oklch(0.72 0 0);
211
- --accent: oklch(0.245 0 0);
212
- --accent-foreground: oklch(0.985 0 0);
217
+ --shadow-xs: 0 1px 2px 0 oklch(0 0 0 / 0.4);
218
+ --background: var(--theme-neutral-950);
219
+ --foreground: var(--theme-neutral-50);
220
+ --card: var(--theme-neutral-900);
221
+ --card-foreground: var(--theme-neutral-50);
222
+ --popover: var(--theme-neutral-900);
223
+ --popover-foreground: var(--theme-neutral-50);
224
+ --primary: var(--theme-primary-dark);
225
+ --primary-foreground: var(--theme-primary-dark-foreground);
226
+ --secondary: var(--theme-neutral-800);
227
+ --secondary-foreground: var(--theme-neutral-50);
228
+ --muted: var(--theme-neutral-800);
229
+ --muted-foreground: var(--theme-neutral-400);
230
+ --accent: var(--theme-neutral-800);
231
+ --accent-foreground: var(--theme-neutral-50);
213
232
  --destructive: oklch(0.704 0.191 22.216);
214
233
  --destructive-foreground: oklch(0.985 0.002 247.839);
215
234
  --success: oklch(0.72 0.14 162);
@@ -254,17 +273,20 @@
254
273
  --home-radius-card: 24px;
255
274
  --home-radius-section: 32px;
256
275
  --home-radius-panel: 40px;
257
- --code: oklch(0.18 0.02 260);
258
- --code-foreground: oklch(0.985 0.002 247.839);
259
- --code-border: oklch(1 0 0 / 12%);
260
- --border: oklch(1 0 0 / 10%);
261
- --input: oklch(1 0 0 / 15%);
262
- --ring: oklch(0.551 0.027 264.364);
276
+ --code: var(--theme-neutral-900);
277
+ --code-foreground: var(--theme-neutral-50);
278
+ --code-border: var(--theme-neutral-800);
279
+ --border: var(--theme-neutral-800);
280
+ --input: var(--theme-neutral-700);
281
+ --ring: var(--theme-primary-dark-ring);
282
+ --agent-background: var(--theme-neutral-950);
283
+ --agent-foreground: var(--theme-neutral-100);
284
+ --agent-muted: var(--theme-neutral-400);
263
285
  --chart-1: oklch(0.488 0.243 264.376);
264
286
  --chart-2: oklch(0.696 0.17 162.48);
265
287
  --chart-3: oklch(0.769 0.188 70.08);
266
288
  --chart-4: oklch(0.627 0.265 303.9);
267
- --chart-5: oklch(0.645 0.246 16.439);
289
+ --chart-5: var(--primary);
268
290
  }
269
291
 
270
292
  @layer base {
@@ -0,0 +1,309 @@
1
+ /* Palette selection is separate from semantic shadcn tokens. The root values
2
+ are complete fallbacks for the default theme and incomplete custom ramps. */
3
+ :root {
4
+ --theme-neutral-50: var(--color-zinc-50);
5
+ --theme-neutral-100: var(--color-zinc-100);
6
+ --theme-neutral-200: var(--color-zinc-200);
7
+ --theme-neutral-300: var(--color-zinc-300);
8
+ --theme-neutral-400: var(--color-zinc-400);
9
+ --theme-neutral-500: var(--color-zinc-500);
10
+ --theme-neutral-600: var(--color-zinc-600);
11
+ --theme-neutral-700: var(--color-zinc-700);
12
+ --theme-neutral-800: var(--color-zinc-800);
13
+ --theme-neutral-900: var(--color-zinc-900);
14
+ --theme-neutral-950: var(--color-zinc-950);
15
+ --theme-primary-light: var(--theme-neutral-900);
16
+ --theme-primary-light-foreground: var(--theme-neutral-50);
17
+ --theme-primary-light-ring: var(--theme-neutral-500);
18
+ --theme-primary-dark: var(--theme-neutral-100);
19
+ --theme-primary-dark-foreground: var(--theme-neutral-900);
20
+ --theme-primary-dark-ring: var(--theme-neutral-400);
21
+ }
22
+
23
+ html[data-neutral="slate"] {
24
+ --theme-neutral-50: var(--color-slate-50);
25
+ --theme-neutral-100: var(--color-slate-100);
26
+ --theme-neutral-200: var(--color-slate-200);
27
+ --theme-neutral-300: var(--color-slate-300);
28
+ --theme-neutral-400: var(--color-slate-400);
29
+ --theme-neutral-500: var(--color-slate-500);
30
+ --theme-neutral-600: var(--color-slate-600);
31
+ --theme-neutral-700: var(--color-slate-700);
32
+ --theme-neutral-800: var(--color-slate-800);
33
+ --theme-neutral-900: var(--color-slate-900);
34
+ --theme-neutral-950: var(--color-slate-950);
35
+ }
36
+
37
+ html[data-neutral="gray"] {
38
+ --theme-neutral-50: var(--color-gray-50);
39
+ --theme-neutral-100: var(--color-gray-100);
40
+ --theme-neutral-200: var(--color-gray-200);
41
+ --theme-neutral-300: var(--color-gray-300);
42
+ --theme-neutral-400: var(--color-gray-400);
43
+ --theme-neutral-500: var(--color-gray-500);
44
+ --theme-neutral-600: var(--color-gray-600);
45
+ --theme-neutral-700: var(--color-gray-700);
46
+ --theme-neutral-800: var(--color-gray-800);
47
+ --theme-neutral-900: var(--color-gray-900);
48
+ --theme-neutral-950: var(--color-gray-950);
49
+ }
50
+
51
+ html[data-neutral="zinc"] {
52
+ --theme-neutral-50: var(--color-zinc-50);
53
+ --theme-neutral-100: var(--color-zinc-100);
54
+ --theme-neutral-200: var(--color-zinc-200);
55
+ --theme-neutral-300: var(--color-zinc-300);
56
+ --theme-neutral-400: var(--color-zinc-400);
57
+ --theme-neutral-500: var(--color-zinc-500);
58
+ --theme-neutral-600: var(--color-zinc-600);
59
+ --theme-neutral-700: var(--color-zinc-700);
60
+ --theme-neutral-800: var(--color-zinc-800);
61
+ --theme-neutral-900: var(--color-zinc-900);
62
+ --theme-neutral-950: var(--color-zinc-950);
63
+ }
64
+
65
+ html[data-neutral="neutral"] {
66
+ --theme-neutral-50: var(--color-neutral-50);
67
+ --theme-neutral-100: var(--color-neutral-100);
68
+ --theme-neutral-200: var(--color-neutral-200);
69
+ --theme-neutral-300: var(--color-neutral-300);
70
+ --theme-neutral-400: var(--color-neutral-400);
71
+ --theme-neutral-500: var(--color-neutral-500);
72
+ --theme-neutral-600: var(--color-neutral-600);
73
+ --theme-neutral-700: var(--color-neutral-700);
74
+ --theme-neutral-800: var(--color-neutral-800);
75
+ --theme-neutral-900: var(--color-neutral-900);
76
+ --theme-neutral-950: var(--color-neutral-950);
77
+ }
78
+
79
+ html[data-neutral="stone"] {
80
+ --theme-neutral-50: var(--color-stone-50);
81
+ --theme-neutral-100: var(--color-stone-100);
82
+ --theme-neutral-200: var(--color-stone-200);
83
+ --theme-neutral-300: var(--color-stone-300);
84
+ --theme-neutral-400: var(--color-stone-400);
85
+ --theme-neutral-500: var(--color-stone-500);
86
+ --theme-neutral-600: var(--color-stone-600);
87
+ --theme-neutral-700: var(--color-stone-700);
88
+ --theme-neutral-800: var(--color-stone-800);
89
+ --theme-neutral-900: var(--color-stone-900);
90
+ --theme-neutral-950: var(--color-stone-950);
91
+ }
92
+
93
+ html[data-neutral="taupe"] {
94
+ --theme-neutral-50: var(--color-taupe-50);
95
+ --theme-neutral-100: var(--color-taupe-100);
96
+ --theme-neutral-200: var(--color-taupe-200);
97
+ --theme-neutral-300: var(--color-taupe-300);
98
+ --theme-neutral-400: var(--color-taupe-400);
99
+ --theme-neutral-500: var(--color-taupe-500);
100
+ --theme-neutral-600: var(--color-taupe-600);
101
+ --theme-neutral-700: var(--color-taupe-700);
102
+ --theme-neutral-800: var(--color-taupe-800);
103
+ --theme-neutral-900: var(--color-taupe-900);
104
+ --theme-neutral-950: var(--color-taupe-950);
105
+ }
106
+
107
+ html[data-neutral="mauve"] {
108
+ --theme-neutral-50: var(--color-mauve-50);
109
+ --theme-neutral-100: var(--color-mauve-100);
110
+ --theme-neutral-200: var(--color-mauve-200);
111
+ --theme-neutral-300: var(--color-mauve-300);
112
+ --theme-neutral-400: var(--color-mauve-400);
113
+ --theme-neutral-500: var(--color-mauve-500);
114
+ --theme-neutral-600: var(--color-mauve-600);
115
+ --theme-neutral-700: var(--color-mauve-700);
116
+ --theme-neutral-800: var(--color-mauve-800);
117
+ --theme-neutral-900: var(--color-mauve-900);
118
+ --theme-neutral-950: var(--color-mauve-950);
119
+ }
120
+
121
+ html[data-neutral="mist"] {
122
+ --theme-neutral-50: var(--color-mist-50);
123
+ --theme-neutral-100: var(--color-mist-100);
124
+ --theme-neutral-200: var(--color-mist-200);
125
+ --theme-neutral-300: var(--color-mist-300);
126
+ --theme-neutral-400: var(--color-mist-400);
127
+ --theme-neutral-500: var(--color-mist-500);
128
+ --theme-neutral-600: var(--color-mist-600);
129
+ --theme-neutral-700: var(--color-mist-700);
130
+ --theme-neutral-800: var(--color-mist-800);
131
+ --theme-neutral-900: var(--color-mist-900);
132
+ --theme-neutral-950: var(--color-mist-950);
133
+ }
134
+
135
+ html[data-neutral="olive"] {
136
+ --theme-neutral-50: var(--color-olive-50);
137
+ --theme-neutral-100: var(--color-olive-100);
138
+ --theme-neutral-200: var(--color-olive-200);
139
+ --theme-neutral-300: var(--color-olive-300);
140
+ --theme-neutral-400: var(--color-olive-400);
141
+ --theme-neutral-500: var(--color-olive-500);
142
+ --theme-neutral-600: var(--color-olive-600);
143
+ --theme-neutral-700: var(--color-olive-700);
144
+ --theme-neutral-800: var(--color-olive-800);
145
+ --theme-neutral-900: var(--color-olive-900);
146
+ --theme-neutral-950: var(--color-olive-950);
147
+ }
148
+
149
+ html[data-primary="neutral"] {
150
+ --theme-primary-light: var(--theme-neutral-900);
151
+ --theme-primary-light-foreground: var(--theme-neutral-50);
152
+ --theme-primary-light-ring: var(--theme-neutral-500);
153
+ --theme-primary-dark: var(--theme-neutral-100);
154
+ --theme-primary-dark-foreground: var(--theme-neutral-900);
155
+ --theme-primary-dark-ring: var(--theme-neutral-400);
156
+ }
157
+
158
+ html[data-primary="red"] {
159
+ --theme-primary-light: var(--color-red-700);
160
+ --theme-primary-light-foreground: var(--color-red-50);
161
+ --theme-primary-light-ring: var(--color-red-500);
162
+ --theme-primary-dark: var(--color-red-400);
163
+ --theme-primary-dark-foreground: var(--color-red-950);
164
+ --theme-primary-dark-ring: var(--color-red-400);
165
+ }
166
+
167
+ html[data-primary="orange"] {
168
+ --theme-primary-light: var(--color-orange-700);
169
+ --theme-primary-light-foreground: var(--color-orange-50);
170
+ --theme-primary-light-ring: var(--color-orange-500);
171
+ --theme-primary-dark: var(--color-orange-400);
172
+ --theme-primary-dark-foreground: var(--color-orange-950);
173
+ --theme-primary-dark-ring: var(--color-orange-400);
174
+ }
175
+
176
+ html[data-primary="amber"] {
177
+ --theme-primary-light: var(--color-amber-500);
178
+ --theme-primary-light-foreground: var(--color-amber-950);
179
+ --theme-primary-light-ring: var(--color-amber-500);
180
+ --theme-primary-dark: var(--color-amber-400);
181
+ --theme-primary-dark-foreground: var(--color-amber-950);
182
+ --theme-primary-dark-ring: var(--color-amber-400);
183
+ }
184
+
185
+ html[data-primary="yellow"] {
186
+ --theme-primary-light: var(--color-yellow-400);
187
+ --theme-primary-light-foreground: var(--color-yellow-950);
188
+ --theme-primary-light-ring: var(--color-yellow-500);
189
+ --theme-primary-dark: var(--color-yellow-400);
190
+ --theme-primary-dark-foreground: var(--color-yellow-950);
191
+ --theme-primary-dark-ring: var(--color-yellow-400);
192
+ }
193
+
194
+ html[data-primary="lime"] {
195
+ --theme-primary-light: var(--color-lime-500);
196
+ --theme-primary-light-foreground: var(--color-lime-950);
197
+ --theme-primary-light-ring: var(--color-lime-500);
198
+ --theme-primary-dark: var(--color-lime-400);
199
+ --theme-primary-dark-foreground: var(--color-lime-950);
200
+ --theme-primary-dark-ring: var(--color-lime-400);
201
+ }
202
+
203
+ html[data-primary="green"] {
204
+ --theme-primary-light: var(--color-green-700);
205
+ --theme-primary-light-foreground: var(--color-green-50);
206
+ --theme-primary-light-ring: var(--color-green-500);
207
+ --theme-primary-dark: var(--color-green-400);
208
+ --theme-primary-dark-foreground: var(--color-green-950);
209
+ --theme-primary-dark-ring: var(--color-green-400);
210
+ }
211
+
212
+ html[data-primary="emerald"] {
213
+ --theme-primary-light: var(--color-emerald-700);
214
+ --theme-primary-light-foreground: var(--color-emerald-50);
215
+ --theme-primary-light-ring: var(--color-emerald-500);
216
+ --theme-primary-dark: var(--color-emerald-400);
217
+ --theme-primary-dark-foreground: var(--color-emerald-950);
218
+ --theme-primary-dark-ring: var(--color-emerald-400);
219
+ }
220
+
221
+ html[data-primary="teal"] {
222
+ --theme-primary-light: var(--color-teal-700);
223
+ --theme-primary-light-foreground: var(--color-teal-50);
224
+ --theme-primary-light-ring: var(--color-teal-500);
225
+ --theme-primary-dark: var(--color-teal-400);
226
+ --theme-primary-dark-foreground: var(--color-teal-950);
227
+ --theme-primary-dark-ring: var(--color-teal-400);
228
+ }
229
+
230
+ html[data-primary="cyan"] {
231
+ --theme-primary-light: var(--color-cyan-700);
232
+ --theme-primary-light-foreground: var(--color-cyan-50);
233
+ --theme-primary-light-ring: var(--color-cyan-500);
234
+ --theme-primary-dark: var(--color-cyan-400);
235
+ --theme-primary-dark-foreground: var(--color-cyan-950);
236
+ --theme-primary-dark-ring: var(--color-cyan-400);
237
+ }
238
+
239
+ html[data-primary="sky"] {
240
+ --theme-primary-light: var(--color-sky-700);
241
+ --theme-primary-light-foreground: var(--color-sky-50);
242
+ --theme-primary-light-ring: var(--color-sky-500);
243
+ --theme-primary-dark: var(--color-sky-400);
244
+ --theme-primary-dark-foreground: var(--color-sky-950);
245
+ --theme-primary-dark-ring: var(--color-sky-400);
246
+ }
247
+
248
+ html[data-primary="blue"] {
249
+ --theme-primary-light: var(--color-blue-600);
250
+ --theme-primary-light-foreground: var(--color-blue-50);
251
+ --theme-primary-light-ring: var(--color-blue-500);
252
+ --theme-primary-dark: var(--color-blue-400);
253
+ --theme-primary-dark-foreground: var(--color-blue-950);
254
+ --theme-primary-dark-ring: var(--color-blue-400);
255
+ }
256
+
257
+ html[data-primary="indigo"] {
258
+ --theme-primary-light: var(--color-indigo-600);
259
+ --theme-primary-light-foreground: var(--color-indigo-50);
260
+ --theme-primary-light-ring: var(--color-indigo-500);
261
+ --theme-primary-dark: var(--color-indigo-400);
262
+ --theme-primary-dark-foreground: var(--color-indigo-950);
263
+ --theme-primary-dark-ring: var(--color-indigo-400);
264
+ }
265
+
266
+ html[data-primary="violet"] {
267
+ --theme-primary-light: var(--color-violet-600);
268
+ --theme-primary-light-foreground: var(--color-violet-50);
269
+ --theme-primary-light-ring: var(--color-violet-500);
270
+ --theme-primary-dark: var(--color-violet-400);
271
+ --theme-primary-dark-foreground: var(--color-violet-950);
272
+ --theme-primary-dark-ring: var(--color-violet-400);
273
+ }
274
+
275
+ html[data-primary="purple"] {
276
+ --theme-primary-light: var(--color-purple-600);
277
+ --theme-primary-light-foreground: var(--color-purple-50);
278
+ --theme-primary-light-ring: var(--color-purple-500);
279
+ --theme-primary-dark: var(--color-purple-400);
280
+ --theme-primary-dark-foreground: var(--color-purple-950);
281
+ --theme-primary-dark-ring: var(--color-purple-400);
282
+ }
283
+
284
+ html[data-primary="fuchsia"] {
285
+ --theme-primary-light: var(--color-fuchsia-700);
286
+ --theme-primary-light-foreground: var(--color-fuchsia-50);
287
+ --theme-primary-light-ring: var(--color-fuchsia-500);
288
+ --theme-primary-dark: var(--color-fuchsia-400);
289
+ --theme-primary-dark-foreground: var(--color-fuchsia-950);
290
+ --theme-primary-dark-ring: var(--color-fuchsia-400);
291
+ }
292
+
293
+ html[data-primary="pink"] {
294
+ --theme-primary-light: var(--color-pink-700);
295
+ --theme-primary-light-foreground: var(--color-pink-50);
296
+ --theme-primary-light-ring: var(--color-pink-500);
297
+ --theme-primary-dark: var(--color-pink-400);
298
+ --theme-primary-dark-foreground: var(--color-pink-950);
299
+ --theme-primary-dark-ring: var(--color-pink-400);
300
+ }
301
+
302
+ html[data-primary="rose"] {
303
+ --theme-primary-light: var(--color-rose-700);
304
+ --theme-primary-light-foreground: var(--color-rose-50);
305
+ --theme-primary-light-ring: var(--color-rose-500);
306
+ --theme-primary-dark: var(--color-rose-400);
307
+ --theme-primary-dark-foreground: var(--color-rose-950);
308
+ --theme-primary-dark-ring: var(--color-rose-400);
309
+ }
@@ -46,7 +46,7 @@ const { t } = useI18n();
46
46
  </ul>
47
47
  </nav>
48
48
 
49
- <article class="[&_h2[id]]:scroll-mt-28">
49
+ <article class="[&_h2[id]]:scroll-mt-[var(--content-scroll-margin)]">
50
50
  <slot />
51
51
  </article>
52
52
  </div>
@@ -4,6 +4,7 @@ import { computed, ref } from "vue";
4
4
  import { useI18n, useRoute } from "#imports";
5
5
  import { useGinkoAnalytics } from "#ginko-docs/composables/useGinkoAnalytics";
6
6
  import { useGinkoDocsConfig } from "#ginko-docs/composables/useGinkoDocsConfig";
7
+ import { buildRepoIssueUrl } from "#ginko-docs/utils/repository";
7
8
 
8
9
  withDefaults(
9
10
  defineProps<{
@@ -17,8 +18,8 @@ const { t, locale } = useI18n();
17
18
  const route = useRoute();
18
19
 
19
20
  const config = useGinkoDocsConfig();
20
- const enabled = config.feedback.enabled;
21
- const { track } = useGinkoAnalytics();
21
+ const analytics = useGinkoAnalytics();
22
+ const enabled = config.feedback.enabled && analytics.enabled;
22
23
 
23
24
  type Sentiment = "positive" | "negative";
24
25
 
@@ -27,25 +28,25 @@ const sentiment = ref<Sentiment | null>(null);
27
28
  const issueUrl = computed(() => {
28
29
  const repository = config.repository;
29
30
  if (!repository) return null;
30
- const url = new URL(`${repository.url.replace(/\/$/, "")}/issues/new`);
31
- url.searchParams.set("title", t("feedback.issueTitle", { path: route.path }));
32
- url.searchParams.set("body", t("docs.issueBody", { path: route.path }));
33
- return url.toString();
31
+ return buildRepoIssueUrl(repository, {
32
+ title: t("feedback.issueTitle", { path: route.path }),
33
+ body: t("docs.issueBody", { path: route.path }),
34
+ });
34
35
  });
35
36
 
36
37
  function selectSentiment(s: Sentiment) {
37
38
  if (sentiment.value !== null) return;
38
- sentiment.value = s;
39
- track("docs-feedback", {
39
+ analytics.track("docs-feedback", {
40
40
  path: route.path,
41
41
  helpful: s === "positive" ? "yes" : "no",
42
42
  locale: locale.value,
43
43
  });
44
+ sentiment.value = s;
44
45
  }
45
46
  </script>
46
47
 
47
48
  <template>
48
- <div v-if="enabled" class="mt-14 border-y border-border py-5">
49
+ <div v-if="enabled">
49
50
  <Transition
50
51
  enter-active-class="transition-opacity duration-200"
51
52
  enter-from-class="opacity-0"
@@ -75,7 +76,7 @@ function selectSentiment(s: Sentiment) {
75
76
  <Button
76
77
  variant="outline"
77
78
  size="sm"
78
- class="h-9 w-20 rounded-full px-4 text-muted-foreground shadow-sm"
79
+ class="h-9 min-w-20 rounded-full px-4 text-muted-foreground shadow-sm"
79
80
  @click="selectSentiment('positive')"
80
81
  >
81
82
  <Icon name="lucide:thumbs-up" class="shrink-0" aria-hidden="true" />
@@ -85,7 +86,7 @@ function selectSentiment(s: Sentiment) {
85
86
  <Button
86
87
  variant="outline"
87
88
  size="sm"
88
- class="h-9 w-20 rounded-full px-4 text-muted-foreground shadow-sm"
89
+ class="h-9 min-w-20 rounded-full px-4 text-muted-foreground shadow-sm"
89
90
  @click="selectSentiment('negative')"
90
91
  >
91
92
  <Icon name="lucide:thumbs-down" class="shrink-0" aria-hidden="true" />
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { AnimatePresence, Motion } from "motion-v";
3
+ import { ref, useId } from "vue";
4
+ import {
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogPortal,
8
+ DialogRoot,
9
+ DialogTitle,
10
+ DialogTrigger,
11
+ } from "reka-ui";
12
+ import { useI18n } from "#imports";
13
+ import { useImageZoomMotion } from "./imageZoom";
14
+
15
+ defineProps<{
16
+ src?: string;
17
+ alt?: string;
18
+ /** Accessible dialog title; falls back to alt, then the zoom label. */
19
+ label?: string;
20
+ description?: string;
21
+ }>();
22
+
23
+ const { t } = useI18n();
24
+ const open = ref(false);
25
+ const layoutId = `ginko-image-${useId()}`;
26
+ const { imageTransition, fadeTransition } = useImageZoomMotion();
27
+ </script>
28
+
29
+ <template>
30
+ <DialogRoot v-model:open="open" :modal="false">
31
+ <DialogTrigger as-child>
32
+ <slot name="trigger" :layout-id="layoutId" :transition="imageTransition" />
33
+ </DialogTrigger>
34
+
35
+ <DialogPortal>
36
+ <AnimatePresence>
37
+ <Motion
38
+ v-if="open"
39
+ :initial="{ opacity: 0 }"
40
+ :animate="{ opacity: 1 }"
41
+ :exit="{ opacity: 0 }"
42
+ :transition="fadeTransition"
43
+ class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm will-change-opacity"
44
+ />
45
+
46
+ <DialogContent
47
+ v-if="open"
48
+ class="fixed inset-0 z-50 flex cursor-zoom-out flex-col items-center justify-center gap-4 p-[4vw] focus:outline-none"
49
+ @click="open = false"
50
+ >
51
+ <DialogTitle class="sr-only">{{ label || alt || t("docs.zoomImage") }}</DialogTitle>
52
+ <DialogDescription class="sr-only">{{
53
+ description || alt || t("docs.zoomImage")
54
+ }}</DialogDescription>
55
+
56
+ <Motion as-child :layout-id="layoutId" :transition="imageTransition">
57
+ <img
58
+ :src="src"
59
+ :alt="alt"
60
+ class="max-h-[82dvh] w-auto max-w-[min(94vw,80rem)] rounded-lg object-contain will-change-transform"
61
+ />
62
+ </Motion>
63
+
64
+ <Motion
65
+ v-if="label || alt"
66
+ as-child
67
+ :initial="{ opacity: 0, y: 8 }"
68
+ :animate="{ opacity: 1, y: 0 }"
69
+ :exit="{ opacity: 0, y: -4 }"
70
+ :transition="fadeTransition"
71
+ >
72
+ <p
73
+ aria-hidden="true"
74
+ class="max-w-[min(92vw,40rem)] rounded-full border border-border bg-background px-4 py-1.5 text-center text-sm shadow-sm"
75
+ >
76
+ {{ label || alt
77
+ }}<span v-if="description" class="text-muted-foreground"> · {{ description }}</span>
78
+ </p>
79
+ </Motion>
80
+ </DialogContent>
81
+ </AnimatePresence>
82
+ </DialogPortal>
83
+ </DialogRoot>
84
+ </template>
@@ -54,6 +54,8 @@ const statusMessage = computed(() => {
54
54
  </Button>
55
55
 
56
56
  <DropdownMenu>
57
+ <!-- No tooltip here: Tooltip is itself a popper root, so wrapping the
58
+ dropdown trigger steals its anchor and the menu renders off-screen. -->
57
59
  <DropdownMenuTrigger as-child>
58
60
  <Button
59
61
  type="button"
@@ -0,0 +1,25 @@
1
+ import { useReducedMotion } from "motion-v";
2
+ import { computed } from "vue";
3
+
4
+ export const imageZoomTransition = {
5
+ type: "spring",
6
+ bounce: 0,
7
+ duration: 0.42,
8
+ ease: "easeInOut",
9
+ } as const;
10
+
11
+ const imageZoomFadeTransition = { duration: 0.16, ease: "easeOut" } as const;
12
+ const noMotionTransition = { duration: 0 } as const;
13
+
14
+ export function useImageZoomMotion() {
15
+ const reducedMotion = useReducedMotion();
16
+
17
+ return {
18
+ imageTransition: computed(() =>
19
+ reducedMotion.value ? noMotionTransition : imageZoomTransition,
20
+ ),
21
+ fadeTransition: computed(() =>
22
+ reducedMotion.value ? noMotionTransition : imageZoomFadeTransition,
23
+ ),
24
+ };
25
+ }