@nuxtify/pages 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 (53) hide show
  1. package/README.md +203 -0
  2. package/dist/module.cjs +5 -0
  3. package/dist/module.d.mts +139 -0
  4. package/dist/module.d.ts +139 -0
  5. package/dist/module.json +13 -0
  6. package/dist/module.mjs +138 -0
  7. package/dist/runtime/components/EmailSubscribeForm.vue +215 -0
  8. package/dist/runtime/components/FooterCallToAction.vue +38 -0
  9. package/dist/runtime/components/app/AppAnnouncementBar.vue +40 -0
  10. package/dist/runtime/components/app/AppBar.vue +128 -0
  11. package/dist/runtime/components/app/AppDialog.vue +40 -0
  12. package/dist/runtime/components/app/AppFooter.vue +150 -0
  13. package/dist/runtime/components/app/AppLoading.vue +16 -0
  14. package/dist/runtime/components/app/AppLogo.vue +55 -0
  15. package/dist/runtime/components/app/AppNavigationDrawer.vue +101 -0
  16. package/dist/runtime/components/app/AppToast.vue +18 -0
  17. package/dist/runtime/composables/dialog.d.ts +13 -0
  18. package/dist/runtime/composables/dialog.js +15 -0
  19. package/dist/runtime/composables/nuxtify.d.ts +2 -0
  20. package/dist/runtime/composables/nuxtify.js +2 -0
  21. package/dist/runtime/composables/state.d.ts +15 -0
  22. package/dist/runtime/composables/state.js +10 -0
  23. package/dist/runtime/layouts/DefaultLayout.vue +33 -0
  24. package/dist/runtime/pages/DynamicSlug.vue +23 -0
  25. package/dist/runtime/pages/IndexPage.vue +23 -0
  26. package/dist/runtime/server/composables/client.d.ts +1 -0
  27. package/dist/runtime/server/composables/client.js +1 -0
  28. package/dist/runtime/server/tsconfig.json +3 -0
  29. package/dist/runtime/server/utils/client.d.ts +1 -0
  30. package/dist/runtime/server/utils/client.js +1 -0
  31. package/dist/runtime/utils/email.d.ts +5 -0
  32. package/dist/runtime/utils/email.js +26 -0
  33. package/dist/runtime/utils/formRules.d.ts +18 -0
  34. package/dist/runtime/utils/formRules.js +22 -0
  35. package/dist/runtime/utils/icons.d.ts +1 -0
  36. package/dist/runtime/utils/icons.js +1 -0
  37. package/dist/runtime/utils/io.d.ts +2 -0
  38. package/dist/runtime/utils/io.js +19 -0
  39. package/dist/runtime/utils/math.d.ts +2 -0
  40. package/dist/runtime/utils/math.js +13 -0
  41. package/dist/runtime/utils/text.d.ts +23 -0
  42. package/dist/runtime/utils/text.js +159 -0
  43. package/dist/runtime/utils/time.d.ts +2 -0
  44. package/dist/runtime/utils/time.js +7 -0
  45. package/dist/runtime/utils/url.d.ts +9 -0
  46. package/dist/runtime/utils/url.js +27 -0
  47. package/dist/runtime/utils/util.d.ts +7 -0
  48. package/dist/runtime/utils/util.js +5 -0
  49. package/dist/runtime/utils/youtube.d.ts +1 -0
  50. package/dist/runtime/utils/youtube.js +13 -0
  51. package/dist/types.d.mts +7 -0
  52. package/dist/types.d.ts +7 -0
  53. package/package.json +60 -0
