@myelophone/nuxt 0.18.2

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 (259) hide show
  1. package/.devcontainer/Dockerfile +7 -0
  2. package/.devcontainer/devcontainer.json +21 -0
  3. package/.editorconfig +43 -0
  4. package/.github/workflows/package.yml +73 -0
  5. package/.nuxtignore +6 -0
  6. package/.releaserc.js +132 -0
  7. package/.yarn/releases/yarn-4.18.0.cjs +1000 -0
  8. package/.yarnrc.yml +8 -0
  9. package/CHANGELOG.md +141 -0
  10. package/LICENSE +77 -0
  11. package/README.md +3004 -0
  12. package/app/app.vue +455 -0
  13. package/app/assets/css/animation.css +81 -0
  14. package/app/assets/css/backgrounds.css +57 -0
  15. package/app/assets/css/base.css +182 -0
  16. package/app/assets/css/index.css +9 -0
  17. package/app/assets/css/links.css +12 -0
  18. package/app/assets/css/multimedia.css +102 -0
  19. package/app/assets/css/preloader.css +68 -0
  20. package/app/assets/css/print.css +40 -0
  21. package/app/assets/css/tailwind.css +20 -0
  22. package/app/assets/css/theme.css +29 -0
  23. package/app/components/consent/GoogleMap.vue +59 -0
  24. package/app/components/consent/Youtube.vue +27 -0
  25. package/app/components/cookie/Banner.vue +70 -0
  26. package/app/components/cookie/ConsentWrapper.vue +71 -0
  27. package/app/components/cookie/PrivacyPolicy.vue +179 -0
  28. package/app/components/cookie/SettingsModal.vue +387 -0
  29. package/app/components/grid/Col.vue +262 -0
  30. package/app/components/grid/Container.vue +236 -0
  31. package/app/components/grid/Row.vue +248 -0
  32. package/app/components/grid/Stack.vue +233 -0
  33. package/app/components/myelophone/Copyright.vue +5 -0
  34. package/app/components/myelophone/Welcome.vue +107 -0
  35. package/app/components/page/FullscreenPreloader.vue +177 -0
  36. package/app/components/page/Preloader.vue +18 -0
  37. package/app/components/page/ReadyLocalized.vue +19 -0
  38. package/app/components/ready/.gitkeep +0 -0
  39. package/app/components/safe/Email.vue +52 -0
  40. package/app/components/safe/ImgWithLoader.vue +180 -0
  41. package/app/components/safe/NuxtImg.vue +25 -0
  42. package/app/components/safe/NuxtPicture.vue +29 -0
  43. package/app/components/section/.gitkeep +0 -0
  44. package/app/components/seo/BrandInfo.vue +76 -0
  45. package/app/components/seo/ContentNoIndex.vue +35 -0
  46. package/app/components/seo/NoIndex.vue +22 -0
  47. package/app/components/seo/OrgInfo.vue +130 -0
  48. package/app/components/seo/ProfileInfo.vue +137 -0
  49. package/app/components/ui/Accordion.vue +163 -0
  50. package/app/components/ui/Alert.vue +179 -0
  51. package/app/components/ui/Avatar.vue +88 -0
  52. package/app/components/ui/Badge.vue +150 -0
  53. package/app/components/ui/Banner.vue +133 -0
  54. package/app/components/ui/Button.vue +175 -0
  55. package/app/components/ui/Card.vue +66 -0
  56. package/app/components/ui/Checkbox.vue +134 -0
  57. package/app/components/ui/Chip.vue +136 -0
  58. package/app/components/ui/CommandPalette.vue +434 -0
  59. package/app/components/ui/Console.vue +583 -0
  60. package/app/components/ui/CursorCreative.vue +148 -0
  61. package/app/components/ui/FeatureAccordion.vue +165 -0
  62. package/app/components/ui/FileUpload.vue +705 -0
  63. package/app/components/ui/GeoDependent.vue +72 -0
  64. package/app/components/ui/Heading.vue +51 -0
  65. package/app/components/ui/Icon.vue +42 -0
  66. package/app/components/ui/Icon8.vue +39 -0
  67. package/app/components/ui/Input.vue +88 -0
  68. package/app/components/ui/LangLink.vue +34 -0
  69. package/app/components/ui/LangVisible.vue +33 -0
  70. package/app/components/ui/LanguageSelect.vue +202 -0
  71. package/app/components/ui/LightBox.vue +155 -0
  72. package/app/components/ui/LocalTime.vue +170 -0
  73. package/app/components/ui/Modal.vue +161 -0
  74. package/app/components/ui/Popup.vue +200 -0
  75. package/app/components/ui/PostContent.server.vue +23 -0
  76. package/app/components/ui/ProtectedEmail.vue +52 -0
  77. package/app/components/ui/RelativeTime.vue +141 -0
  78. package/app/components/ui/ScheduledContent.vue +150 -0
  79. package/app/components/ui/ScrollToTop.client.vue +48 -0
  80. package/app/components/ui/SegmentedControl.vue +148 -0
  81. package/app/components/ui/SiteSearch.vue +1191 -0
  82. package/app/components/ui/Skeleton.vue +91 -0
  83. package/app/components/ui/SmartContrast.vue +74 -0
  84. package/app/components/ui/SnapContainer.vue +222 -0
  85. package/app/components/ui/SnapSection.vue +294 -0
  86. package/app/components/ui/SonnerStyle.vue +7 -0
  87. package/app/components/ui/SplitSection.vue +99 -0
  88. package/app/components/ui/Steps.vue +858 -0
  89. package/app/components/ui/StickyWrapper.vue +46 -0
  90. package/app/components/ui/Table.vue +251 -0
  91. package/app/components/ui/Tabs.vue +451 -0
  92. package/app/components/ui/TextColumn.vue +36 -0
  93. package/app/components/ui/Textarea.vue +251 -0
  94. package/app/components/ui/Toggler.vue +40 -0
  95. package/app/components/ui/Tooltip.vue +524 -0
  96. package/app/components/ui/TruncateText.vue +105 -0
  97. package/app/components/ui/ViewportSpacer.vue +72 -0
  98. package/app/components/view/AnimatedCounter.vue +121 -0
  99. package/app/components/view/AnnouncementBar.vue +104 -0
  100. package/app/components/view/AvatarGroup.vue +104 -0
  101. package/app/components/view/Breadcrumbs.vue +97 -0
  102. package/app/components/view/ContentWithToc.vue +152 -0
  103. package/app/components/view/Copyright.vue +68 -0
  104. package/app/components/view/CountdownTimer.vue +184 -0
  105. package/app/components/view/CreativeCTA.vue +143 -0
  106. package/app/components/view/CurrencySelect.vue +254 -0
  107. package/app/components/view/FeatureBoxGrid.vue +142 -0
  108. package/app/components/view/HorizontalMenu.vue +97 -0
  109. package/app/components/view/ImageCompare.vue +141 -0
  110. package/app/components/view/ImgSlider.vue +333 -0
  111. package/app/components/view/InfiniteMarquee.vue +147 -0
  112. package/app/components/view/InfoBar.vue +86 -0
  113. package/app/components/view/LogoGrid.vue +106 -0
  114. package/app/components/view/LogoSlider.vue +83 -0
  115. package/app/components/view/NotFound.vue +81 -0
  116. package/app/components/view/PartialBackground.vue +313 -0
  117. package/app/components/view/QuoteBig.vue +111 -0
  118. package/app/components/view/ResponsiveMenu.vue +219 -0
  119. package/app/components/view/ReviewsSlider.vue +230 -0
  120. package/app/components/view/SocialBar.vue +78 -0
  121. package/app/components/view/StickyHeadingContent.vue +158 -0
  122. package/app/components/view/TallyForm.vue +295 -0
  123. package/app/components/view/WarningOutdated.vue +113 -0
  124. package/app/composables/useApi.ts +88 -0
  125. package/app/composables/useAppSeo.ts +78 -0
  126. package/app/composables/useBreadcrumbs.ts +106 -0
  127. package/app/composables/useChangeUrl.ts +73 -0
  128. package/app/composables/useCommand.ts +28 -0
  129. package/app/composables/useCookieControl.ts +111 -0
  130. package/app/composables/useCookieModal.ts +9 -0
  131. package/app/composables/useDevice.ts +109 -0
  132. package/app/composables/useGeoFetch.ts +139 -0
  133. package/app/composables/useLoadScript.ts +36 -0
  134. package/app/composables/useModalState.ts +37 -0
  135. package/app/composables/useMultiLang.ts +278 -0
  136. package/app/composables/useNetwork.ts +49 -0
  137. package/app/composables/useNuxtVersion.ts +5 -0
  138. package/app/composables/usePiniaStore.ts +5 -0
  139. package/app/composables/usePopupControl.ts +57 -0
  140. package/app/composables/useQuery.ts +307 -0
  141. package/app/composables/useReadyPage.ts +182 -0
  142. package/app/composables/useSafeList.ts +90 -0
  143. package/app/composables/useScriptLoader.ts +221 -0
  144. package/app/composables/useSiteSearch.ts +32 -0
  145. package/app/composables/useSnapScroll.ts +24 -0
  146. package/app/composables/useStatic.ts +5 -0
  147. package/app/composables/useStorage.ts +252 -0
  148. package/app/composables/useStoreBroadcast.ts +146 -0
  149. package/app/composables/useThemeSync.ts +81 -0
  150. package/app/constants/layout.ts +109 -0
  151. package/app/constants/predefinedCookieScripts.ts +1222 -0
  152. package/app/constants/ui-events.ts +1 -0
  153. package/app/error.vue +169 -0
  154. package/app/layouts/default-blog.vue +5 -0
  155. package/app/layouts/default.vue +3 -0
  156. package/app/locales/en/commands.json +5 -0
  157. package/app/locales/en/cookies.json +182 -0
  158. package/app/locales/en/error.json +4 -0
  159. package/app/locales/en/interface.json +33 -0
  160. package/app/locales/en/myelophone.json +8 -0
  161. package/app/locales/en/outdated.json +5 -0
  162. package/app/locales/en/times.json +18 -0
  163. package/app/locales/pl/commands.json +5 -0
  164. package/app/locales/pl/cookies.json +182 -0
  165. package/app/locales/pl/error.json +4 -0
  166. package/app/locales/pl/interface.json +33 -0
  167. package/app/locales/pl/myelophone.json +8 -0
  168. package/app/locales/pl/outdated.json +5 -0
  169. package/app/locales/pl/times.json +18 -0
  170. package/app/locales/ru/commands.json +5 -0
  171. package/app/locales/ru/cookies.json +182 -0
  172. package/app/locales/ru/error.json +4 -0
  173. package/app/locales/ru/interface.json +33 -0
  174. package/app/locales/ru/myelophone.json +8 -0
  175. package/app/locales/ru/outdated.json +5 -0
  176. package/app/locales/ru/times.json +18 -0
  177. package/app/middleware/blog-layout.ts +28 -0
  178. package/app/middleware/i18n.global.ts +17 -0
  179. package/app/middleware/not-found-status.ts +11 -0
  180. package/app/modules/default-blog/index.ts +39 -0
  181. package/app/modules/default-blog/posts.ts +80 -0
  182. package/app/modules/default-sitemap/index.ts +116 -0
  183. package/app/modules/default-sitemap/runtime/sitemap.get.ts +42 -0
  184. package/app/modules/fullscreen-preloader/module.ts +43 -0
  185. package/app/modules/htaccess/.htaccess +38 -0
  186. package/app/modules/htaccess/index.ts +23 -0
  187. package/app/modules/i18n-shaker.mjs +221 -0
  188. package/app/modules/multi18n/module.ts +71 -0
  189. package/app/modules/myelophone-config/module.ts +241 -0
  190. package/app/pages/[...all].vue +9 -0
  191. package/app/pages/index.vue +3 -0
  192. package/app/pages/post/[...slug].vue +30 -0
  193. package/app/plugins/bfcache-refresh.client.ts +9 -0
  194. package/app/plugins/canonical.ts +30 -0
  195. package/app/plugins/csp-nonce.server.ts +41 -0
  196. package/app/plugins/multi18n.client.ts +21 -0
  197. package/app/plugins/multi18n.server.ts +15 -0
  198. package/app/plugins/nuxt-version.client.ts +4 -0
  199. package/app/plugins/restore.client.ts +21 -0
  200. package/app/plugins/reveal.ts +89 -0
  201. package/app/plugins/url-helper.client.ts +66 -0
  202. package/app/server/api/exchange-rates.get.ts +216 -0
  203. package/app/server/api/healthz.ts +1 -0
  204. package/app/server/api/logout.post.ts +6 -0
  205. package/app/server/middleware/normalize-url.ts +14 -0
  206. package/app/server/middleware/security-headers.ts +147 -0
  207. package/app/server/middleware/url-helper.ts +43 -0
  208. package/app/server/plugins/clean-html-spaces.ts +15 -0
  209. package/app/server/plugins/clean-styles.ts +94 -0
  210. package/app/server/plugins/compression.ts +122 -0
  211. package/app/server/plugins/css-preload.ts +26 -0
  212. package/app/server/plugins/early-hints.ts +31 -0
  213. package/app/server/plugins/force-noindex.ts +43 -0
  214. package/app/server/plugins/html-one-line.ts +17 -0
  215. package/app/server/plugins/inline-style.ts +7 -0
  216. package/app/server/routes/healthz.get.ts +1 -0
  217. package/app/server/tsconfig.json +3 -0
  218. package/app/stores/cart.ts +945 -0
  219. package/app/stores/commands.ts +43 -0
  220. package/app/stores/device.ts +156 -0
  221. package/app/stores/querySync.ts +5 -0
  222. package/app/stores/settings.ts +145 -0
  223. package/app/stores/user.ts +607 -0
  224. package/app/types/cookie.ts +89 -0
  225. package/app/types/myelophone.d.ts +87 -0
  226. package/app/types/site-search.ts +110 -0
  227. package/app/utils/asyncRefresh.ts +4 -0
  228. package/app/utils/blog.server.ts +142 -0
  229. package/app/utils/jsonLd.ts +56 -0
  230. package/app/utils/myelophoneConfig.ts +4 -0
  231. package/app/utils/sortBy.ts +18 -0
  232. package/app/utils/transliterate.ts +75 -0
  233. package/app/utils/ytVideo.ts +37 -0
  234. package/app/workers/site-search.worker.ts +661 -0
  235. package/biome.json +94 -0
  236. package/commitlint.config.js +3 -0
  237. package/nuxt.config.ts +539 -0
  238. package/package.json +101 -0
  239. package/playground/content/.gitkeep +0 -0
  240. package/playground/locales/.gitkeep +0 -0
  241. package/playground/myelophone.dev.ts +45 -0
  242. package/playground/nuxt.config.ts +105 -0
  243. package/playground/package.json +5 -0
  244. package/playground/pages/.gitkeep +0 -0
  245. package/playground/tsconfig.json +3 -0
  246. package/playwright.config.ts +23 -0
  247. package/public/browsers/chrome.svg +77 -0
  248. package/public/browsers/edge.svg +98 -0
  249. package/public/browsers/firefox.svg +300 -0
  250. package/public/favicon.ico +0 -0
  251. package/public/myelophone_eng.png +0 -0
  252. package/public/myelophone_eng_white.png +0 -0
  253. package/spa-loading-template.html +72 -0
  254. package/tests/home.spec.ts +9 -0
  255. package/tests/utils.ts +23 -0
  256. package/tsconfig.json +16 -0
  257. package/vite/plugins/clean-css.ts +190 -0
  258. package/vite/plugins/postcss-add-viewportunits.ts +33 -0
  259. package/vite/plugins/postcss-viewport-fallback.ts +49 -0
