@kirchdev/duxt 0.1.0

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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/app/app.config.ts +13 -0
  4. package/app/app.vue +122 -0
  5. package/app/assets/css/duxt.css +555 -0
  6. package/app/assets/css/typeset.css +498 -0
  7. package/app/components/DuxtBrand.vue +53 -0
  8. package/app/components/DuxtBreadcrumb.vue +37 -0
  9. package/app/components/DuxtCodeBlock.vue +143 -0
  10. package/app/components/DuxtCopyPage.vue +117 -0
  11. package/app/components/DuxtFooter.vue +86 -0
  12. package/app/components/DuxtHeader.vue +496 -0
  13. package/app/components/DuxtJsonEditor.vue +359 -0
  14. package/app/components/DuxtLandingProse.vue +90 -0
  15. package/app/components/DuxtLandingShowcase.vue +295 -0
  16. package/app/components/DuxtLiveWindow.vue +343 -0
  17. package/app/components/DuxtLocale.vue +86 -0
  18. package/app/components/DuxtMetaList.vue +76 -0
  19. package/app/components/DuxtNavigation.vue +94 -0
  20. package/app/components/DuxtNavigationLink.vue +41 -0
  21. package/app/components/DuxtOpenApiCallbacks.vue +92 -0
  22. package/app/components/DuxtOpenApiClient.vue +1339 -0
  23. package/app/components/DuxtOpenApiMedia.vue +108 -0
  24. package/app/components/DuxtOpenApiMethod.vue +59 -0
  25. package/app/components/DuxtOpenApiParameters.vue +114 -0
  26. package/app/components/DuxtOpenApiResponses.vue +109 -0
  27. package/app/components/DuxtOpenApiSchema.vue +365 -0
  28. package/app/components/DuxtOpenApiSecurity.vue +115 -0
  29. package/app/components/DuxtPageFeedback.vue +56 -0
  30. package/app/components/DuxtPageInfo.vue +194 -0
  31. package/app/components/DuxtPageNav.vue +93 -0
  32. package/app/components/DuxtPreviewSkeleton.vue +262 -0
  33. package/app/components/DuxtProgress.vue +45 -0
  34. package/app/components/DuxtSearch.vue +271 -0
  35. package/app/components/DuxtSections.vue +65 -0
  36. package/app/components/DuxtShortcuts.vue +46 -0
  37. package/app/components/DuxtSkipLink.vue +34 -0
  38. package/app/components/DuxtToc.vue +86 -0
  39. package/app/components/DuxtTranslationBanner.vue +64 -0
  40. package/app/components/DuxtVersion.vue +168 -0
  41. package/app/components/DuxtVersionBanner.vue +109 -0
  42. package/app/components/OgImage/Duxt.satori.vue +82 -0
  43. package/app/components/content/Accordion.vue +37 -0
  44. package/app/components/content/AccordionItem.vue +46 -0
  45. package/app/components/content/Callout.vue +58 -0
  46. package/app/components/content/ChangelogGroup.vue +91 -0
  47. package/app/components/content/ChangelogReleases.vue +520 -0
  48. package/app/components/content/CodeGroup.vue +183 -0
  49. package/app/components/content/DevtoolsPanel.vue +107 -0
  50. package/app/components/content/Field.vue +50 -0
  51. package/app/components/content/FieldGroup.vue +8 -0
  52. package/app/components/content/FileTree.vue +92 -0
  53. package/app/components/content/Mermaid.vue +70 -0
  54. package/app/components/content/OpenApiOperation.vue +185 -0
  55. package/app/components/content/OpenApiOperations.vue +72 -0
  56. package/app/components/content/OpenApiOverview.vue +302 -0
  57. package/app/components/content/PackageManagers.vue +187 -0
  58. package/app/components/content/PageCards.vue +48 -0
  59. package/app/components/content/Partial.vue +65 -0
  60. package/app/components/content/Preview.vue +173 -0
  61. package/app/components/content/ProseA.vue +55 -0
  62. package/app/components/content/ProseH2.vue +53 -0
  63. package/app/components/content/ProseH3.vue +53 -0
  64. package/app/components/content/ProseH4.vue +53 -0
  65. package/app/components/content/ProseImg.vue +87 -0
  66. package/app/components/content/ProsePre.vue +58 -0
  67. package/app/components/content/ProseTable.vue +11 -0
  68. package/app/components/content/Since.vue +21 -0
  69. package/app/components/content/Steps.vue +20 -0
  70. package/app/components/content/Tab.vue +23 -0
  71. package/app/components/content/TabGroup.vue +58 -0
  72. package/app/components/ui/alert/Alert.vue +21 -0
  73. package/app/components/ui/alert/AlertDescription.vue +22 -0
  74. package/app/components/ui/alert/AlertTitle.vue +22 -0
  75. package/app/components/ui/alert/index.ts +24 -0
  76. package/app/components/ui/badge/Badge.vue +28 -0
  77. package/app/components/ui/badge/index.ts +52 -0
  78. package/app/components/ui/breadcrumb/Breadcrumb.vue +13 -0
  79. package/app/components/ui/breadcrumb/BreadcrumbEllipsis.vue +22 -0
  80. package/app/components/ui/breadcrumb/BreadcrumbItem.vue +17 -0
  81. package/app/components/ui/breadcrumb/BreadcrumbLink.vue +24 -0
  82. package/app/components/ui/breadcrumb/BreadcrumbList.vue +22 -0
  83. package/app/components/ui/breadcrumb/BreadcrumbPage.vue +20 -0
  84. package/app/components/ui/breadcrumb/BreadcrumbSeparator.vue +21 -0
  85. package/app/components/ui/breadcrumb/index.ts +7 -0
  86. package/app/components/ui/button/Button.vue +31 -0
  87. package/app/components/ui/button/index.ts +39 -0
  88. package/app/components/ui/card/Card.vue +22 -0
  89. package/app/components/ui/card/CardAction.vue +22 -0
  90. package/app/components/ui/card/CardContent.vue +14 -0
  91. package/app/components/ui/card/CardDescription.vue +17 -0
  92. package/app/components/ui/card/CardFooter.vue +17 -0
  93. package/app/components/ui/card/CardHeader.vue +22 -0
  94. package/app/components/ui/card/CardTitle.vue +17 -0
  95. package/app/components/ui/card/index.ts +7 -0
  96. package/app/components/ui/collapsible/Collapsible.vue +19 -0
  97. package/app/components/ui/collapsible/CollapsibleContent.vue +12 -0
  98. package/app/components/ui/collapsible/CollapsibleTrigger.vue +12 -0
  99. package/app/components/ui/collapsible/index.ts +3 -0
  100. package/app/components/ui/command/Command.vue +98 -0
  101. package/app/components/ui/command/CommandDialog.vue +42 -0
  102. package/app/components/ui/command/CommandEmpty.vue +31 -0
  103. package/app/components/ui/command/CommandGroup.vue +52 -0
  104. package/app/components/ui/command/CommandInput.vue +45 -0
  105. package/app/components/ui/command/CommandItem.vue +85 -0
  106. package/app/components/ui/command/CommandList.vue +32 -0
  107. package/app/components/ui/command/CommandSeparator.vue +23 -0
  108. package/app/components/ui/command/CommandShortcut.vue +19 -0
  109. package/app/components/ui/command/index.ts +29 -0
  110. package/app/components/ui/dialog/Dialog.vue +15 -0
  111. package/app/components/ui/dialog/DialogClose.vue +12 -0
  112. package/app/components/ui/dialog/DialogContent.vue +61 -0
  113. package/app/components/ui/dialog/DialogDescription.vue +25 -0
  114. package/app/components/ui/dialog/DialogFooter.vue +30 -0
  115. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  116. package/app/components/ui/dialog/DialogOverlay.vue +28 -0
  117. package/app/components/ui/dialog/DialogScrollContent.vue +65 -0
  118. package/app/components/ui/dialog/DialogTitle.vue +25 -0
  119. package/app/components/ui/dialog/DialogTrigger.vue +12 -0
  120. package/app/components/ui/dialog/index.ts +10 -0
  121. package/app/components/ui/dropdown-menu/DropdownMenu.vue +19 -0
  122. package/app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue +47 -0
  123. package/app/components/ui/dropdown-menu/DropdownMenuContent.vue +47 -0
  124. package/app/components/ui/dropdown-menu/DropdownMenuGroup.vue +12 -0
  125. package/app/components/ui/dropdown-menu/DropdownMenuItem.vue +41 -0
  126. package/app/components/ui/dropdown-menu/DropdownMenuLabel.vue +27 -0
  127. package/app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue +21 -0
  128. package/app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue +48 -0
  129. package/app/components/ui/dropdown-menu/DropdownMenuSeparator.vue +23 -0
  130. package/app/components/ui/dropdown-menu/DropdownMenuShortcut.vue +19 -0
  131. package/app/components/ui/dropdown-menu/DropdownMenuSub.vue +19 -0
  132. package/app/components/ui/dropdown-menu/DropdownMenuSubContent.vue +34 -0
  133. package/app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue +34 -0
  134. package/app/components/ui/dropdown-menu/DropdownMenuTrigger.vue +17 -0
  135. package/app/components/ui/dropdown-menu/index.ts +16 -0
  136. package/app/components/ui/input/Input.vue +35 -0
  137. package/app/components/ui/input/index.ts +1 -0
  138. package/app/components/ui/navigation-menu/NavigationMenu.vue +42 -0
  139. package/app/components/ui/navigation-menu/NavigationMenuContent.vue +35 -0
  140. package/app/components/ui/navigation-menu/NavigationMenuIndicator.vue +32 -0
  141. package/app/components/ui/navigation-menu/NavigationMenuItem.vue +23 -0
  142. package/app/components/ui/navigation-menu/NavigationMenuLink.vue +30 -0
  143. package/app/components/ui/navigation-menu/NavigationMenuList.vue +30 -0
  144. package/app/components/ui/navigation-menu/NavigationMenuTrigger.vue +31 -0
  145. package/app/components/ui/navigation-menu/NavigationMenuViewport.vue +30 -0
  146. package/app/components/ui/navigation-menu/index.ts +14 -0
  147. package/app/components/ui/scroll-area/ScrollArea.vue +31 -0
  148. package/app/components/ui/scroll-area/ScrollBar.vue +38 -0
  149. package/app/components/ui/scroll-area/index.ts +2 -0
  150. package/app/components/ui/select/Select.vue +15 -0
  151. package/app/components/ui/select/SelectContent.vue +59 -0
  152. package/app/components/ui/select/SelectGroup.vue +19 -0
  153. package/app/components/ui/select/SelectItem.vue +42 -0
  154. package/app/components/ui/select/SelectItemText.vue +12 -0
  155. package/app/components/ui/select/SelectLabel.vue +16 -0
  156. package/app/components/ui/select/SelectScrollDownButton.vue +28 -0
  157. package/app/components/ui/select/SelectScrollUpButton.vue +28 -0
  158. package/app/components/ui/select/SelectSeparator.vue +20 -0
  159. package/app/components/ui/select/SelectTrigger.vue +32 -0
  160. package/app/components/ui/select/SelectValue.vue +12 -0
  161. package/app/components/ui/select/index.ts +11 -0
  162. package/app/components/ui/separator/Separator.vue +30 -0
  163. package/app/components/ui/separator/index.ts +1 -0
  164. package/app/components/ui/sheet/Sheet.vue +15 -0
  165. package/app/components/ui/sheet/SheetClose.vue +12 -0
  166. package/app/components/ui/sheet/SheetContent.vue +68 -0
  167. package/app/components/ui/sheet/SheetDescription.vue +23 -0
  168. package/app/components/ui/sheet/SheetFooter.vue +15 -0
  169. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  170. package/app/components/ui/sheet/SheetOverlay.vue +28 -0
  171. package/app/components/ui/sheet/SheetTitle.vue +23 -0
  172. package/app/components/ui/sheet/SheetTrigger.vue +12 -0
  173. package/app/components/ui/sheet/index.ts +8 -0
  174. package/app/components/ui/sidebar/Sidebar.vue +110 -0
  175. package/app/components/ui/sidebar/SidebarContent.vue +23 -0
  176. package/app/components/ui/sidebar/SidebarFooter.vue +18 -0
  177. package/app/components/ui/sidebar/SidebarGroup.vue +18 -0
  178. package/app/components/ui/sidebar/SidebarGroupAction.vue +31 -0
  179. package/app/components/ui/sidebar/SidebarGroupContent.vue +18 -0
  180. package/app/components/ui/sidebar/SidebarGroupLabel.vue +30 -0
  181. package/app/components/ui/sidebar/SidebarHeader.vue +18 -0
  182. package/app/components/ui/sidebar/SidebarInput.vue +19 -0
  183. package/app/components/ui/sidebar/SidebarInset.vue +23 -0
  184. package/app/components/ui/sidebar/SidebarMenu.vue +18 -0
  185. package/app/components/ui/sidebar/SidebarMenuAction.vue +42 -0
  186. package/app/components/ui/sidebar/SidebarMenuBadge.vue +28 -0
  187. package/app/components/ui/sidebar/SidebarMenuButton.vue +60 -0
  188. package/app/components/ui/sidebar/SidebarMenuButtonChild.vue +36 -0
  189. package/app/components/ui/sidebar/SidebarMenuItem.vue +18 -0
  190. package/app/components/ui/sidebar/SidebarMenuSkeleton.vue +35 -0
  191. package/app/components/ui/sidebar/SidebarMenuSub.vue +24 -0
  192. package/app/components/ui/sidebar/SidebarMenuSubButton.vue +43 -0
  193. package/app/components/ui/sidebar/SidebarMenuSubItem.vue +18 -0
  194. package/app/components/ui/sidebar/SidebarProvider.vue +109 -0
  195. package/app/components/ui/sidebar/SidebarRail.vue +35 -0
  196. package/app/components/ui/sidebar/SidebarSeparator.vue +19 -0
  197. package/app/components/ui/sidebar/SidebarTrigger.vue +26 -0
  198. package/app/components/ui/sidebar/index.ts +62 -0
  199. package/app/components/ui/sidebar/utils.ts +19 -0
  200. package/app/components/ui/skeleton/Skeleton.vue +17 -0
  201. package/app/components/ui/skeleton/index.ts +1 -0
  202. package/app/components/ui/sonner/Sonner.vue +67 -0
  203. package/app/components/ui/sonner/index.ts +1 -0
  204. package/app/components/ui/tabs/Tabs.vue +26 -0
  205. package/app/components/ui/tabs/TabsContent.vue +23 -0
  206. package/app/components/ui/tabs/TabsList.vue +28 -0
  207. package/app/components/ui/tabs/TabsTrigger.vue +30 -0
  208. package/app/components/ui/tabs/index.ts +4 -0
  209. package/app/components/ui/textarea/Textarea.vue +32 -0
  210. package/app/components/ui/textarea/index.ts +1 -0
  211. package/app/components/ui/tooltip/Tooltip.vue +15 -0
  212. package/app/components/ui/tooltip/TooltipContent.vue +49 -0
  213. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  214. package/app/components/ui/tooltip/TooltipTrigger.vue +12 -0
  215. package/app/components/ui/tooltip/index.ts +4 -0
  216. package/app/composables/useActiveHeading.ts +90 -0
  217. package/app/composables/useDuxtAnimatedHeight.ts +155 -0
  218. package/app/composables/useDuxtBreadcrumb.ts +60 -0
  219. package/app/composables/useDuxtChoice.ts +28 -0
  220. package/app/composables/useDuxtCollection.ts +89 -0
  221. package/app/composables/useDuxtConfig.ts +47 -0
  222. package/app/composables/useDuxtNavigation.ts +87 -0
  223. package/app/composables/useDuxtPageFocus.ts +30 -0
  224. package/app/composables/useDuxtPath.ts +42 -0
  225. package/app/composables/useDuxtSearch.ts +208 -0
  226. package/app/composables/useDuxtSection.ts +44 -0
  227. package/app/composables/useDuxtSectionRow.ts +49 -0
  228. package/app/composables/useDuxtShortcuts.ts +58 -0
  229. package/app/composables/useDuxtSiteUrl.ts +27 -0
  230. package/app/composables/useDuxtToast.ts +25 -0
  231. package/app/composables/useDuxtVersion.ts +138 -0
  232. package/app/composables/useFuzzySearch.ts +83 -0
  233. package/app/composables/usePackageManager.ts +4 -0
  234. package/app/composables/useRecentPages.ts +52 -0
  235. package/app/error.vue +133 -0
  236. package/app/layouts/changelog.vue +85 -0
  237. package/app/layouts/default.vue +23 -0
  238. package/app/layouts/docs.vue +62 -0
  239. package/app/layouts/reference.vue +85 -0
  240. package/app/lib/utils.ts +7 -0
  241. package/app/middleware/duxt-section-layout.global.ts +57 -0
  242. package/app/pages/[...slug].vue +441 -0
  243. package/app/pages/index.vue +479 -0
  244. package/app/types/duxt.d.ts +955 -0
  245. package/app/utils/aside.ts +16 -0
  246. package/app/utils/changelog.ts +182 -0
  247. package/app/utils/code-highlight.ts +116 -0
  248. package/app/utils/duxt-config.ts +185 -0
  249. package/app/utils/duxt-text.ts +135 -0
  250. package/app/utils/file-icons.ts +62 -0
  251. package/app/utils/generated-sections.ts +190 -0
  252. package/app/utils/generated-toc.ts +216 -0
  253. package/app/utils/heading-link.ts +32 -0
  254. package/app/utils/locale-path.ts +45 -0
  255. package/app/utils/navigation-tree.ts +206 -0
  256. package/app/utils/nearest-page.ts +72 -0
  257. package/app/utils/openapi.ts +1028 -0
  258. package/app/utils/package-command.ts +162 -0
  259. package/app/utils/request-samples.ts +635 -0
  260. package/app/utils/section-scope.ts +97 -0
  261. package/app/utils/sheet-navigation.ts +100 -0
  262. package/app/utils/source-file.ts +84 -0
  263. package/app/utils/version-choices.ts +68 -0
  264. package/app/utils/version-paths.ts +65 -0
  265. package/bin/duxt-report.mjs +45 -0
  266. package/components.json +19 -0
  267. package/content-cache.ts +193 -0
  268. package/content.config.ts +38 -0
  269. package/duxt-app-config.ts +124 -0
  270. package/highlight-langs.ts +68 -0
  271. package/i18n/i18n.config.ts +16 -0
  272. package/i18n/locales/de/duxt/changelog.json +17 -0
  273. package/i18n/locales/de/duxt/code.json +13 -0
  274. package/i18n/locales/de/duxt/defaults.json +22 -0
  275. package/i18n/locales/de/duxt/devtools.json +7 -0
  276. package/i18n/locales/de/duxt/error.json +10 -0
  277. package/i18n/locales/de/duxt/footer.json +7 -0
  278. package/i18n/locales/de/duxt/locale.json +7 -0
  279. package/i18n/locales/de/duxt/nav.json +17 -0
  280. package/i18n/locales/de/duxt/openapi.json +89 -0
  281. package/i18n/locales/de/duxt/page.json +34 -0
  282. package/i18n/locales/de/duxt/search.json +12 -0
  283. package/i18n/locales/de/duxt/shortcuts.json +11 -0
  284. package/i18n/locales/de/duxt/theme.json +7 -0
  285. package/i18n/locales/de/duxt/toc.json +7 -0
  286. package/i18n/locales/de/duxt/version.json +25 -0
  287. package/i18n/locales/en/duxt/changelog.json +17 -0
  288. package/i18n/locales/en/duxt/code.json +13 -0
  289. package/i18n/locales/en/duxt/defaults.json +22 -0
  290. package/i18n/locales/en/duxt/devtools.json +7 -0
  291. package/i18n/locales/en/duxt/error.json +10 -0
  292. package/i18n/locales/en/duxt/footer.json +7 -0
  293. package/i18n/locales/en/duxt/locale.json +7 -0
  294. package/i18n/locales/en/duxt/nav.json +17 -0
  295. package/i18n/locales/en/duxt/openapi.json +89 -0
  296. package/i18n/locales/en/duxt/page.json +34 -0
  297. package/i18n/locales/en/duxt/search.json +12 -0
  298. package/i18n/locales/en/duxt/shortcuts.json +11 -0
  299. package/i18n/locales/en/duxt/theme.json +7 -0
  300. package/i18n/locales/en/duxt/toc.json +7 -0
  301. package/i18n/locales/en/duxt/version.json +25 -0
  302. package/i18n/locales/es/duxt/changelog.json +17 -0
  303. package/i18n/locales/es/duxt/code.json +13 -0
  304. package/i18n/locales/es/duxt/defaults.json +22 -0
  305. package/i18n/locales/es/duxt/devtools.json +7 -0
  306. package/i18n/locales/es/duxt/error.json +10 -0
  307. package/i18n/locales/es/duxt/footer.json +7 -0
  308. package/i18n/locales/es/duxt/locale.json +7 -0
  309. package/i18n/locales/es/duxt/nav.json +17 -0
  310. package/i18n/locales/es/duxt/openapi.json +89 -0
  311. package/i18n/locales/es/duxt/page.json +34 -0
  312. package/i18n/locales/es/duxt/search.json +12 -0
  313. package/i18n/locales/es/duxt/shortcuts.json +11 -0
  314. package/i18n/locales/es/duxt/theme.json +7 -0
  315. package/i18n/locales/es/duxt/toc.json +7 -0
  316. package/i18n/locales/es/duxt/version.json +25 -0
  317. package/i18n/locales/fr/duxt/changelog.json +17 -0
  318. package/i18n/locales/fr/duxt/code.json +13 -0
  319. package/i18n/locales/fr/duxt/defaults.json +22 -0
  320. package/i18n/locales/fr/duxt/devtools.json +7 -0
  321. package/i18n/locales/fr/duxt/error.json +10 -0
  322. package/i18n/locales/fr/duxt/footer.json +7 -0
  323. package/i18n/locales/fr/duxt/locale.json +7 -0
  324. package/i18n/locales/fr/duxt/nav.json +17 -0
  325. package/i18n/locales/fr/duxt/openapi.json +89 -0
  326. package/i18n/locales/fr/duxt/page.json +34 -0
  327. package/i18n/locales/fr/duxt/search.json +12 -0
  328. package/i18n/locales/fr/duxt/shortcuts.json +11 -0
  329. package/i18n/locales/fr/duxt/theme.json +7 -0
  330. package/i18n/locales/fr/duxt/toc.json +7 -0
  331. package/i18n/locales/fr/duxt/version.json +25 -0
  332. package/i18n/locales/pt/duxt/changelog.json +17 -0
  333. package/i18n/locales/pt/duxt/code.json +13 -0
  334. package/i18n/locales/pt/duxt/defaults.json +22 -0
  335. package/i18n/locales/pt/duxt/devtools.json +7 -0
  336. package/i18n/locales/pt/duxt/error.json +10 -0
  337. package/i18n/locales/pt/duxt/footer.json +7 -0
  338. package/i18n/locales/pt/duxt/locale.json +7 -0
  339. package/i18n/locales/pt/duxt/nav.json +17 -0
  340. package/i18n/locales/pt/duxt/openapi.json +89 -0
  341. package/i18n/locales/pt/duxt/page.json +34 -0
  342. package/i18n/locales/pt/duxt/search.json +12 -0
  343. package/i18n/locales/pt/duxt/shortcuts.json +11 -0
  344. package/i18n/locales/pt/duxt/theme.json +7 -0
  345. package/i18n/locales/pt/duxt/toc.json +7 -0
  346. package/i18n/locales/pt/duxt/version.json +25 -0
  347. package/i18n/locales/pt-BR/duxt/error.json +8 -0
  348. package/i18n/locales/pt-BR/duxt/nav.json +10 -0
  349. package/i18n/locales/pt-BR/duxt/page.json +10 -0
  350. package/i18n/locales/pt-BR/duxt/search.json +11 -0
  351. package/i18n/locales/pt-BR/duxt/version.json +13 -0
  352. package/mdc.config.ts +28 -0
  353. package/modules/config.ts +440 -0
  354. package/modules/devtools.ts +126 -0
  355. package/modules/git-meta.ts +349 -0
  356. package/modules/redirects.ts +152 -0
  357. package/modules/validate.ts +137 -0
  358. package/nuxt.config.ts +530 -0
  359. package/openapi-model.ts +358 -0
  360. package/openapi-parse.ts +1052 -0
  361. package/package.json +161 -0
  362. package/public/devtools/cache.html +318 -0
  363. package/public/devtools/checks.html +309 -0
  364. package/public/devtools/config.html +461 -0
  365. package/public/devtools/i18n.html +309 -0
  366. package/public/devtools/pages.html +348 -0
  367. package/public/devtools/paths.html +315 -0
  368. package/public/devtools/redirects.html +309 -0
  369. package/public/devtools/search.html +324 -0
  370. package/public/devtools/sources.html +309 -0
  371. package/public/devtools/versions.html +318 -0
  372. package/report.ts +303 -0
  373. package/repository-root.ts +26 -0
  374. package/section-reports.ts +55 -0
  375. package/sections-changelog.ts +644 -0
  376. package/sections-openapi.ts +450 -0
  377. package/sections-resolve.ts +711 -0
  378. package/sections.ts +157 -0
  379. package/server/devtools/content.ts +120 -0
  380. package/server/devtools/context.ts +38 -0
  381. package/server/devtools/entry-path.ts +30 -0
  382. package/server/devtools/handler.ts +104 -0
  383. package/server/devtools/preview.ts +461 -0
  384. package/server/devtools/render/content.ts +233 -0
  385. package/server/devtools/render/sources.ts +414 -0
  386. package/server/devtools/render/system.ts +352 -0
  387. package/server/devtools/shell.ts +611 -0
  388. package/server/devtools/sources.ts +122 -0
  389. package/server/devtools/system.ts +178 -0
  390. package/server/mcp/tools/list-pages.ts +52 -0
  391. package/server/mcp/tools/read-page.ts +57 -0
  392. package/server/mcp/tools/search-docs.ts +74 -0
  393. package/server/middleware/raw-markdown.ts +86 -0
  394. package/server/routes/llms-full.txt.get.ts +75 -0
  395. package/server/routes/llms.txt.get.ts +63 -0
  396. package/server/routes/rss.xml.get.ts +125 -0
  397. package/server/utils/duxt-server-text.ts +46 -0
  398. package/sources-git.ts +113 -0
  399. package/sources-resolve.ts +908 -0
  400. package/sources.ts +287 -0
  401. package/validate-report.ts +494 -0