@@ -0,0 +1,215 @@
1
+ <script setup lang="ts">
2
+ // Types
3
+ import type { VForm } from 'vuetify/lib/components/VForm/index.mjs'
4
+
5
+ import { useId, navigateTo, useDisplay, ref, useNuxtifyConfig, getUtmParams, getBaseUrl, submitFormData, formRules, isExternalUrl,
6
+ } from '#imports'
7
+
8
+ // Props
9
+ const props = defineProps({
10
+ submitUrl: {
11
+ type: String,
12
+ required: true,
13
+ },
14
+ buttonText: {
15
+ type: String,
16
+ default: 'Subscribe',
17
+ },
18
+ emailPlaceholder: {
19
+ type: String,
20
+ default: 'Enter your best email',
21
+ },
22
+ marketingConsentText: {
23
+ type: String,
24
+ default: 'Unsubscribe any time.',
25
+ },
26
+ showPrivacy: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
30
+ redirectUrl: {
31
+ type: String,
32
+ default: '',
33
+ },
34
+ thankYouMessage: {
35
+ type: String,
36
+ default: 'Thanks for signing up!',
37
+ },
38
+ dark: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
42
+ appendButtonIcon: {
43
+ type: String,
44
+ default: '',
45
+ },
46
+ prependButtonIcon: {
47
+ type: String,
48
+ default: '',
49
+ },
50
+ })
51
+
52
+ // App state
53
+ const id = useId()
54
+ const nuxtifyConfig = useNuxtifyConfig()
55
+ const { xs } = useDisplay()
56
+
57
+ // Form state
58
+ const form = ref<VForm>()
59
+ const isSubmitted = ref(false)
60
+ const isError = ref(false)
61
+ const errorMessage = ref('')
62
+ const loading = ref(false)
63
+ const formInput = ref({
64
+ email: '',
65
+ })
66
+
67
+ // Button style
68
+ const rounded = () => {
69
+ if (nuxtifyConfig.style?.btn?.rounded) {
70
+ return xs.value ? 'lg' : '0 e-lg'
71
+ }
72
+ else {
73
+ return 0
74
+ }
75
+ }
76
+
77
+ async function handleSubmit() {
78
+ loading.value = true
79
+ const res = await form.value?.validate()
80
+ if (res?.valid) {
81
+ const formData = new FormData()
82
+
83
+ // Get UTM params
84
+ const { utmSource, utmMedium, utmCampaign, utmTerm, utmContent }
85
+ = getUtmParams(window.location.href)
86
+
87
+ // Set form field keys
88
+ // Default
89
+ let fieldPrepend = ''
90
+ let fieldAppend = ''
91
+
92
+ // Mailerlite
93
+ if (props.submitUrl.includes('mailerlite')) {
94
+ fieldPrepend = 'fields['
95
+ fieldAppend = ']'
96
+ }
97
+
98
+ // Set form field values
99
+ formData.append(
100
+ `${fieldPrepend}email${fieldAppend}`,
101
+ formInput.value.email,
102
+ )
103
+ formData.append(
104
+ `${fieldPrepend}referrer${fieldAppend}`,
105
+ getBaseUrl(window.location.href),
106
+ )
107
+
108
+ if (utmSource)
109
+ formData.append(`${fieldPrepend}utm_source${fieldAppend}`, utmSource)
110
+ if (utmMedium)
111
+ formData.append(`${fieldPrepend}utm_medium${fieldAppend}`, utmMedium)
112
+ if (utmCampaign)
113
+ formData.append(`${fieldPrepend}utm_campaign${fieldAppend}`, utmCampaign)
114
+ if (utmTerm)
115
+ formData.append(`${fieldPrepend}utm_term${fieldAppend}`, utmTerm)
116
+ if (utmContent)
117
+ formData.append(`${fieldPrepend}utm_content${fieldAppend}`, utmContent)
118
+
119
+ // Send to email provider
120
+ const providerResponse = await submitFormData(props.submitUrl, formData)
121
+ isError.value = providerResponse.isError
122
+ errorMessage.value = providerResponse.errorMessage
123
+
124
+ // Redirect
125
+ if (!isError.value && props.redirectUrl) {
126
+ await navigateTo(props.redirectUrl, {
127
+ external: isExternalUrl(props.redirectUrl, nuxtifyConfig.brand?.domain ?? ''),
128
+ })
129
+ }
130
+ else {
131
+ isSubmitted.value = providerResponse.isSubmitted
132
+ }
133
+ }
134
+ loading.value = false
135
+ }
136
+ </script>
137
+
138
+ <template>
139
+ <!-- Form -->
140
+ <v-form
141
+ v-if="!isSubmitted"
142
+ ref="form"
143
+ validate-on="submit"
144
+ @submit.prevent="handleSubmit"
145
+ >
146
+ <div class="d-sm-flex justify-center">
147
+ <!-- Using useId prevents hydration mismatch warnings issue with Vuetify -->
148
+ <!-- See: https://github.com/vuetifyjs/vuetify/issues/19696 -->
149
+ <!-- Once the issue is resolved (and it's used internally in Vuetify), remove use of useId -->
150
+ <v-text-field
151
+ :id
152
+ v-model="formInput.email"
153
+ type="email"
154
+ color="secondary"
155
+ :placeholder="emailPlaceholder"
156
+ :rules="[formRules.required, formRules.validEmail]"
157
+ :rounded="xs ? 't-lg' : '0 ts-lg'"
158
+ hide-details="auto"
159
+ class="text-start"
160
+ >
161
+ <template #message="{ message }">
162
+ <span :class="dark ? 'text-red-lighten-3' : ''">{{ message }}</span>
163
+ </template>
164
+ </v-text-field>
165
+ <v-btn
166
+ type="submit"
167
+ variant="flat"
168
+ color="secondary"
169
+ size="x-large"
170
+ class="d-flex align-center py-7 mt-2 mt-sm-0"
171
+ :loading
172
+ :rounded="rounded()"
173
+ :append-icon="appendButtonIcon"
174
+ :prepend-icon="prependButtonIcon"
175
+ :block="xs"
176
+ >
177
+ {{ buttonText }}
178
+ </v-btn>
179
+ </div>
180
+
181
+ <!-- Supporting Text -->
182
+ <div
183
+ v-if="showPrivacy || marketingConsentText"
184
+ :class="`text-body-2 ${
185
+ dark ? 'text-grey-lighten-2' : 'text-medium-emphasis'
186
+ } mt-2`"
187
+ >
188
+ <span v-if="marketingConsentText">
189
+ {{ marketingConsentText }}
190
+ </span>
191
+ <span v-if="showPrivacy">
192
+ By signing up you agree to the
193
+ <NuxtLink
194
+ :to="nuxtifyConfig.pages?.policies?.privacyUrl"
195
+ :class="`text-decoration-none ${
196
+ dark ? 'text-grey-lighten-2' : 'text-medium-emphasis'
197
+ }`"
198
+ >
199
+ Privacy Policy</NuxtLink>.
200
+ </span>
201
+ </div>
202
+ </v-form>
203
+
204
+ <!-- Thank You -->
205
+ <div
206
+ v-else
207
+ class="text-body-1"
208
+ >
209
+ {{ thankYouMessage }}
210
+ </div>
211
+ </template>
212
+
213
+ <style scoped>
214
+ :deep(.v-input__details){padding-inline:0}:deep(.v-text-field .v-input__details){padding-inline:0}a:hover{text-decoration:underline!important}
215
+ </style>
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ import { useNuxtifyConfig } from '#imports'
3
+
4
+ // App state
5
+ const nuxtifyConfig = useNuxtifyConfig()
6
+ </script>
7
+
8
+ <template>
9
+ <v-row
10
+ justify="center"
11
+ class="text-center my-4"
12
+ >
13
+ <v-col
14
+ cols="12"
15
+ md="7"
16
+ lg="6"
17
+ xl="4"
18
+ >
19
+ <div
20
+ v-if="nuxtifyConfig.footer?.cta?.title"
21
+ :class="`text-h5 text-${nuxtifyConfig.footer.cta.color}-lighten-4`"
22
+ >
23
+ {{ nuxtifyConfig.footer.cta.title }}
24
+ </div>
25
+ <div
26
+ v-if="nuxtifyConfig.footer?.cta?.subtitle"
27
+ :class="`text-subtitle-1 text-${nuxtifyConfig.footer.cta.color}-lighten-3 mb-4`"
28
+ >
29
+ {{ nuxtifyConfig.footer.cta.subtitle }}
30
+ </div>
31
+ <EmailSubscribeForm
32
+ v-if="nuxtifyConfig.email?.provider?.defaultSubmitUrl"
33
+ :submit-url="nuxtifyConfig.email.provider.defaultSubmitUrl"
34
+ dark
35
+ />
36
+ </v-col>
37
+ </v-row>
38
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import { useDisplay, computed, useNuxtifyConfig, isExternalUrl } from '#imports'
3
+
4
+ // App state
5
+ const nuxtifyConfig = useNuxtifyConfig()
6
+ const { xs } = useDisplay()
7
+
8
+ // Component state
9
+ const isExternalLink = computed(() =>
10
+ isExternalUrl(nuxtifyConfig.announcement?.buttonUrl ?? '', nuxtifyConfig.brand?.domain ?? ''),
11
+ )
12
+ </script>
13
+
14
+ <template>
15
+ <v-system-bar
16
+ :height="xs ? 60 : 40"
17
+ :order="-100"
18
+ color="primary"
19
+ class="justify-center text-start"
20
+ >
21
+ <div
22
+ v-if="nuxtifyConfig.announcement?.message"
23
+ :class="`${xs ? 'text-subtitle-2' : 'text-subtitle-1'} mr-4`"
24
+ >
25
+ {{ nuxtifyConfig.announcement.message }}
26
+ </div>
27
+ <v-btn
28
+ v-if="
29
+ nuxtifyConfig.announcement?.buttonText && nuxtifyConfig.announcement.buttonUrl
30
+ "
31
+ :to="!isExternalLink ? nuxtifyConfig.announcement.buttonUrl : undefined"
32
+ :href="isExternalLink ? nuxtifyConfig.announcement.buttonUrl : undefined"
33
+ size="small"
34
+ variant="flat"
35
+ color="secondary"
36
+ >
37
+ {{ nuxtifyConfig.announcement.buttonText }}
38
+ </v-btn>
39
+ </v-system-bar>
40
+ </template>
@@ -0,0 +1,128 @@
1
+ <script setup lang="ts">
2
+ import { useDisplay, useDrawer, useNuxtifyConfig, mdiArrowTopRight, mdiClose, mdiMenu } from '#imports'
3
+
4
+ // App state
5
+ const { smAndDown } = useDisplay()
6
+ const nuxtifyConfig = useNuxtifyConfig()
7
+ const drawer = useDrawer()
8
+
9
+ // Navigation
10
+ const primaryNavLinks = nuxtifyConfig.navigation?.primary
11
+ const secondaryNavLinks = nuxtifyConfig.navigation?.secondary
12
+ const featuredSecondaryLink = secondaryNavLinks?.slice(0, 1)[0] // first link gets featured
13
+ </script>
14
+
15
+ <template>
16
+ <v-app-bar
17
+ :density="smAndDown ? 'compact' : 'default'"
18
+ flat
19
+ class="px-sm-2 bottom-border"
20
+ >
21
+ <template #prepend>
22
+ <!-- Logo -->
23
+ <NuxtLink
24
+ to="/"
25
+ class="ml-2"
26
+ >
27
+ <AppLogo />
28
+ </NuxtLink>
29
+ </template>
30
+
31
+ <!-- Desktop navigation -->
32
+ <div v-if="!smAndDown">
33
+ <!-- Primary links -->
34
+ <v-btn
35
+ v-for="link in primaryNavLinks"
36
+ :key="link.text"
37
+ :to="link.to"
38
+ :href="link.href"
39
+ :active="false"
40
+ :prepend-icon="link.icon"
41
+ slim
42
+ exact
43
+ :ripple="false"
44
+ size="large"
45
+ color="unset"
46
+ :target="link.openInNew ? '_blank' : undefined"
47
+ :rel="link.openInNew ? 'noopener nofollow' : undefined"
48
+ class="nav-items mx-2"
49
+ >
50
+ {{ link.text }}
51
+ <v-icon
52
+ v-if="link.openInNew"
53
+ :icon="mdiArrowTopRight"
54
+ size="x-small"
55
+ color="grey"
56
+ class="ml-1"
57
+ />
58
+ </v-btn>
59
+ </div>
60
+
61
+ <template #append>
62
+ <!-- Mobile navigation -->
63
+ <v-app-bar-nav-icon
64
+ v-if="smAndDown"
65
+ :icon="drawer ? mdiClose : mdiMenu"
66
+ color="primary"
67
+ aria-label="Navigation Menu"
68
+ @click="drawer = !drawer"
69
+ />
70
+
71
+ <!-- Desktop navigation -->
72
+ <nav
73
+ v-else
74
+ class="d-flex align-center"
75
+ >
76
+ <!-- Secondary links -->
77
+ <v-btn
78
+ v-for="link in secondaryNavLinks?.slice(1).reverse()"
79
+ :key="link.text"
80
+ :to="link.to"
81
+ :href="link.href"
82
+ :prepend-icon="link.icon"
83
+ :active="false"
84
+ size="large"
85
+ color="unset"
86
+ :target="link.openInNew ? '_blank' : undefined"
87
+ :rel="link.openInNew ? 'noopener nofollow' : undefined"
88
+ class="nav-items mx-2"
89
+ >
90
+ {{ link.text }}
91
+ <v-icon
92
+ v-if="link.openInNew"
93
+ :icon="mdiArrowTopRight"
94
+ size="x-small"
95
+ color="grey"
96
+ class="ml-1"
97
+ />
98
+ </v-btn>
99
+
100
+ <!-- Featured secondary link -->
101
+ <v-btn
102
+ v-if="featuredSecondaryLink?.text"
103
+ :to="featuredSecondaryLink.to"
104
+ :href="featuredSecondaryLink.href"
105
+ :prepend-icon="featuredSecondaryLink.icon"
106
+ :active="false"
107
+ variant="flat"
108
+ size="large"
109
+ :target="featuredSecondaryLink.openInNew ? '_blank' : undefined"
110
+ :rel="featuredSecondaryLink.openInNew ? 'noopener nofollow' : undefined"
111
+ class="mx-2"
112
+ >
113
+ {{ featuredSecondaryLink.text }}
114
+ <v-icon
115
+ v-if="featuredSecondaryLink.openInNew"
116
+ :icon="mdiArrowTopRight"
117
+ size="small"
118
+ class="ml-1"
119
+ />
120
+ </v-btn>
121
+ </nav>
122
+ </template>
123
+ </v-app-bar>
124
+ </template>
125
+
126
+ <style scoped>
127
+ a{text-decoration:none}.nav-items:hover{color:rgb(var(--v-theme-secondary))!important}:deep(.v-btn--variant-text .v-btn__overlay){background-color:transparent}.bottom-border{border-bottom:1px solid #e7edf6}:deep(.v-toolbar__content){align-self:center;max-width:1280px}:deep(.v-toolbar__prepend){margin-inline:4px 12px}:deep(.v-btn__content){line-height:1}
128
+ </style>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ import { useDialog } from '#imports'
3
+
4
+ // App state
5
+ const dialog = useDialog()
6
+ </script>
7
+
8
+ <template>
9
+ <v-dialog
10
+ v-model="dialog.show"
11
+ width="500"
12
+ >
13
+ <v-card class="pa-2">
14
+ <v-card-title>{{ dialog.title }}</v-card-title>
15
+
16
+ <v-card-text class="ml-n2">
17
+ {{ dialog.message }}
18
+ </v-card-text>
19
+
20
+ <v-card-actions>
21
+ <v-spacer />
22
+
23
+ <v-btn
24
+ color="grey-darken-1"
25
+ @click="dialog.show = false"
26
+ >
27
+ {{ dialog.closeButtonText }}
28
+ </v-btn>
29
+
30
+ <v-btn
31
+ :color="dialog.action.buttonColor"
32
+ variant="flat"
33
+ @click="dialog.action.function"
34
+ >
35
+ {{ dialog.action.buttonText }}
36
+ </v-btn>
37
+ </v-card-actions>
38
+ </v-card>
39
+ </v-dialog>
40
+ </template>
@@ -0,0 +1,150 @@
1
+ <script setup lang="ts">
2
+ import { useNuxtifyConfig, mdiArrowTopRight } from '#imports'
3
+
4
+ // App state
5
+ const nuxtifyConfig = useNuxtifyConfig()
6
+
7
+ // Navigation
8
+ const footerPrimaryLinks = nuxtifyConfig.navigation?.footerPrimary
9
+ const footerSecondaryLinks = nuxtifyConfig.navigation?.footerSecondary
10
+ </script>
11
+
12
+ <template>
13
+ <v-row
14
+ class="px-sm-1 pt-12 pb-4 mb-1"
15
+ style="max-width: 1280px"
16
+ >
17
+ <v-col cols="12">
18
+ <FooterCallToAction v-if="nuxtifyConfig.footer?.cta?.show" />
19
+
20
+ <v-row class="mb-2">
21
+ <!-- Brand -->
22
+ <v-col
23
+ cols="12"
24
+ :lg="footerPrimaryLinks?.length === 4 ? 3 : 4"
25
+ >
26
+ <!-- Logo -->
27
+ <AppLogo dark />
28
+
29
+ <!-- Tagline -->
30
+ <p class="mt-2 clip-text">
31
+ {{ nuxtifyConfig.brand?.tagline }}
32
+ </p>
33
+ </v-col>
34
+
35
+ <v-spacer />
36
+
37
+ <!-- Primary Links -->
38
+ <v-col
39
+ v-for="group in footerPrimaryLinks"
40
+ :key="group.title"
41
+ cols="6"
42
+ md="3"
43
+ lg="2"
44
+ >
45
+ <p class="text-body-1 font-weight-bold mb-3">
46
+ {{ group.title }}
47
+ </p>
48
+ <div
49
+ v-for="link in group.links"
50
+ :key="link.text"
51
+ >
52
+ <v-btn
53
+ :to="link.to"
54
+ :href="link.href"
55
+ variant="text"
56
+ :active="false"
57
+ :ripple="false"
58
+ :target="link.openInNew ? '_blank' : undefined"
59
+ :rel="link.openInNew ? 'noopener nofollow' : undefined"
60
+ class="px-0"
61
+ >
62
+ {{ link.text }}
63
+ <v-icon
64
+ v-if="link.openInNew"
65
+ :icon="mdiArrowTopRight"
66
+ size="small"
67
+ color="grey"
68
+ class="ml-1"
69
+ />
70
+ </v-btn>
71
+ </div>
72
+ </v-col>
73
+ </v-row>
74
+
75
+ <v-row>
76
+ <v-col
77
+ cols="12"
78
+ sm="9"
79
+ >
80
+ <small>
81
+ <!-- Copyright -->
82
+ ©
83
+ {{
84
+ nuxtifyConfig.footer?.copyright
85
+ || nuxtifyConfig.brand?.name
86
+ || nuxtifyConfig.brand?.domain
87
+ }}.
88
+
89
+ <!-- Credits -->
90
+ {{ nuxtifyConfig.footer?.credits?.prependText }}
91
+ <span v-if="nuxtifyConfig.footer?.credits?.creator?.name">
92
+ <a
93
+ v-if="nuxtifyConfig.footer.credits.creator.domain"
94
+ :href="`https://${nuxtifyConfig.footer.credits.creator.domain}/?utm_source=${nuxtifyConfig.brand?.domain}&utm_medium=referral&utm_campaign=createdby`"
95
+ target="_blank"
96
+ rel="noopener nofollow"
97
+ class="font-weight-bold"
98
+ >{{ nuxtifyConfig.footer?.credits?.creator?.name }}</a><span v-else>{{ nuxtifyConfig.footer?.credits?.creator?.name }}</span>.
99
+ </span>
100
+
101
+ <!-- Message -->
102
+ {{ nuxtifyConfig.footer?.credits?.appendText }}
103
+
104
+ <!-- Powered By -->
105
+ <span v-if="nuxtifyConfig.footer?.credits?.showPoweredBy">
106
+ <a
107
+ :href="`https://nuxtify.dev/?utm_source=${nuxtifyConfig.brand?.domain}&utm_medium=referral&utm_campaign=poweredby`"
108
+ target="_blank"
109
+ rel="noopener nofollow"
110
+ >Powered by Nuxtify</a>.
111
+ </span>
112
+ </small>
113
+
114
+ <v-divider
115
+ v-if="footerSecondaryLinks?.length"
116
+ class="my-4"
117
+ style="width: 50px"
118
+ />
119
+
120
+ <!-- Secondary Links -->
121
+ <v-btn
122
+ v-for="link in footerSecondaryLinks"
123
+ :key="link.text"
124
+ :to="link.to"
125
+ :href="link.href"
126
+ variant="plain"
127
+ size="small"
128
+ :ripple="false"
129
+ :target="link.openInNew ? '_blank' : undefined"
130
+ :rel="link.openInNew ? 'noopener nofollow' : undefined"
131
+ class="text-capitalize pl-0 mb-2"
132
+ >
133
+ {{ link.text }}
134
+ <v-icon
135
+ v-if="link.openInNew"
136
+ :icon="mdiArrowTopRight"
137
+ size="small"
138
+ color="grey"
139
+ class="ml-1"
140
+ />
141
+ </v-btn>
142
+ </v-col>
143
+ </v-row>
144
+ </v-col>
145
+ </v-row>
146
+ </template>
147
+
148
+ <style scoped>
149
+ a{color:inherit;justify-content:start;text-decoration:none}.v-btn:hover,a:hover{text-decoration:underline;text-underline-offset:4px}:deep(.v-btn:hover>.v-btn__overlay){opacity:0}.clip-text{max-width:600px}
150
+ </style>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="center">
3
+ <div class="sk-chase">
4
+ <div class="sk-chase-dot" />
5
+ <div class="sk-chase-dot" />
6
+ <div class="sk-chase-dot" />
7
+ <div class="sk-chase-dot" />
8
+ <div class="sk-chase-dot" />
9
+ <div class="sk-chase-dot" />
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <style scoped>
15
+ .center{align-items:center;display:flex;height:100vh;justify-content:center}.sk-chase{animation:sk-chase 2.5s linear infinite both;height:40px;position:relative;width:40px}.sk-chase-dot{animation:sk-chase-dot 2s ease-in-out infinite both;height:100%;left:0;position:absolute;top:0;width:100%}.sk-chase-dot:before{animation:sk-chase-dot-before 2s ease-in-out infinite both;background-color:rgb(var(--v-theme-primary));border-radius:100%;content:"";display:block;height:25%;width:25%}.sk-chase-dot:first-child{animation-delay:-1.1s}.sk-chase-dot:nth-child(2){animation-delay:-1s}.sk-chase-dot:nth-child(3){animation-delay:-.9s}.sk-chase-dot:nth-child(4){animation-delay:-.8s}.sk-chase-dot:nth-child(5){animation-delay:-.7s}.sk-chase-dot:nth-child(6){animation-delay:-.6s}.sk-chase-dot:first-child:before{animation-delay:-1.1s}.sk-chase-dot:nth-child(2):before{animation-delay:-1s}.sk-chase-dot:nth-child(3):before{animation-delay:-.9s}.sk-chase-dot:nth-child(4):before{animation-delay:-.8s}.sk-chase-dot:nth-child(5):before{animation-delay:-.7s}.sk-chase-dot:nth-child(6):before{animation-delay:-.6s}@keyframes sk-chase{to{transform:rotate(1turn)}}@keyframes sk-chase-dot{80%,to{transform:rotate(1turn)}}@keyframes sk-chase-dot-before{50%{transform:scale(.4)}0%,to{transform:scale(1)}}
16
+ </style>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ import { useAppConfig, useDisplay, computed } from '#imports'
3
+
4
+ // Props
5
+ const props = defineProps({
6
+ dark: {
7
+ type: Boolean,
8
+ default: false,
9
+ },
10
+ width: {
11
+ type: Number,
12
+ default: undefined,
13
+ },
14
+ })
15
+
16
+ // App state
17
+ const nuxtifyConfig = useAppConfig().nuxtify
18
+ const { smAndDown } = useDisplay()
19
+
20
+ // Image URL logic
21
+ // Set default to the light image url
22
+ let imageUrl = nuxtifyConfig.brand.logo.lightUrl
23
+
24
+ // If it's dark theme logo and there's a dark image url, use that
25
+ if (props.dark && nuxtifyConfig.brand.logo.darkUrl) {
26
+ imageUrl = nuxtifyConfig.brand.logo.darkUrl
27
+ }
28
+
29
+ // Image width logic
30
+ const width = computed(() => {
31
+ if (props.width) {
32
+ return props.width
33
+ }
34
+ else {
35
+ return smAndDown.value
36
+ ? nuxtifyConfig.brand.logo.mobileWidth
37
+ : nuxtifyConfig.brand.logo.width
38
+ }
39
+ })
40
+ </script>
41
+
42
+ <template>
43
+ <v-img
44
+ v-if="imageUrl"
45
+ :width
46
+ :src="imageUrl"
47
+ :alt="`${nuxtifyConfig.brand.name} logo`"
48
+ />
49
+ <span
50
+ v-else
51
+ :class="`text-subtitle-1 text-sm-h6 ${dark ? '' : 'text-primary'}`"
52
+ >
53
+ {{ nuxtifyConfig.brand.name }}
54
+ </span>
55
+ </template>