@@ -0,0 +1,130 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ enabled?: boolean;
4
+ context?: string;
5
+ type?: string | string[];
6
+ id?: string;
7
+ name?: string;
8
+ alternateName?: string | string[];
9
+ legalName?: string;
10
+ description?: string;
11
+ url?: string;
12
+ logo?: string | JsonLdNode;
13
+ image?: string | JsonLdNode | JsonLdValue[];
14
+ sameAs?: string[];
15
+ email?: string;
16
+ telephone?: string;
17
+ address?: string | JsonLdNode | JsonLdValue[];
18
+ contactPoint?: JsonLdNode | JsonLdValue[];
19
+ founder?: JsonLdNode | JsonLdValue[];
20
+ foundingDate?: string;
21
+ foundingLocation?: string | JsonLdNode;
22
+ employee?: JsonLdNode | JsonLdValue[];
23
+ numberOfEmployees?: number | JsonLdNode;
24
+ parentOrganization?: JsonLdNode;
25
+ subOrganization?: JsonLdNode | JsonLdValue[];
26
+ department?: JsonLdNode | JsonLdValue[];
27
+ member?: JsonLdNode | JsonLdValue[];
28
+ memberOf?: JsonLdNode | JsonLdValue[];
29
+ owns?: JsonLdNode | JsonLdValue[];
30
+ brand?: JsonLdNode | JsonLdValue[];
31
+ slogan?: string;
32
+ areaServed?: string | JsonLdNode | JsonLdValue[];
33
+ award?: string | string[];
34
+ knowsAbout?: string | JsonLdNode | JsonLdValue[];
35
+ keywords?: string | string[];
36
+ taxID?: string;
37
+ vatID?: string;
38
+ duns?: string;
39
+ globalLocationNumber?: string;
40
+ iso6523Code?: string;
41
+ leiCode?: string;
42
+ naics?: string;
43
+ nonprofitStatus?: string;
44
+ aggregateRating?: JsonLdNode;
45
+ review?: JsonLdNode | JsonLdValue[];
46
+ makesOffer?: JsonLdNode | JsonLdValue[];
47
+ seeks?: JsonLdNode | JsonLdValue[];
48
+ hasOfferCatalog?: JsonLdNode;
49
+ hasMerchantReturnPolicy?: JsonLdNode | JsonLdValue[];
50
+ hasShippingService?: JsonLdNode | JsonLdValue[];
51
+ graph?: JsonLdNode[];
52
+ data?: JsonLdNode;
53
+ scriptKey?: string;
54
+ scriptId?: string;
55
+ }
56
+
57
+ const props = withDefaults(defineProps<Props>(), {
58
+ enabled: true,
59
+ context: "https://schema.org",
60
+ type: "Organization",
61
+ data: () => ({}),
62
+ });
63
+
64
+ const instanceId = useId();
65
+ const schema = computed<JsonLdNode | null>(() => {
66
+ if (!props.enabled) return null;
67
+
68
+ const record: JsonLdNode = {
69
+ ...props.data,
70
+ "@context": props.context,
71
+ "@type": props.type,
72
+ ...compactJsonLd({
73
+ "@id": props.id,
74
+ name: props.name,
75
+ alternateName: props.alternateName,
76
+ legalName: props.legalName,
77
+ description: props.description,
78
+ url: props.url,
79
+ logo: props.logo,
80
+ image: props.image,
81
+ sameAs: props.sameAs,
82
+ email: props.email,
83
+ telephone: props.telephone,
84
+ address: props.address,
85
+ contactPoint: props.contactPoint,
86
+ founder: props.founder,
87
+ foundingDate: props.foundingDate,
88
+ foundingLocation: props.foundingLocation,
89
+ employee: props.employee,
90
+ numberOfEmployees: props.numberOfEmployees,
91
+ parentOrganization: props.parentOrganization,
92
+ subOrganization: props.subOrganization,
93
+ department: props.department,
94
+ member: props.member,
95
+ memberOf: props.memberOf,
96
+ owns: props.owns,
97
+ brand: props.brand,
98
+ slogan: props.slogan,
99
+ areaServed: props.areaServed,
100
+ award: props.award,
101
+ knowsAbout: props.knowsAbout,
102
+ keywords: props.keywords,
103
+ taxID: props.taxID,
104
+ vatID: props.vatID,
105
+ duns: props.duns,
106
+ globalLocationNumber: props.globalLocationNumber,
107
+ iso6523Code: props.iso6523Code,
108
+ leiCode: props.leiCode,
109
+ naics: props.naics,
110
+ nonprofitStatus: props.nonprofitStatus,
111
+ aggregateRating: props.aggregateRating,
112
+ review: props.review,
113
+ makesOffer: props.makesOffer,
114
+ seeks: props.seeks,
115
+ hasOfferCatalog: props.hasOfferCatalog,
116
+ hasMerchantReturnPolicy: props.hasMerchantReturnPolicy,
117
+ hasShippingService: props.hasShippingService,
118
+ }),
119
+ };
120
+
121
+ return wrapJsonLdGraph(record, props.graph);
122
+ });
123
+
124
+ useJsonLd(schema, {
125
+ key: computed(() => props.scriptKey ?? `seo-org-info-${instanceId}`),
126
+ id: computed(() => props.scriptId),
127
+ });
128
+ </script>
129
+
130
+ <template />
@@ -0,0 +1,137 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ enabled?: boolean;
4
+ context?: string;
5
+ type?: string | string[];
6
+ id?: string;
7
+ url?: string;
8
+ name?: string;
9
+ headline?: string;
10
+ description?: string;
11
+ dateCreated?: string;
12
+ dateModified?: string;
13
+ datePublished?: string;
14
+ inLanguage?: string | string[];
15
+ image?: string | JsonLdNode | JsonLdValue[];
16
+ isPartOf?: string | JsonLdNode;
17
+ breadcrumb?: string | JsonLdNode;
18
+ hasPart?: JsonLdNode | JsonLdValue[];
19
+ about?: JsonLdNode | JsonLdValue[];
20
+ author?: JsonLdNode | JsonLdValue[];
21
+ publisher?: JsonLdNode;
22
+ mainEntity?: JsonLdNode;
23
+ entityOnly?: boolean;
24
+ entity?: JsonLdNode;
25
+ entityType?: "Person" | "Organization" | string;
26
+ entityId?: string;
27
+ entityName?: string;
28
+ alternateName?: string | string[];
29
+ identifier?: string | JsonLdNode | JsonLdValue[];
30
+ entityDescription?: string;
31
+ entityImage?: string | JsonLdNode | JsonLdValue[];
32
+ entityUrl?: string;
33
+ sameAs?: string[];
34
+ jobTitle?: string;
35
+ email?: string;
36
+ telephone?: string;
37
+ address?: string | JsonLdNode | JsonLdValue[];
38
+ founder?: JsonLdNode | JsonLdValue[];
39
+ owns?: JsonLdNode | JsonLdValue[];
40
+ worksFor?: JsonLdNode | JsonLdValue[];
41
+ knowsAbout?: string | JsonLdNode | JsonLdValue[];
42
+ knowsLanguage?: string | JsonLdNode | JsonLdValue[];
43
+ interactionStatistic?: JsonLdNode | JsonLdValue[];
44
+ agentInteractionStatistic?: JsonLdNode | JsonLdValue[];
45
+ graph?: JsonLdNode[];
46
+ data?: JsonLdNode;
47
+ scriptKey?: string;
48
+ scriptId?: string;
49
+ }
50
+
51
+ const props = withDefaults(defineProps<Props>(), {
52
+ enabled: true,
53
+ context: "https://schema.org",
54
+ type: "ProfilePage",
55
+ entityType: "Person",
56
+ entityOnly: false,
57
+ entity: () => ({}),
58
+ data: () => ({}),
59
+ });
60
+
61
+ const instanceId = useId();
62
+ const generatedEntity = computed<JsonLdNode>(() => ({
63
+ ...props.entity,
64
+ "@type": props.entityType,
65
+ ...compactJsonLd({
66
+ "@id": props.entityId ?? (props.entityOnly ? props.id : undefined),
67
+ name: props.entityName ?? (props.entityOnly ? props.name : undefined),
68
+ alternateName: props.alternateName,
69
+ identifier: props.identifier,
70
+ description:
71
+ props.entityDescription ??
72
+ (props.entityOnly ? props.description : undefined),
73
+ image:
74
+ props.entityImage ?? (props.entityOnly ? props.image : undefined),
75
+ url: props.entityUrl ?? (props.entityOnly ? props.url : undefined),
76
+ sameAs: props.sameAs,
77
+ jobTitle: props.jobTitle,
78
+ email: props.email,
79
+ telephone: props.telephone,
80
+ address: props.address,
81
+ founder: props.founder,
82
+ owns: props.owns,
83
+ worksFor: props.worksFor,
84
+ knowsAbout: props.knowsAbout,
85
+ knowsLanguage: props.knowsLanguage,
86
+ interactionStatistic: props.interactionStatistic,
87
+ agentInteractionStatistic: props.agentInteractionStatistic,
88
+ }),
89
+ }));
90
+
91
+ const schema = computed<JsonLdNode | null>(() => {
92
+ if (!props.enabled) return null;
93
+ if (props.entityOnly) {
94
+ return wrapJsonLdGraph(
95
+ {
96
+ "@context": props.context,
97
+ ...generatedEntity.value,
98
+ },
99
+ props.graph,
100
+ );
101
+ }
102
+
103
+ const record: JsonLdNode = {
104
+ ...props.data,
105
+ "@context": props.context,
106
+ "@type": props.type,
107
+ ...compactJsonLd({
108
+ "@id": props.id,
109
+ url: props.url,
110
+ name: props.name,
111
+ headline: props.headline,
112
+ description: props.description,
113
+ dateCreated: props.dateCreated,
114
+ dateModified: props.dateModified,
115
+ datePublished: props.datePublished,
116
+ inLanguage: props.inLanguage,
117
+ image: props.image,
118
+ isPartOf: props.isPartOf,
119
+ breadcrumb: props.breadcrumb,
120
+ hasPart: props.hasPart,
121
+ about: props.about,
122
+ author: props.author,
123
+ publisher: props.publisher,
124
+ mainEntity: props.mainEntity ?? generatedEntity.value,
125
+ }),
126
+ };
127
+
128
+ return wrapJsonLdGraph(record, props.graph);
129
+ });
130
+
131
+ useJsonLd(schema, {
132
+ key: computed(() => props.scriptKey ?? `seo-profile-info-${instanceId}`),
133
+ id: computed(() => props.scriptId),
134
+ });
135
+ </script>
136
+
137
+ <template />
@@ -0,0 +1,163 @@
1
+ <script setup lang="ts">
2
+ interface AccordionItem {
3
+ label: string;
4
+ content: string;
5
+ defaultOpen?: boolean;
6
+ [key: string]: any;
7
+ }
8
+
9
+ interface AccordionClasses {
10
+ wrapper?: string;
11
+ item?: string;
12
+ header?: string;
13
+ headerActive?: string;
14
+ headerIcon?: string;
15
+ contentWrapper?: string;
16
+ content?: string;
17
+ }
18
+
19
+ const props = withDefaults(
20
+ defineProps<{
21
+ items: AccordionItem[];
22
+ multiple?: boolean;
23
+ faq?: boolean;
24
+ ui?: AccordionClasses;
25
+ }>(),
26
+ {
27
+ multiple: false,
28
+ faq: false,
29
+ ui: () => ({}),
30
+ },
31
+ );
32
+
33
+ const openIndices = ref<number[]>(
34
+ props.items
35
+ .map((item, index) => (item.defaultOpen ? index : -1))
36
+ .filter((i) => i !== -1),
37
+ );
38
+
39
+ const toggle = (index: number) => {
40
+ if (props.multiple) {
41
+ if (openIndices.value.includes(index)) {
42
+ openIndices.value = openIndices.value.filter((i) => i !== index);
43
+ } else {
44
+ openIndices.value.push(index);
45
+ }
46
+ } else {
47
+ if (openIndices.value.includes(index)) {
48
+ openIndices.value = [];
49
+ } else {
50
+ openIndices.value = [index];
51
+ }
52
+ }
53
+ };
54
+
55
+ const isOpen = (index: number) => openIndices.value.includes(index);
56
+
57
+ if (props.faq) {
58
+ const faqSchema = computed(() => ({
59
+ "@context": "https://schema.org",
60
+ "@type": "FAQPage",
61
+ mainEntity: props.items.map((item) => ({
62
+ "@type": "Question",
63
+ name: item.label,
64
+ acceptedAnswer: {
65
+ "@type": "Answer",
66
+ text: item.content,
67
+ },
68
+ })),
69
+ }));
70
+
71
+ useHead(() => ({
72
+ script: [
73
+ {
74
+ type: "application/ld+json",
75
+ key: "faq-schema",
76
+ innerHTML: JSON.stringify(faqSchema.value),
77
+ },
78
+ ],
79
+ }));
80
+ }
81
+
82
+ const layoutContentWrapper =
83
+ "grid transition-[grid-template-rows] duration-300 ease-out";
84
+ const layoutHeader =
85
+ "flex items-center justify-between w-full text-left cursor-pointer select-none pt-[0.8rem]";
86
+
87
+ const defaultWrapper = "space-y-4";
88
+ const defaultItem =
89
+ "border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 overflow-hidden";
90
+ const defaultHeader =
91
+ "p-4 font-semibold text-neutral-800 dark:text-neutral-100 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors";
92
+ const defaultHeaderActive = "bg-neutral-50 dark:bg-neutral-800 text-primary";
93
+ const defaultIcon =
94
+ "w-5 h-5 text-neutral-400 transition-transform duration-300";
95
+ const defaultContent =
96
+ "p-4 text-neutral-600 dark:text-neutral-400 leading-relaxed border-t border-transparent";
97
+ </script>
98
+
99
+ <template>
100
+ <div :class="ui.wrapper || defaultWrapper">
101
+ <div
102
+ v-for="(item, index) in items"
103
+ :key="index"
104
+ :class="ui.item || defaultItem"
105
+ >
106
+ <button
107
+ type="button"
108
+ @click="toggle(index)"
109
+ :class="[
110
+ layoutHeader,
111
+ ui.header || defaultHeader,
112
+ isOpen(index) ? ui.headerActive || defaultHeaderActive : '',
113
+ ]"
114
+ :aria-expanded="isOpen(index)"
115
+ >
116
+ <slot
117
+ name="header"
118
+ :item="item"
119
+ :index="index"
120
+ :is-open="isOpen(index)"
121
+ >
122
+ <span>{{ item.label }}</span>
123
+ </slot>
124
+
125
+ <span
126
+ :class="[
127
+ ui.headerIcon || defaultIcon,
128
+ isOpen(index) ? 'rotate-180' : '',
129
+ ]"
130
+ >
131
+ <svg
132
+ xmlns="http://www.w3.org/2000/svg"
133
+ viewBox="0 0 24 24"
134
+ fill="none"
135
+ stroke="currentColor"
136
+ stroke-width="2"
137
+ stroke-linecap="round"
138
+ stroke-linejoin="round"
139
+ class="w-full h-full"
140
+ >
141
+ <polyline points="6 9 12 15 18 9"></polyline>
142
+ </svg>
143
+ </span>
144
+ </button>
145
+
146
+ <div
147
+ :class="[
148
+ layoutContentWrapper,
149
+ ui.contentWrapper,
150
+ isOpen(index) ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
151
+ ]"
152
+ >
153
+ <div class="overflow-hidden">
154
+ <div :class="ui.content || defaultContent">
155
+ <slot name="content" :item="item" :index="index">
156
+ <div v-html="item.content" />
157
+ </slot>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </template>
@@ -0,0 +1,179 @@
1
+ <script setup lang="ts">
2
+ const props = withDefaults(
3
+ defineProps<{
4
+ variant?: "info" | "success" | "warning" | "error";
5
+
6
+ title?: string;
7
+ description?: string;
8
+
9
+ icon?: string;
10
+ avatar?: {
11
+ src?: string;
12
+ icon?: string;
13
+ alt?: string;
14
+ };
15
+
16
+ size?: "sm" | "md" | "lg";
17
+ closable?: boolean;
18
+ rounded?: boolean;
19
+
20
+ avatarSize?: AvatarSize;
21
+ }>(),
22
+ {
23
+ variant: "info",
24
+ size: "md",
25
+ closable: false,
26
+ rounded: true,
27
+ },
28
+ );
29
+
30
+ const visible = ref(true);
31
+
32
+ const slots = useSlots();
33
+
34
+ type AvatarSize = "sm" | "md" | "lg";
35
+
36
+ const avatarSize = computed<AvatarSize>(() => {
37
+ if (props.avatarSize) return props.avatarSize;
38
+ if (slots.actions) return "lg";
39
+ if (props.description || slots.default) return "md";
40
+ return "sm";
41
+ });
42
+
43
+ const avatarSizes: Record<AvatarSize, string> = {
44
+ sm: "h-8 w-8",
45
+ md: "h-10 w-10",
46
+ lg: "h-14 w-14",
47
+ };
48
+
49
+ const avatarPadding: Record<AvatarSize, string> = {
50
+ sm: "px-2",
51
+ md: "px-3",
52
+ lg: "px-4",
53
+ };
54
+
55
+ const close = () => {
56
+ visible.value = false;
57
+ };
58
+
59
+ const styles = computed(() => {
60
+ const map = {
61
+ info: {
62
+ base: "bg-blue-50 text-blue-900",
63
+ border: "border-blue-200",
64
+ accent: "text-blue-600",
65
+ },
66
+ success: {
67
+ base: "bg-emerald-50 text-emerald-900",
68
+ border: "border-emerald-200",
69
+ accent: "text-emerald-600",
70
+ },
71
+ warning: {
72
+ base: "bg-amber-50 text-amber-900",
73
+ border: "border-amber-200",
74
+ accent: "text-amber-600",
75
+ },
76
+ error: {
77
+ base: "bg-red-50 text-red-900",
78
+ border: "border-red-200",
79
+ accent: "text-red-600",
80
+ },
81
+ };
82
+ return map[props.variant];
83
+ });
84
+
85
+ const sizes = computed(
86
+ () =>
87
+ ({
88
+ sm: {
89
+ container: "p-3 gap-3 text-sm",
90
+ icon: "h-4 w-4",
91
+ avatar: "w-12",
92
+ },
93
+ md: {
94
+ container: "p-4 gap-4 text-sm",
95
+ icon: "h-5 w-5",
96
+ avatar: "w-14",
97
+ },
98
+ lg: {
99
+ container: "p-5 gap-5 text-base",
100
+ icon: "h-6 w-6",
101
+ avatar: "w-16",
102
+ },
103
+ })[props.size],
104
+ );
105
+ </script>
106
+
107
+ <template>
108
+ <div
109
+ v-if="visible"
110
+ role="alert"
111
+ :class="[
112
+ avatar ? 'grid grid-cols-[auto,1fr]' : 'block',
113
+ 'relative border',
114
+ styles.base,
115
+ styles.border,
116
+ sizes.container,
117
+ rounded ? 'rounded-xl' : 'rounded-none',
118
+ ]"
119
+ >
120
+ <div
121
+ v-if="avatar"
122
+ class="self-stretch flex items-center justify-center"
123
+ :class="avatarPadding[avatarSize]"
124
+ >
125
+ <div
126
+ :class="[
127
+ 'flex items-center justify-center',
128
+ avatarSizes[avatarSize],
129
+ ]"
130
+ >
131
+ <SafeImgWithLoader
132
+ v-if="avatar.src"
133
+ :src="avatar.src"
134
+ :alt="avatar.alt || ''"
135
+ class="h-full w-full object-contain rounded-full"
136
+ />
137
+
138
+ <UiIcon
139
+ v-else-if="avatar.icon"
140
+ :name="avatar.icon"
141
+ class="h-full w-full"
142
+ />
143
+ </div>
144
+ </div>
145
+
146
+ <div class="flex flex-col justify-center gap-1 min-w-0">
147
+ <div
148
+ v-if="$slots.title || title"
149
+ class="font-semibold leading-tight"
150
+ >
151
+ <span v-if="icon" class="inline-flex align-middle mr-2">
152
+ <UiIcon :name="icon" :class="[sizes.icon, styles.accent]" />
153
+ </span>
154
+
155
+ <slot name="title">{{ title }}</slot>
156
+ </div>
157
+
158
+ <div
159
+ v-if="$slots.default || description"
160
+ class="leading-relaxed opacity-90"
161
+ >
162
+ <slot>{{ description }}</slot>
163
+ </div>
164
+
165
+ <div v-if="$slots.actions" class="mt-2">
166
+ <slot name="actions" />
167
+ </div>
168
+ </div>
169
+
170
+ <button
171
+ v-if="closable"
172
+ @click="close"
173
+ class="absolute top-3 right-3 opacity-60 hover:opacity-100 transition"
174
+ aria-label="Close alert"
175
+ >
176
+ <UiIcon name="heroicons:x-mark" class="h-5 w-5" />
177
+ </button>
178
+ </div>
179
+ </template>
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ src?: string;
4
+ alt?: string;
5
+ name?: string;
6
+ icon?: string;
7
+ text?: string;
8
+ size?: "sm" | "md" | "lg" | "xl";
9
+ chipColor?: string;
10
+ chipPosition?: "top-right" | "bottom-right" | "top-left" | "bottom-left";
11
+ class?: any;
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ size: "md",
16
+ chipPosition: "top-right",
17
+ });
18
+
19
+ const initials = computed(() => {
20
+ if (props.text) return props.text.slice(0, 2).toUpperCase();
21
+ if (!props.name) return "";
22
+ const parts = props.name.trim().split(/\s+/);
23
+ if (parts.length >= 2) {
24
+ const first = parts[0]?.[0] || "";
25
+ const second = parts[1]?.[0] || "";
26
+ return (first + second).toUpperCase();
27
+ }
28
+ return parts[0]?.slice(0, 2).toUpperCase() || "";
29
+ });
30
+
31
+ const sizeClasses = {
32
+ sm: "w-8 h-8 text-[10px]",
33
+ md: "w-10 h-10 text-xs",
34
+ lg: "w-12 h-12 text-sm",
35
+ xl: "w-16 h-16 text-base",
36
+ };
37
+
38
+ const chipPositionClasses = {
39
+ "top-right": "-top-0.5 -right-0.5",
40
+ "bottom-right": "-bottom-0.5 -right-0.5",
41
+ "top-left": "-top-0.5 -left-0.5",
42
+ "bottom-left": "-bottom-0.5 -left-0.5",
43
+ };
44
+ </script>
45
+
46
+ <template>
47
+ <div
48
+ :class="[
49
+ 'relative inline-flex shrink-0 items-center justify-center select-none rounded-full',
50
+ sizeClasses[size],
51
+ props.class,
52
+ ]"
53
+ >
54
+ <div
55
+ class="absolute inset-0 rounded-full overflow-hidden bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-500 dark:text-zinc-400 font-medium"
56
+ >
57
+ <span v-if="initials && !src" class="uppercase">
58
+ {{ initials }}
59
+ </span>
60
+
61
+ <UiIcon v-else-if="icon" :name="icon" class="w-1/2 h-1/2" />
62
+
63
+ <SafeImgWithLoader
64
+ v-else-if="src"
65
+ :src="src"
66
+ :alt="alt || name || 'avatar'"
67
+ class="absolute inset-0 h-full w-full object-cover"
68
+ loading="lazy"
69
+ />
70
+
71
+ <UiIcon
72
+ v-else
73
+ name="i-heroicons-user-20-solid"
74
+ class="w-1/2 h-1/2 opacity-50"
75
+ />
76
+ </div>
77
+
78
+ <span
79
+ v-if="chipColor"
80
+ :class="[
81
+ 'absolute block rounded-full border-2 border-white dark:border-zinc-900 z-10',
82
+ size === 'sm' ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5',
83
+ chipPositionClasses[chipPosition],
84
+ ]"
85
+ :style="{ backgroundColor: chipColor }"
86
+ ></span>
87
+ </div>
88
+ </template>