@@ -0,0 +1,520 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * The changelog overview: every release, newest first, filterable by what
4
+ * changed.
5
+ *
6
+ * MDC: `::changelog-releases` on the section's index page, written by
7
+ * `sections-changelog.ts`. What it draws is a TIMELINE rather than a bullet
8
+ * list of links — the two questions a reader arrives at a changelog with are
9
+ * "what is the latest" and "when did X change", and a date and the kinds of
10
+ * change beside each version answer both without opening anything.
11
+ *
12
+ * THE ENTRIES ARE NOT HERE. Each release has a page of its own — that is what
13
+ * the split granularity buys — and repeating its bullets here would put the
14
+ * whole history twice into the search index, `llms-full.txt` and the feed.
15
+ *
16
+ * The FILTER is what the grouping is for. On a project with two hundred
17
+ * releases "show me the ones with breaking changes" is the question, and the
18
+ * chips are built from the names the file itself used, so a changelog written
19
+ * in German filters in German with nothing configured.
20
+ *
21
+ * The whole entry is the link, not the version alone: a version number is a
22
+ * four-character target, and everything beside it — the date, the kinds of
23
+ * change — describes the page that link goes to.
24
+ */
25
+ const props = withDefaults(
26
+ defineProps<{
27
+ releases?: {
28
+ version: string;
29
+ date?: string;
30
+ to: string;
31
+ groups?: { name: string; count: number }[];
32
+ }[];
33
+ }>(),
34
+ { releases: () => [] }
35
+ );
36
+
37
+ const { locale } = useI18n();
38
+ const localeLink = useDuxtLink();
39
+
40
+ /** Every group name the file used, in the order it first used it. */
41
+ const names = computed(() => {
42
+ const seen: string[] = [];
43
+
44
+ for (const release of props.releases) {
45
+ for (const group of release.groups ?? []) {
46
+ if (!seen.includes(group.name)) seen.push(group.name);
47
+ }
48
+ }
49
+
50
+ return seen;
51
+ });
52
+
53
+ /** How many releases carry each name — what the chip counts. */
54
+ const totals = computed(() => {
55
+ const counts: Record<string, number> = {};
56
+
57
+ for (const release of props.releases) {
58
+ for (const name of new Set((release.groups ?? []).map((g) => g.name))) {
59
+ counts[name] = (counts[name] ?? 0) + 1;
60
+ }
61
+ }
62
+
63
+ return counts;
64
+ });
65
+
66
+ /**
67
+ * Nothing selected is EVERYTHING, not nothing.
68
+ *
69
+ * It is also what the server renders: a filter is a reader's choice, so the
70
+ * page has to be complete before anyone makes one — otherwise the crawler, the
71
+ * reader with no JavaScript and the reader whose hydration has not landed yet
72
+ * are each shown a filtered changelog nobody asked for.
73
+ */
74
+ const selected = ref<string[]>([]);
75
+
76
+ const shown = computed(() =>
77
+ selected.value.length
78
+ ? props.releases.filter((release) =>
79
+ (release.groups ?? []).some((group) =>
80
+ selected.value.includes(group.name)
81
+ )
82
+ )
83
+ : props.releases
84
+ );
85
+
86
+ function toggle(name: string) {
87
+ selected.value = selected.value.includes(name)
88
+ ? selected.value.filter((entry) => entry !== name)
89
+ : [...selected.value, name];
90
+ }
91
+
92
+ /**
93
+ * The release day, in the reader's language — shared with the meta row a
94
+ * release page draws, so one release cannot be dated two different ways.
95
+ */
96
+ const formatted = (value?: string) => changelogDate(value, locale.value);
97
+ /** How many groups a row names before it starts counting the rest. */
98
+ const SHOWN_GROUPS = 3;
99
+
100
+ /**
101
+ * The whole history in two numbers, over the list rather than in it.
102
+ *
103
+ * The same summary the landing page opens with — a figure set large with a
104
+ * small labelled line under it — because it answers the question the list
105
+ * cannot: how much there IS. A list of six rows is read as six rows whatever
106
+ * they carry, and the sum of what those releases changed is nowhere on the page
107
+ * otherwise.
108
+ *
109
+ * IT COUNTS WHAT IS SHOWN, not what exists, so a filtered page's figures are
110
+ * about the filtered page — with the total kept beside the first one, which is
111
+ * also where the heading's own count used to sit.
112
+ */
113
+ const stats = computed(() => ({
114
+ releases: shown.value.length,
115
+ total: props.releases.length,
116
+ changes: shown.value.reduce(
117
+ (all, release) =>
118
+ all + (release.groups ?? []).reduce((sum, group) => sum + group.count, 0),
119
+ 0
120
+ ),
121
+ // What the project actually does, as opposed to how much of it: a history of
122
+ // forty releases that only ever fixes bugs says something a change count
123
+ // cannot. Counted over the names the file used, so it is the file's own
124
+ // vocabulary being counted and not a taxonomy of ours.
125
+ kinds: new Set(
126
+ shown.value.flatMap((release) =>
127
+ (release.groups ?? []).map((group) => group.name)
128
+ )
129
+ ).size,
130
+ // The last day something shipped — the one figure here that is not a count,
131
+ // and the one a reader checks first on a project they are considering.
132
+ last: formatted(shown.value[0]?.date)
133
+ }));
134
+
135
+ /** The newest release the file lists — marked wherever the filter puts it. */
136
+ const latest = (release: { to: string }) =>
137
+ release.to === props.releases[0]?.to;
138
+
139
+ /**
140
+ * The rows as they are drawn: a release, the groups it NAMES, and how many it
141
+ * leaves.
142
+ *
143
+ * The whole list was the row before this, and on a release that touched
144
+ * everything that was twelve names over two lines — twelve facts on a page
145
+ * whose job is to tell six releases apart. A row that grows with the release it
146
+ * describes also stops the list comparing them: the one release with every
147
+ * section becomes the page.
148
+ *
149
+ * So three, BY COUNT rather than by the order the file wrote them: the
150
+ * shallowest reading of "what was this release" is what it did most of, and
151
+ * release-please's own section order is fixed and means nothing. What is left
152
+ * is counted, never dropped silently.
153
+ *
154
+ * A FILTERED row names what it was filtered by first. Otherwise selecting
155
+ * "Tests" leaves a release matching on a group its row does not mention, which
156
+ * reads as a filter that let the wrong page through.
157
+ */
158
+ const rows = computed(() =>
159
+ shown.value.map((release) => {
160
+ const groups = [...(release.groups ?? [])].sort((a, b) => {
161
+ const chosen =
162
+ Number(selected.value.includes(b.name)) -
163
+ Number(selected.value.includes(a.name));
164
+
165
+ return chosen || b.count - a.count;
166
+ });
167
+
168
+ return {
169
+ release,
170
+ named: groups.slice(0, SHOWN_GROUPS),
171
+ rest: Math.max(0, groups.length - SHOWN_GROUPS)
172
+ };
173
+ })
174
+ );
175
+ </script>
176
+
177
+ <template>
178
+ <div class="not-typeset">
179
+ <!-- The list gets a heading of its own, and the control sits on the same
180
+ line at the other end. Two things follow from that: the timeline stops
181
+ being a slab of versions hanging off the preamble with no name, and the
182
+ filter reads as what acts on the list rather than as something wedged
183
+ between the prose and it.
184
+
185
+ WHAT WAS CHOSEN GOES ON ITS OWN ROW, under the control and above the
186
+ versions. Beside the control it moved the control: every chip added or
187
+ dropped re-laid the row it shared, so the button a reader was aiming at
188
+ slid out from under the pointer between two clicks. On a row of its own
189
+ the header is fixed and only the row that is actually changing moves. -->
190
+ <div class="mt-12 mb-8 space-y-3">
191
+ <div class="flex flex-wrap items-center justify-between gap-x-4 gap-y-3">
192
+ <h2 class="text-lg font-semibold tracking-tight">
193
+ {{ $t('duxt.changelog.history') }}
194
+ </h2>
195
+
196
+ <!-- One kind of change is no choice: the control would offer a single
197
+ item that hides everything else.
198
+
199
+ A MENU rather than a row of chips, and the reason is the fixture: a
200
+ release-please changelog writes twelve section names, and twelve
201
+ coloured chips wrapped over three rows read as noise sitting
202
+ between the prose and the history.
203
+
204
+ THE MENU KEEPS ITS DOTS while the history below sets the name in
205
+ the tone instead, and the split is the rule rather than an
206
+ oversight: a menu item is a control, where a mark beside a label is
207
+ what makes the row hittable and scannable, and the history is
208
+ reading matter, where a mark on every line of every release is
209
+ noise. Same six colours either way. -->
210
+ <div
211
+ v-if="names.length > 1"
212
+ role="group"
213
+ :aria-label="$t('duxt.changelog.filter')"
214
+ >
215
+ <UiDropdownMenu>
216
+ <UiDropdownMenuTrigger as-child>
217
+ <button
218
+ type="button"
219
+ class="inline-flex cursor-pointer items-center gap-2 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
220
+ >
221
+ <Icon name="lucide:list-filter" class="size-3.5 opacity-60" />
222
+ {{ $t('duxt.changelog.kind') }}
223
+ <span v-if="selected.length" class="tabular-nums text-primary">
224
+ {{ selected.length }}
225
+ </span>
226
+ <Icon name="lucide:chevron-down" class="size-3.5 opacity-60" />
227
+ </button>
228
+ </UiDropdownMenuTrigger>
229
+
230
+ <!-- Capped for the same reason the version menu is: a project with
231
+ twelve kinds is not the ceiling, and reka only stops the list
232
+ at the viewport edge. `end`, because the trigger sits at the
233
+ right edge of the reading column. -->
234
+ <UiDropdownMenuContent
235
+ align="end"
236
+ class="max-h-[min(24rem,var(--reka-dropdown-menu-content-available-height))] w-64 overflow-y-auto"
237
+ >
238
+ <UiDropdownMenuCheckboxItem
239
+ v-for="name in names"
240
+ :key="name"
241
+ :model-value="selected.includes(name)"
242
+ @select="(event: Event) => event.preventDefault()"
243
+ @update:model-value="toggle(name)"
244
+ >
245
+ <span
246
+ aria-hidden="true"
247
+ class="size-1.5 shrink-0 rounded-full"
248
+ :class="changelogTone(name).dot"
249
+ />
250
+ <span class="truncate">{{ changelogLabel(name) }}</span>
251
+ <span
252
+ class="ml-auto pl-2 text-xs text-muted-foreground tabular-nums"
253
+ >
254
+ {{ totals[name] }}
255
+ </span>
256
+ </UiDropdownMenuCheckboxItem>
257
+ </UiDropdownMenuContent>
258
+ </UiDropdownMenu>
259
+ </div>
260
+ </div>
261
+
262
+ <!-- Each chip removes its own kind, because "which one did I turn on" is
263
+ answered by the same control that turns it off.
264
+
265
+ ANIMATED FOR THE SAME REASON THE LIST IS: a chip appearing is the
266
+ visible half of a filter being set, and the list below moves at the
267
+ same moment — the two reading as one gesture is the whole point. Same
268
+ timings, same reduced-motion behaviour.
269
+
270
+ NEITHER CHIP CARRIES `transition-colors`, and that is what makes the
271
+ leave visible at all: two transition utilities on one element are two
272
+ declarations of the same property, and which one wins is the order
273
+ Tailwind emitted them in — not the order they are written here. With
274
+ `transition-colors` on the chip the leave ran its 150ms on the colour
275
+ and left opacity and transform untouched, so the chip simply
276
+ vanished. The hover colour is instant now, which on a chip this size
277
+ is not a loss.
278
+
279
+ The row is ALWAYS RENDERED and hidden while empty (`empty:hidden`),
280
+ rather than held behind a `v-if` that would take the leaving chips
281
+ with it before they could leave. During a leave it is no longer
282
+ empty, so it stays laid out until the last chip is gone. -->
283
+ <TransitionGroup
284
+ tag="div"
285
+ role="group"
286
+ :aria-label="$t('duxt.changelog.selected')"
287
+ class="flex flex-wrap items-center gap-2 empty:hidden"
288
+ enter-active-class="transition duration-200 ease-out motion-reduce:transition-none"
289
+ enter-from-class="scale-95 opacity-0"
290
+ leave-active-class="transition duration-150 ease-in motion-reduce:transition-none"
291
+ leave-to-class="scale-95 opacity-0"
292
+ move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
293
+ >
294
+ <button
295
+ v-for="name in selected"
296
+ :key="name"
297
+ type="button"
298
+ class="inline-flex cursor-pointer items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium ring-1 ring-inset focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
299
+ :class="changelogTone(name).chip"
300
+ @click="toggle(name)"
301
+ >
302
+ <span
303
+ aria-hidden="true"
304
+ class="size-1.5 rounded-full"
305
+ :class="changelogTone(name).dot"
306
+ />
307
+ {{ changelogLabel(name) }}
308
+ <Icon name="lucide:x" class="size-3 opacity-60" />
309
+ </button>
310
+
311
+ <button
312
+ v-if="selected.length"
313
+ key="reset"
314
+ type="button"
315
+ class="cursor-pointer text-xs text-muted-foreground underline-offset-4 hover:text-foreground hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
316
+ @click="selected = []"
317
+ >
318
+ {{ $t('duxt.changelog.reset') }}
319
+ </button>
320
+ </TransitionGroup>
321
+ </div>
322
+
323
+ <!-- WHAT THE LIST CANNOT SAY: how much there is. Two figures set the way
324
+ the landing page sets its own — a number, and a small labelled line
325
+ under it — because six rows are read as six rows whatever they carry.
326
+
327
+ They count what is SHOWN. The total stays beside the first one, which
328
+ is where the heading's own count used to sit.
329
+
330
+ A GRID, NOT A WRAPPING ROW, and it is the one place on this page where
331
+ the column count is written down: four figures over the full width read
332
+ as the header of the list under them, where the same four bunched at
333
+ the left edge read as a caption with a hole beside it. The landing's
334
+ own row wraps and centres instead, for a number of figures a SITE
335
+ chooses; here the four are the layer's and fixed.
336
+
337
+ THE LABEL CARRIES THE ACCENT, the figure does not: `--primary` is the
338
+ one colour a consuming site sets to make the theme its own, and two
339
+ short labelled lines are where it costs nothing and reads as the
340
+ site's. The numbers stay in the text colour — they are read, not
341
+ branded. -->
342
+ <dl
343
+ class="mb-1 grid grid-cols-2 gap-x-6 gap-y-7 border-b pb-5 sm:grid-cols-4"
344
+ >
345
+ <div class="flex flex-col gap-1">
346
+ <dd
347
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
348
+ >
349
+ {{ stats.releases
350
+ }}<span v-if="selected.length" class="text-muted-foreground">
351
+ &thinsp;/&thinsp;{{ stats.total }}
352
+ </span>
353
+ </dd>
354
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
355
+ <Icon name="lucide:tag" class="size-3.5" />
356
+ {{ $t('duxt.changelog.releases') }}
357
+ </dt>
358
+ </div>
359
+
360
+ <div class="flex flex-col gap-1">
361
+ <dd
362
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
363
+ >
364
+ {{ stats.changes }}
365
+ </dd>
366
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
367
+ <Icon name="lucide:git-commit-horizontal" class="size-3.5" />
368
+ {{ $t('duxt.changelog.changes') }}
369
+ </dt>
370
+ </div>
371
+
372
+ <div v-if="stats.kinds" class="flex flex-col gap-1">
373
+ <dd
374
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
375
+ >
376
+ {{ stats.kinds }}
377
+ </dd>
378
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
379
+ <Icon name="lucide:list-filter" class="size-3.5" />
380
+ {{ $t('duxt.changelog.kinds') }}
381
+ </dt>
382
+ </div>
383
+
384
+ <!-- A date rather than a count, and the only one here: it is what a
385
+ reader weighing up a project checks first. Dropped where the file
386
+ dates nothing, rather than printed as a dash. -->
387
+ <div v-if="stats.last" class="flex flex-col gap-1">
388
+ <dd
389
+ class="text-2xl font-semibold tracking-tight tabular-nums sm:text-3xl"
390
+ >
391
+ {{ stats.last }}
392
+ </dd>
393
+ <dt class="flex items-center gap-1.5 text-xs font-medium text-primary">
394
+ <Icon name="lucide:calendar" class="size-3.5" />
395
+ {{ $t('duxt.changelog.last') }}
396
+ </dt>
397
+ </div>
398
+ </dl>
399
+
400
+ <!-- ONE LINE PER RELEASE, in columns: version, date, what it carried. Two
401
+ stacked lines gave every release the height of a paragraph and put the
402
+ versions six rows apart — and a history is read down the left edge, one
403
+ line at a time.
404
+
405
+ A GRID rather than a flex row, because the columns are what makes the
406
+ line readable: every date starts at the same x, so does every summary,
407
+ and the eye compares releases instead of re-finding the fields. The
408
+ badge column stays in the grid even where no release carries one — an
409
+ empty track is what keeps the fifth row's summary under the first's.
410
+
411
+ It COLLAPSES rather than scrolls: under `sm` the wrapper stops being
412
+ `contents`, so version, date and badge sit on one line and the summary
413
+ takes the one below, which is the same row with less room. -->
414
+ <!-- FILTERING MOVES ROWS, so the rows move. A list that jumps from six
415
+ entries to two gives a reader no way to see what happened; the same
416
+ change taken over 200ms is read as a filter narrowing rather than as a
417
+ new page.
418
+
419
+ `TransitionGroup` rather than a motion library: this is a fade, a
420
+ nudge and the FLIP the browser does for `move-class`, and none of it is
421
+ worth a runtime dependency in every site that extends the layer. A
422
+ leaving row is taken out of the flow (`absolute`) so the rows under it
423
+ close the gap while it fades rather than after.
424
+
425
+ `motion-reduce:transition-none` throughout, because a reader who asked
426
+ the system for less motion asked this list too. -->
427
+ <TransitionGroup
428
+ tag="ol"
429
+ class="relative -mx-3"
430
+ enter-active-class="transition duration-200 ease-out motion-reduce:transition-none"
431
+ enter-from-class="translate-y-1 opacity-0"
432
+ leave-active-class="absolute inset-x-0 transition duration-150 ease-in motion-reduce:transition-none"
433
+ leave-to-class="-translate-y-1 opacity-0"
434
+ move-class="transition-transform duration-200 ease-out motion-reduce:transition-none"
435
+ >
436
+ <li
437
+ v-for="{ release, named, rest } in rows"
438
+ :key="release.to"
439
+ class="border-t border-border/60 first:border-t-0"
440
+ >
441
+ <NuxtLink
442
+ :to="localeLink(release.to)"
443
+ class="group grid grid-cols-[1fr_auto] items-baseline gap-x-4 gap-y-1.5 rounded-lg px-3 py-3 transition-colors hover:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none sm:grid-cols-[9rem_1fr_auto_auto]"
444
+ >
445
+ <span class="flex items-baseline gap-3 sm:contents">
446
+ <!-- ONE CELL for the version and its badge, so the two stand a
447
+ chip's width apart rather than a column apart: the badge is a
448
+ fact about the version, and a fixed track between them made the
449
+ gap a layout decision instead of a typographic one. The cell
450
+ itself is fixed, which is what still lines the summaries up. -->
451
+ <span
452
+ class="flex items-baseline gap-2 sm:col-start-1 sm:row-start-1"
453
+ >
454
+ <h3
455
+ class="font-semibold tabular-nums transition-colors group-hover:text-primary"
456
+ >
457
+ {{ release.version }}
458
+ </h3>
459
+
460
+ <UiBadge
461
+ v-if="latest(release)"
462
+ class="px-2 py-0 text-[0.6875rem] leading-5"
463
+ >
464
+ {{ $t('duxt.changelog.latest') }}
465
+ </UiBadge>
466
+ </span>
467
+
468
+ <!-- At the far end, beside the arrow. It is the one field a reader
469
+ scans down rather than reads across, and the right edge is
470
+ where a column of dates lines up without competing with the
471
+ version for the left one. -->
472
+ <time
473
+ v-if="release.date"
474
+ :datetime="release.date"
475
+ class="text-xs text-muted-foreground tabular-nums sm:col-start-3 sm:row-start-1"
476
+ >
477
+ {{ formatted(release.date) }}
478
+ </time>
479
+ </span>
480
+
481
+ <p
482
+ class="col-span-2 row-start-2 text-xs text-muted-foreground sm:col-span-1 sm:col-start-2 sm:row-start-1"
483
+ >
484
+ <span v-for="(group, index) in named" :key="group.name">
485
+ <!-- The separator is punctuation, not a word: set fainter than
486
+ either side and given room, so the eye breaks the line into
487
+ three facts instead of reading one run of text. -->
488
+ <span
489
+ v-if="index"
490
+ aria-hidden="true"
491
+ class="px-1 text-muted-foreground/50"
492
+ >
493
+ ·
494
+ </span>
495
+ {{ changelogLabel(group.name) }}
496
+ <span class="ml-0.5 font-medium text-foreground tabular-nums">
497
+ {{ group.count }}
498
+ </span>
499
+ </span>
500
+
501
+ <template v-if="rest">
502
+ <span aria-hidden="true" class="px-1 text-muted-foreground/50">
503
+ ·
504
+ </span>
505
+ {{ $t('duxt.changelog.more', { count: rest }) }}
506
+ </template>
507
+ </p>
508
+
509
+ <!-- The affordance every card and row of this site uses: the arrow
510
+ that steps forward under the pointer. -->
511
+ <Icon
512
+ name="lucide:arrow-right"
513
+ aria-hidden="true"
514
+ class="col-start-2 row-start-1 size-3.5 shrink-0 self-center text-muted-foreground transition-transform group-hover:translate-x-0.5 sm:col-start-4"
515
+ />
516
+ </NuxtLink>
517
+ </li>
518
+ </TransitionGroup>
519
+ </div>
520
+ </template>