@murshisoft/docus 1.0.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +116 -0
  3. package/app/app.config.ts +33 -0
  4. package/app/app.vue +79 -0
  5. package/app/components/IconMenuToggle.vue +83 -0
  6. package/app/components/LanguageSelect.vue +83 -0
  7. package/app/components/OgImage/OgImageDocs.vue +78 -0
  8. package/app/components/OgImage/OgImageLanding.vue +75 -0
  9. package/app/components/app/AppFooter.vue +11 -0
  10. package/app/components/app/AppFooterLeft.vue +5 -0
  11. package/app/components/app/AppFooterRight.vue +30 -0
  12. package/app/components/app/AppHeader.vue +82 -0
  13. package/app/components/app/AppHeaderBody.vue +13 -0
  14. package/app/components/app/AppHeaderCTA.vue +3 -0
  15. package/app/components/app/AppHeaderCenter.vue +10 -0
  16. package/app/components/app/AppHeaderLogo.vue +16 -0
  17. package/app/components/docs/DocsAsideLeftBody.vue +12 -0
  18. package/app/components/docs/DocsAsideLeftTop.vue +3 -0
  19. package/app/components/docs/DocsAsideRightBottom.vue +18 -0
  20. package/app/components/docs/DocsPageHeaderLinks.vue +96 -0
  21. package/app/composables/useDocusI18n.ts +35 -0
  22. package/app/error.vue +79 -0
  23. package/app/layouts/default.vue +5 -0
  24. package/app/layouts/docs.vue +15 -0
  25. package/app/pages/[[lang]]/[...slug].vue +145 -0
  26. package/app/plugins/i18n.ts +40 -0
  27. package/app/templates/landing.vue +44 -0
  28. package/app/types/index.d.ts +42 -0
  29. package/app/utils/navigation.ts +7 -0
  30. package/app/utils/prerender.ts +12 -0
  31. package/content.config.ts +67 -0
  32. package/i18n/locales/ar.json +22 -0
  33. package/i18n/locales/be.json +23 -0
  34. package/i18n/locales/bg.json +22 -0
  35. package/i18n/locales/bn.json +22 -0
  36. package/i18n/locales/ca.json +22 -0
  37. package/i18n/locales/ckb.json +18 -0
  38. package/i18n/locales/cs.json +22 -0
  39. package/i18n/locales/da.json +22 -0
  40. package/i18n/locales/de.json +22 -0
  41. package/i18n/locales/el.json +22 -0
  42. package/i18n/locales/en.json +22 -0
  43. package/i18n/locales/es.json +22 -0
  44. package/i18n/locales/et.json +22 -0
  45. package/i18n/locales/fi.json +22 -0
  46. package/i18n/locales/fr.json +22 -0
  47. package/i18n/locales/he.json +22 -0
  48. package/i18n/locales/hi.json +22 -0
  49. package/i18n/locales/hy.json +22 -0
  50. package/i18n/locales/it.json +22 -0
  51. package/i18n/locales/ja.json +22 -0
  52. package/i18n/locales/kk.json +22 -0
  53. package/i18n/locales/km.json +22 -0
  54. package/i18n/locales/ko.json +22 -0
  55. package/i18n/locales/ky.json +22 -0
  56. package/i18n/locales/lb.json +22 -0
  57. package/i18n/locales/ms.json +22 -0
  58. package/i18n/locales/nb.json +22 -0
  59. package/i18n/locales/nl.json +22 -0
  60. package/i18n/locales/pl.json +23 -0
  61. package/i18n/locales/ro.json +22 -0
  62. package/i18n/locales/ru.json +23 -0
  63. package/i18n/locales/sl.json +22 -0
  64. package/i18n/locales/sv.json +22 -0
  65. package/i18n/locales/uk.json +22 -0
  66. package/i18n/locales/ur.json +22 -0
  67. package/i18n/locales/vi.json +22 -0
  68. package/modules/config.ts +116 -0
  69. package/modules/css.ts +32 -0
  70. package/modules/routing.ts +41 -0
  71. package/nuxt.config.ts +84 -0
  72. package/nuxt.schema.ts +218 -0
  73. package/package.json +55 -0
  74. package/server/mcp/tools/get-page.ts +60 -0
  75. package/server/mcp/tools/list-pages.ts +60 -0
  76. package/server/routes/raw/[...slug].md.get.ts +45 -0
  77. package/server/utils/content.ts +37 -0
  78. package/utils/git.ts +110 -0
  79. package/utils/meta.ts +29 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) NuxtLabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ [![docus](https://docus.dev/__og-image__/static/og.png)](https://docus.dev)
2
+
3
+ # Docus
4
+
5
+ > A minimal and beautiful Nuxt layer for documentation websites
6
+
7
+ [![npm version](https://img.shields.io/npm/v/docus.svg)](https://www.npmjs.com/package/docus)
8
+ [![npm downloads](https://img.shields.io/npm/dm/docus.svg)](https://www.npmjs.com/package/docus)
9
+
10
+ This is the official Nuxt layer for [Docus](https://docus.dev), providing a complete documentation theming. It works with the [Docus CLI](https://github.com/nuxt-content/docus/tree/main/cli) for rapid project setup.
11
+
12
+ ## 🚀 Features
13
+
14
+ - ✨ **Beautiful Design** - Clean, modern documentation theme
15
+ - 📱 **Responsive** - Mobile-first responsive design
16
+ - 🌙 **Dark Mode** - Built-in dark/light mode support
17
+ - 🌍 **Internationalization** - Native i18n support with automatic routing and language switching
18
+ - 🔍 **Search** - Full-text search functionality
19
+ - 📝 **Markdown Enhanced** - Extended markdown with custom components
20
+ - 🎨 **Customizable** - Easy theming and customization
21
+ - ⚡ **Fast** - Optimized for performance
22
+ - 🔧 **TypeScript** - Full TypeScript support
23
+ - 🛠️ **CLI Integration** - Works with Docus CLI for quick project setup
24
+
25
+ ## 📦 Installation
26
+
27
+ ```bash
28
+ npm install docus
29
+ ```
30
+
31
+ ## 🏗️ Quick Setup
32
+
33
+ ### Option 1: Docus CLI (Recommended)
34
+
35
+ The easiest way to get started is using the Docus CLI, which automatically sets up a project with this layer:
36
+
37
+ ```bash
38
+ # Create a new documentation project
39
+ npx create-docus my-docs
40
+
41
+ # Navigate to your project
42
+ cd my-docs
43
+
44
+ # Start development
45
+ npm run dev
46
+ ```
47
+
48
+ This creates a complete documentation project pre-configured with `docus`.
49
+
50
+ For multi-language documentation, use the i18n template:
51
+
52
+ ```bash
53
+ # Create a new i18n documentation project
54
+ npx create-docus my-docs -t i18n
55
+ ```
56
+
57
+ ### Option 2: Manual Setup
58
+
59
+ #### Option 2a: Nuxt Config (recommended)
60
+
61
+ Add the layer to your `nuxt.config.ts`:
62
+
63
+ ```typescript
64
+ export default defineNuxtConfig({
65
+ extends: ['docus']
66
+ })
67
+ ```
68
+
69
+ For internationalization, also add the `@nuxtjs/i18n` module:
70
+
71
+ ```typescript
72
+ export default defineNuxtConfig({
73
+ modules: ['@nuxtjs/i18n'],
74
+ i18n: {
75
+ defaultLocale: 'en',
76
+ locales: [
77
+ { code: 'en', name: 'English' },
78
+ { code: 'fr', name: 'Français' },
79
+ ],
80
+ }
81
+ })
82
+ ```
83
+
84
+ #### Option 2b: CLI Usage
85
+
86
+ Use directly with Nuxt CLI:
87
+
88
+ ```bash
89
+ # Development
90
+ nuxt dev --extends docus
91
+
92
+ # Build
93
+ nuxt build --extends docus
94
+ ```
95
+
96
+ ## 🔗 Related Packages
97
+
98
+ - [`create-docus`](https://www.npmjs.com/package/create-docus) - CLI tool to scaffold Docus projects
99
+
100
+ ## 📄 License
101
+
102
+ [MIT License](./LICENSE)
103
+
104
+ ## 🤝 Contributing
105
+
106
+ Contributions are welcome! Please feel free to submit a Pull Request.
107
+
108
+ ## 📞 Support
109
+
110
+ - 📖 [Documentation](https://docus.dev)
111
+ - 🐛 [Issues](https://github.com/nuxt-content/docus/issues)
112
+ - 💬 [Discussions](https://github.com/nuxt-content/docus/discussions)
113
+
114
+ ---
115
+
116
+ Made with ❤️ for the Nuxt community
@@ -0,0 +1,33 @@
1
+ export default defineAppConfig({
2
+ docus: {
3
+ locale: 'en',
4
+ },
5
+ ui: {
6
+ colors: {
7
+ primary: 'emerald',
8
+ neutral: 'zinc',
9
+ },
10
+ commandPalette: {
11
+ slots: {
12
+ item: 'items-center',
13
+ input: '[&_.iconify]:size-4 [&_.iconify]:mx-0.5',
14
+ itemLeadingIcon: 'size-4 mx-0.5',
15
+ },
16
+ },
17
+ contentNavigation: {
18
+ slots: {
19
+ linkLeadingIcon: 'size-4 mr-1',
20
+ linkTrailing: 'hidden',
21
+ },
22
+ defaultVariants: {
23
+ variant: 'link',
24
+ },
25
+ },
26
+ pageLinks: {
27
+ slots: {
28
+ linkLeadingIcon: 'size-4',
29
+ linkLabelExternalIcon: 'size-2.5',
30
+ },
31
+ },
32
+ },
33
+ })
package/app/app.vue ADDED
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
3
+ import * as nuxtUiLocales from '@nuxt/ui/locale'
4
+
5
+ const { seo } = useAppConfig()
6
+ const site = useSiteConfig()
7
+ const { locale, locales, isEnabled, switchLocalePath } = useDocusI18n()
8
+
9
+ const nuxtUiLocale = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales] || nuxtUiLocales.en)
10
+ const lang = computed(() => nuxtUiLocale.value.code)
11
+ const dir = computed(() => nuxtUiLocale.value.dir)
12
+ const collectionName = computed(() => isEnabled.value ? `docs_${locale.value}` : 'docs')
13
+
14
+ useHead({
15
+ meta: [
16
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
17
+ ],
18
+ link: [
19
+ { rel: 'icon', href: '/favicon.ico' },
20
+ ],
21
+ htmlAttrs: {
22
+ lang,
23
+ dir,
24
+ },
25
+ })
26
+
27
+ useSeoMeta({
28
+ titleTemplate: seo.titleTemplate,
29
+ title: seo.title,
30
+ description: seo.description,
31
+ ogSiteName: site.name,
32
+ twitterCard: 'summary_large_image',
33
+ })
34
+
35
+ if (isEnabled.value) {
36
+ const route = useRoute()
37
+ const defaultLocale = useRuntimeConfig().public.i18n.defaultLocale!
38
+ onMounted(() => {
39
+ const currentLocale = route.path.split('/')[1]
40
+ if (!locales.some(locale => locale.code === currentLocale)) {
41
+ return navigateTo(switchLocalePath(defaultLocale) as string)
42
+ }
43
+ })
44
+ }
45
+
46
+ const { data: navigation } = await useAsyncData(() => `navigation_${collectionName.value}`, () => queryCollectionNavigation(collectionName.value as keyof PageCollections), {
47
+ transform: (data: ContentNavigationItem[]) => {
48
+ const rootResult = data.find(item => item.path === '/docs')?.children || data || []
49
+
50
+ return rootResult.find(item => item.path === `/${locale.value}`)?.children || rootResult
51
+ },
52
+ watch: [locale],
53
+ })
54
+ const { data: files } = useLazyAsyncData(`search_${collectionName.value}`, () => queryCollectionSearchSections(collectionName.value as keyof PageCollections), {
55
+ server: false,
56
+ watch: [locale],
57
+ })
58
+
59
+ provide('navigation', navigation)
60
+ </script>
61
+
62
+ <template>
63
+ <UApp :locale="nuxtUiLocale">
64
+ <NuxtLoadingIndicator color="var(--ui-primary)" />
65
+
66
+ <AppHeader v-if="$route.meta.header !== false" />
67
+ <NuxtLayout>
68
+ <NuxtPage />
69
+ </NuxtLayout>
70
+ <AppFooter v-if="$route.meta.footer !== false" />
71
+
72
+ <ClientOnly>
73
+ <LazyUContentSearch
74
+ :files="files"
75
+ :navigation="navigation"
76
+ />
77
+ </ClientOnly>
78
+ </UApp>
79
+ </template>
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ import { motion } from 'motion-v'
3
+ import type { VariantType } from 'motion-v'
4
+
5
+ const props = defineProps<{
6
+ open: boolean
7
+ }>()
8
+
9
+ const variants: { [k: string]: VariantType | ((custom: unknown) => VariantType) } = {
10
+ normal: {
11
+ rotate: 0,
12
+ y: 0,
13
+ opacity: 1,
14
+ },
15
+ close: (custom: unknown) => {
16
+ const c = custom as number
17
+ return {
18
+ rotate: c === 1 ? 45 : c === 3 ? -45 : 0,
19
+ y: c === 1 ? 6 : c === 3 ? -6 : 0,
20
+ opacity: c === 2 ? 0 : 1,
21
+ transition: {
22
+ type: 'spring',
23
+ stiffness: 260,
24
+ damping: 20,
25
+ },
26
+ }
27
+ },
28
+ }
29
+
30
+ const state = computed(() => props.open ? 'close' : 'normal')
31
+ </script>
32
+
33
+ <template>
34
+ <UButton
35
+ size="sm"
36
+ variant="ghost"
37
+ color="neutral"
38
+ class="-me-1.5"
39
+ square
40
+ >
41
+ <svg
42
+ xmlns="http://www.w3.org/2000/svg"
43
+ class="size-5"
44
+ viewBox="0 0 24 24"
45
+ fill="none"
46
+ stroke="currentColor"
47
+ stroke-width="2"
48
+ stroke-linecap="round"
49
+ stroke-linejoin="round"
50
+ >
51
+ <motion.line
52
+ x1="4"
53
+ y1="6"
54
+ x2="20"
55
+ y2="6"
56
+ :variants="variants"
57
+ :animate="state"
58
+ :custom="1"
59
+ class="outline-none"
60
+ />
61
+ <motion.line
62
+ x1="4"
63
+ y1="12"
64
+ x2="20"
65
+ y2="12"
66
+ :variants="variants"
67
+ :animate="state"
68
+ :custom="2"
69
+ class="outline-none"
70
+ />
71
+ <motion.line
72
+ x1="4"
73
+ y1="18"
74
+ x2="20"
75
+ y2="18"
76
+ :variants="variants"
77
+ :animate="state"
78
+ :custom="3"
79
+ class="outline-none"
80
+ />
81
+ </svg>
82
+ </UButton>
83
+ </template>
@@ -0,0 +1,83 @@
1
+ <script setup lang="ts">
2
+ const { locale, locales, switchLocalePath } = useDocusI18n()
3
+
4
+ function getEmojiFlag(locale: string): string {
5
+ const languageToCountry: Record<string, string> = {
6
+ ar: 'sa', // Arabic -> Saudi Arabia
7
+ bn: 'bd', // Bengali -> Bangladesh
8
+ ca: 'es', // Catalan -> Spain
9
+ ckb: 'iq', // Central Kurdish -> Iraq
10
+ cs: 'cz', // Czech -> Czech Republic (note: modern country code is actually 'cz')
11
+ da: 'dk', // Danish -> Denmark
12
+ el: 'gr', // Greek -> Greece
13
+ en: 'gb', // English -> Great Britain
14
+ et: 'ee', // Estonian -> Estonia
15
+ he: 'il', // Hebrew -> Israel
16
+ hi: 'in', // Hindi -> India
17
+ hy: 'am', // Armenian -> Armenia
18
+ ja: 'jp', // Japanese -> Japan
19
+ kk: 'kz', // Kazakh -> Kazakhstan
20
+ km: 'kh', // Khmer -> Cambodia
21
+ ko: 'kr', // Korean -> South Korea
22
+ ky: 'kg', // Kyrgyz -> Kyrgyzstan
23
+ lb: 'lu', // Luxembourgish -> Luxembourg
24
+ ms: 'my', // Malay -> Malaysia
25
+ nb: 'no', // Norwegian Bokmål -> Norway
26
+ sl: 'si', // Slovenian -> Slovenia
27
+ sv: 'se', // Swedish -> Sweden
28
+ uk: 'ua', // Ukrainian -> Ukraine
29
+ ur: 'pk', // Urdu -> Pakistan
30
+ vi: 'vn', // Vietnamese -> Vietnam
31
+ es: 'es', // Spanish -> Spain
32
+ }
33
+
34
+ const baseLanguage = locale.split('-')[0]?.toLowerCase() || locale
35
+ const countryCode = languageToCountry[baseLanguage] || locale.replace(/^.*-/, '').slice(0, 2)
36
+
37
+ return countryCode.toUpperCase()
38
+ .split('')
39
+ .map(char => String.fromCodePoint(0x1F1A5 + char.charCodeAt(0)))
40
+ .join('')
41
+ }
42
+ </script>
43
+
44
+ <template>
45
+ <UPopover
46
+ mode="hover"
47
+ :content="{ align: 'end' }"
48
+ >
49
+ <UButton
50
+ color="neutral"
51
+ variant="ghost"
52
+ class="size-8"
53
+ >
54
+ <template #trailing>
55
+ <span class="text-lg">
56
+ {{ getEmojiFlag(locale) }}
57
+ </span>
58
+ </template>
59
+ </UButton>
60
+
61
+ <template #content>
62
+ <ul class="flex flex-col">
63
+ <li
64
+ v-for="localeItem in locales"
65
+ :key="localeItem.code"
66
+ >
67
+ <NuxtLink
68
+ class="flex justify-between py-1.5 px-2 gap-1 hover:bg-muted"
69
+ :to="switchLocalePath(localeItem.code) as string"
70
+ :aria-label="localeItem.name"
71
+ >
72
+ <span class="text-sm">
73
+ {{ localeItem.name }}
74
+ </span>
75
+ <span class="size-5 text-center">
76
+ {{ getEmojiFlag(localeItem.code) }}
77
+ </span>
78
+ </NuxtLink>
79
+ </li>
80
+ </ul>
81
+ </template>
82
+ </UPopover>
83
+ </template>
@@ -0,0 +1,78 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from 'vue'
3
+
4
+ const props = withDefaults(defineProps<{ title?: string, description?: string, headline?: string }>(), {
5
+ title: 'title',
6
+ description: 'description',
7
+ })
8
+
9
+ const title = computed(() => (props.title || '').slice(0, 60))
10
+ const description = computed(() => (props.description || '').slice(0, 200))
11
+ </script>
12
+
13
+ <template>
14
+ <div class="w-full h-full flex flex-col justify-center bg-neutral-900">
15
+ <svg
16
+ class="absolute right-0 top-0 opacity-50"
17
+ width="629"
18
+ height="593"
19
+ viewBox="0 0 629 593"
20
+ fill="none"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ >
23
+ <g filter="url(#filter0_f_199_94966)">
24
+ <path
25
+ d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z"
26
+ fill="white"
27
+ />
28
+ </g>
29
+ <defs>
30
+ <filter
31
+ id="filter0_f_199_94966"
32
+ x="0.873535"
33
+ y="-659"
34
+ width="1255.25"
35
+ height="1251.52"
36
+ filterUnits="userSpaceOnUse"
37
+ color-interpolation-filters="sRGB"
38
+ >
39
+ <feFlood
40
+ flood-opacity="0"
41
+ result="BackgroundImageFix"
42
+ />
43
+ <feBlend
44
+ mode="normal"
45
+ in="SourceGraphic"
46
+ in2="BackgroundImageFix"
47
+ result="shape"
48
+ />
49
+ <feGaussianBlur
50
+ stdDeviation="40.5"
51
+ result="effect1_foregroundBlur_199_94966"
52
+ />
53
+ </filter>
54
+ </defs>
55
+ </svg>
56
+
57
+ <div class="pl-[100px]">
58
+ <p
59
+ v-if="headline"
60
+ class="uppercase text-[24px] text-emerald-500 mb-4 font-semibold"
61
+ >
62
+ {{ headline }}
63
+ </p>
64
+ <h1
65
+ v-if="title"
66
+ class="m-0 text-[75px] font-semibold mb-4 text-white flex items-center"
67
+ >
68
+ <span>{{ title }}</span>
69
+ </h1>
70
+ <p
71
+ v-if="description"
72
+ class="text-[32px] text-neutral-300 leading-tight w-[700px]"
73
+ >
74
+ {{ description }}
75
+ </p>
76
+ </div>
77
+ </div>
78
+ </template>
@@ -0,0 +1,75 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from 'vue'
3
+
4
+ const props = withDefaults(defineProps<{ title?: string, description?: string, headline?: string }>(), {
5
+ title: 'title',
6
+ description: 'description',
7
+ })
8
+
9
+ const title = computed(() => (props.title || '').slice(0, 60))
10
+ const description = computed(() => (props.description || '').slice(0, 200))
11
+ </script>
12
+
13
+ <template>
14
+ <div class="w-full h-full flex items-center justify-center bg-neutral-900">
15
+ <svg
16
+ class="absolute right-0 top-0 opacity-50 "
17
+ width="629"
18
+ height="593"
19
+ viewBox="0 0 629 593"
20
+ fill="none"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ >
23
+ <g filter="url(#filter0_f_199_94966)">
24
+ <path
25
+ d="M628.5 -578L639.334 -94.4223L806.598 -548.281L659.827 -87.387L965.396 -462.344L676.925 -74.0787L1087.69 -329.501L688.776 -55.9396L1160.22 -164.149L694.095 -34.9354L1175.13 15.7948L692.306 -13.3422L1130.8 190.83L683.602 6.50012L1032.04 341.989L668.927 22.4412L889.557 452.891L649.872 32.7537L718.78 511.519L628.5 36.32L538.22 511.519L607.128 32.7537L367.443 452.891L588.073 22.4412L224.955 341.989L573.398 6.50012L126.198 190.83L564.694 -13.3422L81.8734 15.7948L562.905 -34.9354L96.7839 -164.149L568.224 -55.9396L169.314 -329.501L580.075 -74.0787L291.604 -462.344L597.173 -87.387L450.402 -548.281L617.666 -94.4223L628.5 -578Z"
26
+ fill="white"
27
+ />
28
+ </g>
29
+ <defs>
30
+ <filter
31
+ id="filter0_f_199_94966"
32
+ x="0.873535"
33
+ y="-659"
34
+ width="1255.25"
35
+ height="1251.52"
36
+ filterUnits="userSpaceOnUse"
37
+ color-interpolation-filters="sRGB"
38
+ >
39
+ <feFlood
40
+ flood-opacity="0"
41
+ result="BackgroundImageFix"
42
+ />
43
+ <feBlend
44
+ mode="normal"
45
+ in="SourceGraphic"
46
+ in2="BackgroundImageFix"
47
+ result="shape"
48
+ />
49
+ <feGaussianBlur
50
+ stdDeviation="40.5"
51
+ result="effect1_foregroundBlur_199_94966"
52
+ />
53
+ </filter>
54
+ </defs>
55
+ </svg>
56
+
57
+ <div class="flex flex-col justify-center p-8">
58
+ <div class="flex justify-center mb-8">
59
+ <AppHeaderLogo white />
60
+ </div>
61
+ <h1
62
+ v-if="title"
63
+ class="flex justify-center m-0 text-5xl font-semibold mb-4 text-white"
64
+ >
65
+ <span>{{ title }}</span>
66
+ </h1>
67
+ <p
68
+ v-if="description"
69
+ class="text-center text-2xl text-neutral-300 leading-tight"
70
+ >
71
+ {{ description }}
72
+ </p>
73
+ </div>
74
+ </div>
75
+ </template>
@@ -0,0 +1,11 @@
1
+ <template>
2
+ <UFooter>
3
+ <template #left>
4
+ <AppFooterLeft />
5
+ </template>
6
+
7
+ <template #right>
8
+ <AppFooterRight />
9
+ </template>
10
+ </UFooter>
11
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="text-sm text-muted">
3
+ Copyright © {{ new Date().getFullYear() }}
4
+ </div>
5
+ </template>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ const appConfig = useAppConfig()
3
+
4
+ const links = computed(() => [
5
+ ...Object.entries(appConfig.socials || {}).map(([key, url]) => ({
6
+ 'icon': `i-simple-icons-${key}`,
7
+ 'to': url,
8
+ 'target': '_blank',
9
+ 'aria-label': `${key} social link`,
10
+ })),
11
+ appConfig.github && appConfig.github.url && {
12
+ 'icon': 'i-simple-icons-github',
13
+ 'to': appConfig.github.url,
14
+ 'target': '_blank',
15
+ 'aria-label': 'GitHub repository',
16
+ },
17
+ ].filter(Boolean))
18
+ </script>
19
+
20
+ <template>
21
+ <template v-if="links.length">
22
+ <UButton
23
+ v-for="(link, index) of links"
24
+ :key="index"
25
+ size="sm"
26
+ v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
27
+ />
28
+ </template>
29
+ <UColorModeButton />
30
+ </template>