@xenterprises/nuxt-x-marketing 0.0.23 → 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.
- package/.claude/settings.local.json +22 -0
- package/.playground/app.config.ts +44 -45
- package/.playground/app.vue +263 -0
- package/.playground/components/AppFooter.vue +103 -0
- package/.playground/components/AppHeader.vue +41 -0
- package/.playground/components/CodeExample.vue +70 -0
- package/.playground/components/ComponentPlayground.vue +117 -0
- package/.playground/components/DocsHeader.vue +19 -0
- package/.playground/layouts/default.vue +13 -3
- package/.playground/layouts/docs-layout.vue +161 -0
- package/.playground/layouts/docs.vue +11 -0
- package/.playground/package-lock.json +6 -0
- package/.playground/pages/blog/author.vue +145 -0
- package/.playground/pages/blog/card.vue +149 -0
- package/.playground/pages/blog/cta.vue +131 -0
- package/.playground/pages/blog/detail.vue +379 -0
- package/.playground/pages/blog/list.vue +261 -0
- package/.playground/pages/blog/navigation.vue +228 -0
- package/.playground/pages/blog/sidebar.vue +279 -0
- package/.playground/pages/config.vue +289 -0
- package/.playground/pages/content/comparison.vue +253 -0
- package/.playground/pages/content/cta.vue +245 -0
- package/.playground/pages/content/features.vue +217 -0
- package/.playground/pages/content/newsletter-section.vue +260 -0
- package/.playground/pages/content/newsletter.vue +200 -0
- package/.playground/pages/content/pricing.vue +335 -0
- package/.playground/pages/content/testimonials.vue +188 -0
- package/.playground/pages/hero.vue +257 -0
- package/.playground/pages/index.vue +379 -2
- package/.playground/pages/layout/footer.vue +210 -0
- package/.playground/pages/layout/legal-footer.vue +121 -0
- package/.playground/pages/layout/navbar.vue +285 -0
- package/.playground/pages/layout/section.vue +266 -0
- package/.playground/pages/layout/x-legal.vue +199 -0
- package/.playground/pages/modals/feature-modal.vue +320 -0
- package/.playground/pages/modals/image-lightbox.vue +277 -0
- package/.playground/pages/modals/video-modal.vue +222 -0
- package/.playground/pages/notifications/announcement-bar.vue +164 -0
- package/.playground/pages/notifications/cookie-banner.vue +210 -0
- package/.playground/pages/notifications/cookie-toast.vue +143 -0
- package/.playground/pages/notifications/gdpr.vue +276 -0
- package/.playground/pages/notifications/social-proof-toast.vue +255 -0
- package/.playground/pages/ui/badge.vue +161 -0
- package/.playground/pages/ui/bg-pattern.vue +177 -0
- package/.playground/pages/ui/glass-card.vue +179 -0
- package/.playground/pages/ui/glow-divider.vue +179 -0
- package/.playground/pages/ui/promo-card.vue +259 -0
- package/.playground/pages/ui/section-stitch.vue +411 -0
- package/.playground/pages/utilities/back-to-top.vue +153 -0
- package/.playground/pages/utilities/popup-chat.vue +224 -0
- package/README.md +799 -164
- package/app/app.config.ts +88 -2
- package/app/app.vue +106 -11
- package/app/assets/css/x-marketing.css +668 -0
- package/app/components/X/Footer/XLegal/index.vue +63 -26
- package/app/components/X/Footer/index.vue +89 -89
- package/app/components/X/Header/App.vue +63 -0
- package/app/components/X/Mark/Blog/Author.vue +170 -0
- package/app/components/X/Mark/Blog/CTANewsletter.vue +197 -0
- package/app/components/X/Mark/Blog/Card.vue +117 -0
- package/app/components/X/Mark/Blog/Detail.vue +240 -0
- package/app/components/X/Mark/Blog/List.vue +156 -0
- package/app/components/X/Mark/Blog/Navigation.vue +100 -0
- package/app/components/X/Mark/Blog/Sidebar.vue +160 -0
- package/app/components/X/Mark/Button/BackToTop.vue +99 -0
- package/app/components/X/Mark/CTA/index.vue +294 -0
- package/app/components/X/Mark/Card/Glass.vue +64 -0
- package/app/components/X/Mark/Card/Promo.vue +164 -0
- package/app/components/X/Mark/Divider/Glow.vue +113 -0
- package/app/components/X/Mark/Features/index.vue +194 -0
- package/app/components/X/Mark/Hero/index.vue +217 -0
- package/app/components/X/Mark/Layout/AnnouncementBar.vue +139 -0
- package/app/components/X/Mark/Layout/Footer.vue +160 -0
- package/app/components/X/Mark/Layout/FooterLegal.vue +42 -0
- package/app/components/X/Mark/Layout/Navbar.vue +267 -0
- package/app/components/X/Mark/Modal/Chat.vue +151 -0
- package/app/components/X/Mark/Modal/Feature.vue +197 -0
- package/app/components/X/Mark/Modal/Image.vue +226 -0
- package/app/components/X/Mark/Modal/Video.vue +203 -0
- package/app/components/X/Mark/Modal/index.vue +133 -0
- package/app/components/X/Mark/Newsletter/Form.vue +174 -0
- package/app/components/X/Mark/Pattern/Bg.vue +86 -0
- package/app/components/X/Mark/Pricing/Comparison.vue +126 -0
- package/app/components/X/Mark/Pricing/Plans.vue +181 -0
- package/app/components/X/Mark/Privacy/CookieBanner.vue +112 -0
- package/app/components/X/Mark/Privacy/CookieToast.vue +43 -0
- package/app/components/X/Mark/Privacy/GDPR.vue +245 -0
- package/app/components/X/Mark/Section/Newsletter.vue +183 -0
- package/app/components/X/Mark/Section/Stitch.vue +156 -0
- package/app/components/X/Mark/Section/index.vue +151 -0
- package/app/components/X/Mark/SocialProof/Badge.vue +102 -0
- package/app/components/X/Mark/SocialProof/Testimonials.vue +326 -0
- package/app/components/X/Mark/SocialProof/Toast.vue +136 -0
- package/app/components/X/Mark/_archive/Pattern/Background.vue +83 -0
- package/app/components/X/Mark/_archive/Pattern/CheckBg.vue +35 -0
- package/app/components/X/Mark/_archive/Pattern/CircuitBg.vue +41 -0
- package/app/components/X/Mark/_archive/Pattern/DotsBg.vue +35 -0
- package/app/components/X/Mark/_archive/Pattern/GlowDivider.vue +86 -0
- package/app/components/X/Mark/_archive/Pattern/HexBg.vue +35 -0
- package/app/components/X/Mark/_archive/Pattern/PlaidBg.vue +47 -0
- package/app/components/X/Mark/_archive/Pattern/SectionStitch.vue +168 -0
- package/app/components/X/Mark/_archive/Pattern/WaveBg.vue +41 -0
- package/app/components/X/_archive/Newsletter/Form.vue +123 -0
- package/app/components/X/_archive/Newsletter/Section.vue +131 -0
- package/app/composables/useParallax.ts +124 -0
- package/app/composables/useScrollReveal.ts +92 -0
- package/app/pages/index.vue +210 -0
- package/app/plugins/marketing.client.ts +13 -0
- package/app/types/marketing.d.ts +268 -0
- package/componentList.json +511 -0
- package/nuxt.config.ts +6 -13
- package/package.json +21 -9
- package/.editorconfig +0 -12
- package/app/assets/css/main.css +0 -2
- package/app/components/X/Marketing/Sections/EmailSubscription/index.vue +0 -129
|
@@ -2,45 +2,82 @@
|
|
|
2
2
|
<footer
|
|
3
3
|
class="bg-gray-900 text-gray-400 text-xs relative overflow-visible pt-2"
|
|
4
4
|
>
|
|
5
|
-
<a href="
|
|
5
|
+
<a v-if="resolvedLogo.src" :href="resolvedLogo.href" target="_blank">
|
|
6
6
|
<img
|
|
7
|
-
src="
|
|
8
|
-
alt="
|
|
7
|
+
:src="resolvedLogo.src"
|
|
8
|
+
:alt="resolvedLogo.alt"
|
|
9
9
|
class="h-6 w-6 mx-auto -mt-4 relative z-10"
|
|
10
10
|
/>
|
|
11
11
|
</a>
|
|
12
12
|
<main
|
|
13
|
-
class="flex flex-row justify-between items-center px-6 pt-3 pb-5 max-w-screen-2xl mx-auto"
|
|
13
|
+
class="flex flex-col md:flex-row justify-between items-center px-6 pt-3 pb-5 max-w-screen-2xl mx-auto gap-4"
|
|
14
14
|
>
|
|
15
|
-
<div class="
|
|
16
|
-
|
|
17
|
-
All Rights Reserved.
|
|
15
|
+
<div class="text-center md:text-left">
|
|
16
|
+
{{ resolvedCopyright }}
|
|
18
17
|
</div>
|
|
19
18
|
|
|
20
|
-
<
|
|
19
|
+
<nav v-if="resolvedLinks.length" class="text-center md:text-right flex flex-wrap justify-center gap-4">
|
|
21
20
|
<a
|
|
22
|
-
|
|
21
|
+
v-for="link in resolvedLinks"
|
|
22
|
+
:key="link.label"
|
|
23
|
+
:href="link.href"
|
|
23
24
|
class="hover:underline"
|
|
24
25
|
target="_blank"
|
|
25
26
|
>
|
|
26
|
-
|
|
27
|
+
{{ link.label }}
|
|
27
28
|
</a>
|
|
28
|
-
|
|
29
|
-
href="https://x.enterprises/legal/privacy-policy"
|
|
30
|
-
class="ml-4 hover:underline"
|
|
31
|
-
target="_blank"
|
|
32
|
-
>
|
|
33
|
-
Privacy Policy
|
|
34
|
-
</a>
|
|
35
|
-
<a
|
|
36
|
-
href="https://x.enterprises/legal/cookie-policy"
|
|
37
|
-
class="ml-4 hover:underline"
|
|
38
|
-
target="_blank"
|
|
39
|
-
>
|
|
40
|
-
Cookie Policy
|
|
41
|
-
</a>
|
|
42
|
-
</div>
|
|
29
|
+
</nav>
|
|
43
30
|
</main>
|
|
44
31
|
</footer>
|
|
45
32
|
</template>
|
|
46
|
-
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
const props = defineProps({
|
|
36
|
+
/** Copyright text (defaults to X Enterprises) */
|
|
37
|
+
copyright: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
/** Legal links array [{ label, href }] (defaults to X Enterprises legal pages) */
|
|
42
|
+
links: {
|
|
43
|
+
type: Array,
|
|
44
|
+
default: null,
|
|
45
|
+
},
|
|
46
|
+
/** Logo object { src, alt, href } (defaults to X Enterprises logo) */
|
|
47
|
+
logo: {
|
|
48
|
+
type: Object,
|
|
49
|
+
default: null,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Default X Enterprises values
|
|
54
|
+
const defaultLogo = {
|
|
55
|
+
src: 'https://cdn.x.enterprises/logo-x.png',
|
|
56
|
+
alt: 'X Enterprises',
|
|
57
|
+
href: 'https://x.enterprises',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const defaultLinks = [
|
|
61
|
+
{ label: 'Terms of Service', href: 'https://x.enterprises/legal/terms' },
|
|
62
|
+
{ label: 'Privacy Policy', href: 'https://x.enterprises/legal/privacy-policy' },
|
|
63
|
+
{ label: 'Cookie Policy', href: 'https://x.enterprises/legal/cookie-policy' },
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const defaultCopyright = `Copyright ©2016-${new Date().getFullYear()} X Enterprises, LLC. All Rights Reserved.`;
|
|
67
|
+
|
|
68
|
+
// Resolved values with defaults
|
|
69
|
+
const resolvedLogo = computed(() => {
|
|
70
|
+
if (props.logo === null) return defaultLogo;
|
|
71
|
+
if (props.logo === false) return { src: '', alt: '', href: '' };
|
|
72
|
+
return { ...defaultLogo, ...props.logo };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const resolvedLinks = computed(() => {
|
|
76
|
+
if (props.links === null) return defaultLinks;
|
|
77
|
+
return props.links;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const resolvedCopyright = computed(() => {
|
|
81
|
+
return props.copyright || defaultCopyright;
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
@@ -1,93 +1,93 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<XMarkSectionNewsletter
|
|
4
|
+
v-if="
|
|
5
|
+
appConfig?.xMarketing?.config?.emailMarketingNewsletters?.organizationId
|
|
6
|
+
"
|
|
7
|
+
title="Subscribe to our newsletter"
|
|
8
|
+
description="Get the latest news and updates."
|
|
9
|
+
@submit="(email) => console.log('Newsletter signup:', email)"
|
|
10
|
+
/>
|
|
11
|
+
<section
|
|
12
|
+
class="relative pt-24 overflow-hidden"
|
|
13
|
+
:class="footer?.bg?.color ? footer?.bg?.color : 'bg-gray-900'"
|
|
14
|
+
>
|
|
15
|
+
<img
|
|
16
|
+
class="absolute left-1/2 bottom-0 h-full transform -translate-x-1/2"
|
|
17
|
+
:src="footer?.bg?.img?.src"
|
|
18
|
+
:alt="footer?.bg?.img?.alt"
|
|
19
|
+
v-if="footer?.bg?.img?.src"
|
|
20
|
+
/>
|
|
21
|
+
<div
|
|
22
|
+
class="relative z-10 container mx-auto max-w-screen-2xl px-4 md:px-8"
|
|
23
|
+
>
|
|
24
|
+
<div class="pb-24">
|
|
25
|
+
<div
|
|
26
|
+
class="flex flex-col lg:flex-row lg:justify-between gap-8 lg:gap-12"
|
|
27
|
+
>
|
|
28
|
+
<div class="shrink-0 lg:max-w-xl">
|
|
29
|
+
<a class="mb-8 inline-block" href="/">
|
|
30
|
+
<img
|
|
31
|
+
:src="footer?.logo?.src"
|
|
32
|
+
:alt="footer?.logo?.alt"
|
|
33
|
+
class="h-6"
|
|
34
|
+
/>
|
|
35
|
+
</a>
|
|
36
|
+
<p
|
|
37
|
+
class="mb-8 text-gray-400 font-medium leading-relaxed max-w-lg mx-auto lg:mx-0"
|
|
38
|
+
>
|
|
39
|
+
{{ footer?.body }}
|
|
40
|
+
</p>
|
|
41
|
+
<div class="flex flex-row flex-wrap gap-2 justify-start">
|
|
42
|
+
<div
|
|
43
|
+
class="w-auto"
|
|
44
|
+
v-for="(social, index) in footer?.socials"
|
|
45
|
+
:key="index"
|
|
46
|
+
>
|
|
47
|
+
<UButton
|
|
48
|
+
:to="social.url"
|
|
49
|
+
:icon="social?.icon"
|
|
50
|
+
:alt="social?.name"
|
|
51
|
+
color="gray"
|
|
52
|
+
square
|
|
53
|
+
target="_blank"
|
|
54
|
+
variant="ghost"
|
|
55
|
+
>
|
|
56
|
+
</UButton>
|
|
57
|
+
</div>
|
|
58
|
+
<UColorModeButton />
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div
|
|
62
|
+
class="flex flex-col sm:flex-row gap-8 lg:gap-12 lg:justify-end"
|
|
63
|
+
>
|
|
64
|
+
<div
|
|
65
|
+
class="text-left sm:text-right"
|
|
66
|
+
v-for="(column, index) in footer.columns"
|
|
67
|
+
:key="index"
|
|
68
|
+
>
|
|
69
|
+
<h3
|
|
70
|
+
class="mb-6 text-sm text-gray-500 uppercase font-semibold leading-normal tracking-wide"
|
|
71
|
+
>
|
|
72
|
+
{{ column?.headerLabel }}
|
|
73
|
+
</h3>
|
|
74
|
+
<ul class="space-y-4">
|
|
75
|
+
<li v-for="link in column?.links" :key="link?.label">
|
|
76
|
+
<nuxt-link
|
|
77
|
+
:to="link?.to"
|
|
78
|
+
:target="link?.target ? link?.target : '_self'"
|
|
79
|
+
class="text-white hover:text-gray-300 font-medium leading-relaxed transition-colors"
|
|
80
|
+
>{{ link?.label }}</nuxt-link
|
|
81
|
+
>
|
|
82
|
+
</li>
|
|
83
|
+
</ul>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</section>
|
|
90
|
+
</div>
|
|
91
91
|
</template>
|
|
92
92
|
<script setup>
|
|
93
93
|
const appConfig = useAppConfig();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UHeader
|
|
3
|
+
:ui="{
|
|
4
|
+
root: headerClasses,
|
|
5
|
+
}"
|
|
6
|
+
>
|
|
7
|
+
<template #left>
|
|
8
|
+
<UNavigationMenu :items="navLinks" />
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<template #center>
|
|
12
|
+
<NuxtLink to="/" class="flex items-center">
|
|
13
|
+
<slot name="logo">
|
|
14
|
+
<span class="text-xl font-bold">LOGO</span>
|
|
15
|
+
</slot>
|
|
16
|
+
</NuxtLink>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template #right>
|
|
20
|
+
<UButton
|
|
21
|
+
v-if="secondaryCta"
|
|
22
|
+
:to="secondaryCta.to"
|
|
23
|
+
variant="ghost"
|
|
24
|
+
color="neutral"
|
|
25
|
+
:label="secondaryCta.label"
|
|
26
|
+
/>
|
|
27
|
+
<UButton
|
|
28
|
+
v-if="primaryCta"
|
|
29
|
+
:to="primaryCta.to"
|
|
30
|
+
variant="solid"
|
|
31
|
+
color="primary"
|
|
32
|
+
:label="primaryCta.label"
|
|
33
|
+
/>
|
|
34
|
+
</template>
|
|
35
|
+
</UHeader>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script setup>
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
navLinks: {
|
|
41
|
+
type: Array,
|
|
42
|
+
default: () => [],
|
|
43
|
+
},
|
|
44
|
+
primaryCta: {
|
|
45
|
+
type: Object,
|
|
46
|
+
default: null,
|
|
47
|
+
},
|
|
48
|
+
secondaryCta: {
|
|
49
|
+
type: Object,
|
|
50
|
+
default: null,
|
|
51
|
+
},
|
|
52
|
+
transparent: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const headerClasses = computed(() => {
|
|
59
|
+
return props.transparent
|
|
60
|
+
? "bg-transparent text-white"
|
|
61
|
+
: "bg-white dark:bg-deep-950";
|
|
62
|
+
});
|
|
63
|
+
</script>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Inline / Small variant -->
|
|
3
|
+
<div
|
|
4
|
+
v-if="variant === 'inline' || variant === 'sm'"
|
|
5
|
+
class="flex items-center gap-3"
|
|
6
|
+
>
|
|
7
|
+
<figure>
|
|
8
|
+
<img
|
|
9
|
+
v-if="author.avatar"
|
|
10
|
+
:src="author.avatar"
|
|
11
|
+
:alt="author.name"
|
|
12
|
+
:class="[
|
|
13
|
+
'rounded-full object-cover',
|
|
14
|
+
variant === 'sm' ? 'w-8 h-8' : 'w-10 h-10'
|
|
15
|
+
]"
|
|
16
|
+
/>
|
|
17
|
+
<div
|
|
18
|
+
v-else
|
|
19
|
+
:class="[
|
|
20
|
+
'rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center',
|
|
21
|
+
variant === 'sm' ? 'w-8 h-8' : 'w-10 h-10'
|
|
22
|
+
]"
|
|
23
|
+
>
|
|
24
|
+
<span class="text-primary-600 dark:text-primary-400 font-medium">
|
|
25
|
+
{{ author.name?.charAt(0) }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
</figure>
|
|
29
|
+
<div>
|
|
30
|
+
<p class="font-medium text-neutral-900 dark:text-white">
|
|
31
|
+
{{ author.name }}
|
|
32
|
+
</p>
|
|
33
|
+
<p v-if="author.title" class="text-sm text-neutral-500">
|
|
34
|
+
{{ author.title }}
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Full Bio variant -->
|
|
40
|
+
<div
|
|
41
|
+
v-else-if="variant === 'full'"
|
|
42
|
+
class="flex flex-col sm:flex-row gap-6 p-6 rounded-2xl bg-neutral-100 dark:bg-neutral-900"
|
|
43
|
+
>
|
|
44
|
+
<figure class="flex-shrink-0">
|
|
45
|
+
<img
|
|
46
|
+
v-if="author.avatar"
|
|
47
|
+
:src="author.avatar"
|
|
48
|
+
:alt="author.name"
|
|
49
|
+
class="w-20 h-20 rounded-full object-cover"
|
|
50
|
+
/>
|
|
51
|
+
<div
|
|
52
|
+
v-else
|
|
53
|
+
class="w-20 h-20 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center"
|
|
54
|
+
>
|
|
55
|
+
<span class="text-primary-600 dark:text-primary-400 font-medium text-2xl">
|
|
56
|
+
{{ author.name?.charAt(0) }}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
</figure>
|
|
60
|
+
<div class="flex-1">
|
|
61
|
+
<p class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
62
|
+
{{ author.name }}
|
|
63
|
+
</p>
|
|
64
|
+
<p v-if="author.title" class="text-sm text-neutral-500 mb-3">
|
|
65
|
+
{{ author.title }}
|
|
66
|
+
</p>
|
|
67
|
+
<p v-if="author.bio" class="text-neutral-600 dark:text-neutral-400">
|
|
68
|
+
{{ author.bio }}
|
|
69
|
+
</p>
|
|
70
|
+
|
|
71
|
+
<!-- Social Links -->
|
|
72
|
+
<div v-if="hasSocialLinks" class="flex gap-2 mt-4">
|
|
73
|
+
<UButton
|
|
74
|
+
v-for="(url, platform) in author.social"
|
|
75
|
+
:key="platform"
|
|
76
|
+
:to="url"
|
|
77
|
+
:icon="getSocialIcon(platform)"
|
|
78
|
+
variant="ghost"
|
|
79
|
+
color="neutral"
|
|
80
|
+
size="sm"
|
|
81
|
+
target="_blank"
|
|
82
|
+
:aria-label="platform"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Card variant -->
|
|
89
|
+
<div
|
|
90
|
+
v-else-if="variant === 'card'"
|
|
91
|
+
class="text-center p-6 rounded-2xl bg-neutral-100 dark:bg-neutral-900"
|
|
92
|
+
>
|
|
93
|
+
<figure class="mx-auto mb-4">
|
|
94
|
+
<img
|
|
95
|
+
v-if="author.avatar"
|
|
96
|
+
:src="author.avatar"
|
|
97
|
+
:alt="author.name"
|
|
98
|
+
class="w-24 h-24 rounded-full object-cover mx-auto ring-4 ring-white dark:ring-neutral-800"
|
|
99
|
+
/>
|
|
100
|
+
<div
|
|
101
|
+
v-else
|
|
102
|
+
class="w-24 h-24 rounded-full bg-primary-100 dark:bg-primary-900 flex items-center justify-center mx-auto"
|
|
103
|
+
>
|
|
104
|
+
<span class="text-primary-600 dark:text-primary-400 font-medium text-3xl">
|
|
105
|
+
{{ author.name?.charAt(0) }}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
</figure>
|
|
109
|
+
<p class="font-semibold text-lg text-neutral-900 dark:text-white">
|
|
110
|
+
{{ author.name }}
|
|
111
|
+
</p>
|
|
112
|
+
<p v-if="author.title" class="text-sm text-neutral-500 mt-1">
|
|
113
|
+
{{ author.title }}
|
|
114
|
+
</p>
|
|
115
|
+
<p v-if="author.bio" class="text-sm text-neutral-600 dark:text-neutral-400 mt-3">
|
|
116
|
+
{{ author.bio }}
|
|
117
|
+
</p>
|
|
118
|
+
|
|
119
|
+
<!-- Social Links -->
|
|
120
|
+
<div v-if="hasSocialLinks" class="flex justify-center gap-2 mt-4">
|
|
121
|
+
<UButton
|
|
122
|
+
v-for="(url, platform) in author.social"
|
|
123
|
+
:key="platform"
|
|
124
|
+
:to="url"
|
|
125
|
+
:icon="getSocialIcon(platform)"
|
|
126
|
+
variant="soft"
|
|
127
|
+
color="neutral"
|
|
128
|
+
size="sm"
|
|
129
|
+
target="_blank"
|
|
130
|
+
:aria-label="platform"
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</template>
|
|
135
|
+
|
|
136
|
+
<script setup>
|
|
137
|
+
const props = defineProps({
|
|
138
|
+
/** Author object */
|
|
139
|
+
author: {
|
|
140
|
+
type: Object,
|
|
141
|
+
required: true,
|
|
142
|
+
// { name, avatar?, title?, bio?, social?: { twitter?, linkedin?, github?, website? } }
|
|
143
|
+
},
|
|
144
|
+
/** Display variant */
|
|
145
|
+
variant: {
|
|
146
|
+
type: String,
|
|
147
|
+
default: 'inline',
|
|
148
|
+
validator: (v) => ['inline', 'sm', 'full', 'card'].includes(v),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const hasSocialLinks = computed(() => {
|
|
153
|
+
return props.author.social && Object.keys(props.author.social).length > 0;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const getSocialIcon = (platform) => {
|
|
157
|
+
const icons = {
|
|
158
|
+
twitter: 'i-lucide-twitter',
|
|
159
|
+
x: 'i-lucide-twitter',
|
|
160
|
+
linkedin: 'i-lucide-linkedin',
|
|
161
|
+
github: 'i-lucide-github',
|
|
162
|
+
website: 'i-lucide-globe',
|
|
163
|
+
instagram: 'i-lucide-instagram',
|
|
164
|
+
facebook: 'i-lucide-facebook',
|
|
165
|
+
youtube: 'i-lucide-youtube',
|
|
166
|
+
email: 'i-lucide-mail',
|
|
167
|
+
};
|
|
168
|
+
return icons[platform.toLowerCase()] || 'i-lucide-link';
|
|
169
|
+
};
|
|
170
|
+
</script>
|