@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
package/app/app.vue ADDED
@@ -0,0 +1,455 @@
1
+ <script setup>
2
+ import "./assets/css/index.css";
3
+ import { useMyelophoneConfig } from "~/utils/myelophoneConfig";
4
+
5
+ const nuxtApp = useNuxtApp();
6
+
7
+ nuxtApp.hook("app:mounted", () => {
8
+ console.info(
9
+ "%cBuilt on top of @myelophone/nuxt, licensed under the PolyForm Noncommercial License 1.0.0. Copyright © 2026 Aliaksandr Ivanou. https://aleksivanov.me/ \nExperiment, customize and enjoy the speed and the performance! With ❤️ from @myeloph.one. Necessariam et Sufficientem.",
10
+ "font-size:14px",
11
+ );
12
+ });
13
+
14
+ const {
15
+ multi18n,
16
+ siteDomain,
17
+ stores,
18
+ creativeCursor,
19
+ pageFullscreenPreloader,
20
+ siteSearch,
21
+ } = useMyelophoneConfig();
22
+
23
+ const globalFullscreenPreloaderOptions =
24
+ pageFullscreenPreloader &&
25
+ typeof pageFullscreenPreloader.component === "string" &&
26
+ pageFullscreenPreloader.component
27
+ ? pageFullscreenPreloader
28
+ : null;
29
+
30
+ const pinia = nuxtApp.$pinia;
31
+
32
+ const settingsStore = useSettingsStore(pinia);
33
+ settingsStore.init();
34
+
35
+ const userStore = stores.user ? useUserStore(pinia) : null;
36
+
37
+ const sessionErrorMessage = (error) =>
38
+ error instanceof Error ? error.message : String(error || "Unknown error");
39
+
40
+ async function extendUserSession() {
41
+ if (!userStore?.user) return;
42
+
43
+ try {
44
+ await userStore.extendSession();
45
+ } catch (error) {
46
+ console.warn("[extendUserSession] failed:", sessionErrorMessage);
47
+ }
48
+ }
49
+
50
+ const cartStore = stores.cart ? useCartStore(pinia) : null;
51
+
52
+ if (cartStore) {
53
+ await callOnce("cart:exchange-rates", async () => {
54
+ if (!cartStore.config.autoFetchRates || cartStore.ratesLoaded) return;
55
+
56
+ try {
57
+ await cartStore.fetchExchangeRates();
58
+ } catch {}
59
+ });
60
+ }
61
+
62
+ const router = useRouter();
63
+ const route = useRoute();
64
+
65
+ const htmlLang = computed(
66
+ () => settingsStore.currentLocale || settingsStore.defaultLocale || "en",
67
+ );
68
+
69
+ const supportedLocales = (multi18n?.locales ?? []).map((l) => l.toLowerCase());
70
+
71
+ watchEffect(() => {
72
+ const m = route.path.match(/^\/([a-z]{2})(?=\/|$)/i);
73
+ const candidate = m?.[1]?.toLowerCase();
74
+
75
+ const locale =
76
+ candidate && supportedLocales.includes(candidate)
77
+ ? candidate
78
+ : (multi18n?.defaultLocale ?? settingsStore.defaultLocale);
79
+
80
+ if (settingsStore.currentLocale !== locale) {
81
+ settingsStore.setLocale(locale);
82
+ }
83
+ });
84
+
85
+ onMounted(() => {
86
+ extendUserSession();
87
+ });
88
+
89
+ router.afterEach(() => {
90
+ extendUserSession();
91
+ });
92
+
93
+ const { t } = useMultiLang(["error", "commands"]);
94
+
95
+ const { isStaticMode } = useDevice();
96
+
97
+ const currentTheme = computed(() => settingsStore.theme);
98
+
99
+ const themeColor = ref(null);
100
+
101
+ onMounted(async () => {
102
+ await nextTick();
103
+
104
+ const getActualBg = () => {
105
+ return getComputedStyle(document.documentElement)
106
+ .getPropertyValue("--ui-bg")
107
+ .trim();
108
+ };
109
+
110
+ const updateColor = () => {
111
+ const color = getActualBg();
112
+ if (color) {
113
+ themeColor.value = color;
114
+ } else {
115
+ themeColor.value =
116
+ document.documentElement.getAttribute("data-theme") === "dark"
117
+ ? "#121212"
118
+ : "#f7f7f7";
119
+ }
120
+ };
121
+
122
+ updateColor();
123
+
124
+ const observer = new MutationObserver((mutations) => {
125
+ for (const mutation of mutations) {
126
+ if (mutation.attributeName === "data-theme") {
127
+ updateColor();
128
+ }
129
+ }
130
+ });
131
+
132
+ observer.observe(document.documentElement, {
133
+ attributes: true,
134
+ attributeFilter: ["data-theme"],
135
+ });
136
+ });
137
+
138
+ const toasterPosition = ref("bottom-center");
139
+ let mq;
140
+ const update = () => {
141
+ toasterPosition.value = mq.matches ? "bottom-center" : "bottom-right";
142
+ };
143
+ onMounted(() => {
144
+ mq = window.matchMedia("(max-width: 640px)");
145
+ update();
146
+ mq.addEventListener("change", update);
147
+ });
148
+ onBeforeUnmount(() => {
149
+ mq && mq.removeEventListener("change", update);
150
+ });
151
+
152
+ onNuxtReady(() => {
153
+ useStoreBroadcast(settingsStore, {
154
+ keys: ["theme", "cookiePreferences", "isCookieBannerVisible"],
155
+ });
156
+
157
+ if (userStore) {
158
+ userStore.initUser();
159
+ extendUserSession();
160
+ }
161
+
162
+ if (cartStore) {
163
+ cartStore.initCart();
164
+
165
+ useStoreBroadcast(cartStore, {
166
+ keys: ["items", "coupon", "meta", "currency"],
167
+ });
168
+ }
169
+ });
170
+
171
+ useHead({
172
+ htmlAttrs: {
173
+ lang: computed(() => htmlLang.value),
174
+ class: () =>
175
+ [
176
+ import.meta.client ? "js" : "nojs",
177
+ isStaticMode.value ? "is-static" : "",
178
+ ]
179
+ .filter(Boolean)
180
+ .join(" "),
181
+ },
182
+ meta: [
183
+ { name: "color-scheme", content: "dark light" },
184
+ {
185
+ key: "theme-color",
186
+ name: "theme-color",
187
+ content: () => themeColor.value,
188
+ },
189
+ ],
190
+ });
191
+
192
+ const breadcrumbsStore = useBreadcrumbs();
193
+ const error = useError();
194
+
195
+ const isNotFound = useState("is_404");
196
+ const { defaultSeo } = useAppConfig();
197
+ const has404 = computed(
198
+ () =>
199
+ isNotFound.value ||
200
+ error.value?.statusCode === 404 ||
201
+ error.value?.status === 404,
202
+ );
203
+
204
+ useHead({
205
+ titleTemplate: (title) =>
206
+ title || (has404.value ? undefined : defaultSeo.title),
207
+ });
208
+
209
+ useSeoMeta({
210
+ description: () => (has404.value ? undefined : defaultSeo.description),
211
+ });
212
+
213
+ useHead(
214
+ computed(() => {
215
+ const fullPath = route.path;
216
+
217
+ const match = fullPath.match(/^\/([a-z]{2})(?=\/|$)/i);
218
+ const urlLang = match?.[1]?.toLowerCase();
219
+ const currentLang =
220
+ urlLang && supportedLocales.includes(urlLang) ? urlLang : "en";
221
+
222
+ // TODO: if 404 - maybe should show error on the selected lang?
223
+
224
+ if (error.value?.status === 404 || isNotFound.value)
225
+ return { link: [], script: [] };
226
+
227
+ const baseOrigin = (siteDomain || "").replace(/\/+$/, "");
228
+ const origin = import.meta.client ? window.location.origin : baseOrigin;
229
+
230
+ const pathSegments = fullPath.split("/").filter(Boolean);
231
+ const cleanPath =
232
+ urlLang && supportedLocales.includes(urlLang)
233
+ ? "/" + pathSegments.slice(1).join("/")
234
+ : fullPath;
235
+
236
+ const finalPath = cleanPath.replace(/\/+$/, "") || "/";
237
+ const absoluteDefaultHref =
238
+ `${origin}${finalPath}`.replace(/\/+$/, "") || "/";
239
+ const isPostPath =
240
+ finalPath === "/post" || finalPath.startsWith("/post/");
241
+
242
+ const links = [];
243
+
244
+ if (!isPostPath) {
245
+ links.push({
246
+ key: "hr-x-default",
247
+ rel: "alternate",
248
+ hreflang: "x-default",
249
+ href: absoluteDefaultHref,
250
+ });
251
+
252
+ supportedLocales.forEach((l) => {
253
+ if (l === currentLang) return;
254
+
255
+ const langPrefix = l === "en" ? "" : `/${l}`;
256
+ const fullHref =
257
+ `${origin}${langPrefix}${finalPath}`.replace(/\/+$/, "") ||
258
+ "/";
259
+
260
+ links.push({
261
+ key: `hr-${l}`,
262
+ rel: "alternate",
263
+ hreflang: l,
264
+ href: fullHref,
265
+ });
266
+ });
267
+ }
268
+
269
+ const bList = unref(breadcrumbsStore.breadcrumbs) || [];
270
+ const schema = {
271
+ "@context": "https://schema.org",
272
+ "@type": "BreadcrumbList",
273
+ itemListElement: [
274
+ {
275
+ "@type": "ListItem",
276
+ position: 1,
277
+ name: unref(breadcrumbsStore.homeLabel) || "Home",
278
+ item: `${origin}${unref(breadcrumbsStore.homePath) || "/"}`,
279
+ },
280
+ ...bList.map((crumb, index) => ({
281
+ "@type": "ListItem",
282
+ position: index + 2,
283
+ name: crumb.title,
284
+ item: `${origin}${crumb.path}`,
285
+ })),
286
+ ],
287
+ };
288
+
289
+ return {
290
+ link: links,
291
+ script: [
292
+ {
293
+ key: "ld-json-bread",
294
+ type: "application/ld+json",
295
+ innerHTML: JSON.stringify(schema),
296
+ },
297
+ ],
298
+ };
299
+ }),
300
+ );
301
+
302
+ const CookieBanner = defineAsyncComponent(
303
+ () => import("~/components/cookie/Banner.vue"),
304
+ );
305
+
306
+ const CookieSettingsModal = defineAsyncComponent(
307
+ () => import("~/components/cookie/SettingsModal.vue"),
308
+ );
309
+
310
+ const SiteSearch = defineAsyncComponent(
311
+ () => import("~/components/ui/SiteSearch.vue"),
312
+ );
313
+
314
+ const BrowserWarning = defineAsyncComponent(
315
+ () => import("~/components/view/WarningOutdated.vue"),
316
+ );
317
+ const isLegacy = ref(false);
318
+ onMounted(() => {
319
+ isLegacy.value = !(
320
+ "fetch" in window &&
321
+ "Promise" in window &&
322
+ "IntersectionObserver" in window
323
+ );
324
+ });
325
+
326
+ const { initCookieConsent } = useCookieControl();
327
+ const { initScripts } = useScriptLoader();
328
+
329
+ initCookieConsent();
330
+ onMounted(() => {
331
+ initScripts();
332
+ });
333
+
334
+ const { open: openCookieSettings } = useCookieModal();
335
+
336
+ useCommand({
337
+ name: t("commands.setDarkTheme"),
338
+ hint: "Dark Mode",
339
+ keywords: ["dark", "ночь", "theme", "skin", "тема"],
340
+ action: () => settingsStore.setTheme("dark"),
341
+ });
342
+
343
+ useCommand({
344
+ name: t("commands.setLightTheme"),
345
+ hint: "Light Mode",
346
+ keywords: ["light", "день", "theme", "skin", "тема"],
347
+ action: () => settingsStore.setTheme("light"),
348
+ });
349
+ </script>
350
+
351
+ <template>
352
+ <a href="#main" class="skip-link">Go to main content</a>
353
+ <NuxtRouteAnnouncer />
354
+ <NuxtLayout>
355
+ <NuxtErrorBoundary>
356
+ <main id="main" tabindex="-1">
357
+ <NuxtPage :keepalive="{ max: 10 }" />
358
+ </main>
359
+ <template #error="{ error, clearError }">
360
+ <SeoNoIndex :status-code="error?.statusCode" />
361
+ <div class="error-container" role="alert">
362
+ <h1>{{ t("error.title") }}</h1>
363
+ <p v-if="error">{{ error.message }}</p>
364
+ <button @click="clearError({ redirect: '/' })">
365
+ {{ t("error.tryAgain") }}
366
+ </button>
367
+ </div>
368
+ </template>
369
+ </NuxtErrorBoundary>
370
+ </NuxtLayout>
371
+ <PageFullscreenPreloader
372
+ v-if="globalFullscreenPreloaderOptions"
373
+ :transparent="globalFullscreenPreloaderOptions.transparent"
374
+ :background="globalFullscreenPreloaderOptions.background"
375
+ :background-dark="globalFullscreenPreloaderOptions.backgroundDark"
376
+ :z-index="globalFullscreenPreloaderOptions.zIndex"
377
+ :aria-label="globalFullscreenPreloaderOptions.ariaLabel"
378
+ :class="globalFullscreenPreloaderOptions.class"
379
+ :content-class="globalFullscreenPreloaderOptions.contentClass"
380
+ :minimum-duration="globalFullscreenPreloaderOptions.minimumDuration"
381
+ >
382
+ <PageFullscreenPreloaderConfiguredContent
383
+ v-bind="globalFullscreenPreloaderOptions.props"
384
+ />
385
+ </PageFullscreenPreloader>
386
+ <Teleport to="#teleports">
387
+ <ClientOnly>
388
+ <BrowserWarning v-if="isLegacy" />
389
+ <PagePreloader />
390
+ <NuxtLoadingIndicator :height="1" color="#0078e7" :throttle="0" />
391
+ <UiCursorCreative v-if="creativeCursor" />
392
+ <CookieBanner @settings="openCookieSettings" />
393
+ <CookieSettingsModal />
394
+ <UiCommandPalette />
395
+ <UiScrollToTop />
396
+ <SiteSearch
397
+ v-if="siteSearch.enabled"
398
+ :strategy="siteSearch.strategy"
399
+ :endpoint="siteSearch.endpoint"
400
+ :min-query-length="siteSearch.minQueryLength"
401
+ :limit="siteSearch.limit"
402
+ :operator="siteSearch.operator"
403
+ :query-param="siteSearch.queryParam"
404
+ :mode-param="siteSearch.modeParam"
405
+ :operator-param="siteSearch.operatorParam"
406
+ :url-mode="siteSearch.urlMode"
407
+ :consume-url="siteSearch.consumeUrl"
408
+ />
409
+ <Toaster
410
+ :position="toasterPosition"
411
+ :theme="currentTheme"
412
+ :key="`${currentTheme}-${toasterPosition}`"
413
+ richColors
414
+ :toastOptions="{
415
+ style: {
416
+ paddingTop: '12px',
417
+ paddingBottom: '15px',
418
+ fontSize: '0.9rem',
419
+ },
420
+ }"
421
+ />
422
+ <UiSonnerStyle />
423
+ </ClientOnly>
424
+ </Teleport>
425
+ </template>
426
+
427
+ <style scoped>
428
+ .error-container {
429
+ padding: 2rem;
430
+ text-align: center;
431
+ border-radius: 8px;
432
+ margin: 2rem auto;
433
+ max-width: 600px;
434
+ }
435
+
436
+ button {
437
+ margin-top: 1rem;
438
+ padding: 0.5rem 1rem;
439
+ cursor: pointer;
440
+ }
441
+
442
+ .skip-link {
443
+ position: absolute;
444
+ left: -9999px;
445
+ }
446
+
447
+ .skip-link:focus {
448
+ left: 1rem;
449
+ top: 1rem;
450
+ background: #000;
451
+ color: #fff;
452
+ padding: 0.5rem 1rem;
453
+ z-index: 1000;
454
+ }
455
+ </style>
@@ -0,0 +1,81 @@
1
+ .js .reveal-active {
2
+ --reveal-duration: 700ms;
3
+ opacity: 0;
4
+ visibility: hidden;
5
+ will-change: opacity, transform;
6
+ transition:
7
+ opacity 0.4s ease-out,
8
+ transform 0.4s ease-out,
9
+ visibility 0.4s step-end;
10
+ }
11
+
12
+ .js .reveal-active.is-visible {
13
+ opacity: 1;
14
+ visibility: visible;
15
+ transform: translate(0, 0) scale(1);
16
+ transition:
17
+ opacity var(--reveal-duration) cubic-bezier(0.2, 0.8, 0.2, 1),
18
+ transform var(--reveal-duration) cubic-bezier(0.2, 0.8, 0.2, 1),
19
+ visibility 0s linear 0s;
20
+ transition-delay: var(--reveal-delay, 0ms);
21
+ }
22
+
23
+ .js .reveal-fast {
24
+ --reveal-duration: 400ms;
25
+ }
26
+ .js .reveal-slow {
27
+ --reveal-duration: 1200ms;
28
+ }
29
+
30
+ .js .reveal-slide {
31
+ transform: translateY(40px);
32
+ }
33
+
34
+ .js .reveal-slide-down {
35
+ transform: translateY(-40px);
36
+ }
37
+
38
+ .js .reveal-slide-left {
39
+ transform: translateX(40px);
40
+ }
41
+
42
+ .js .reveal-slide-right {
43
+ transform: translateX(-40px);
44
+ }
45
+
46
+ .js .reveal-zoom {
47
+ transform: scale(0.9);
48
+ }
49
+
50
+ .js .reveal-zoom-out {
51
+ transform: scale(1.1);
52
+ }
53
+
54
+ .js .reveal-flip {
55
+ transform: perspective(1000px) rotateX(-30deg);
56
+ transform-origin: top;
57
+ }
58
+
59
+ .js .reveal-fade {
60
+ transform: none;
61
+ }
62
+
63
+ @media (max-width: 768px) {
64
+ .js .reveal-active {
65
+ --reveal-duration: 500ms;
66
+ }
67
+ }
68
+
69
+ @media (prefers-reduced-motion: reduce) {
70
+ .js .reveal-active {
71
+ transition: none;
72
+ transform: none;
73
+ }
74
+
75
+ .js .reveal-active.is-visible {
76
+ opacity: 1;
77
+ visibility: visible;
78
+ transform: none;
79
+ transition: none;
80
+ }
81
+ }
@@ -0,0 +1,57 @@
1
+ .layout-container-root,
2
+ .layout-row-root,
3
+ .layout-col-root,
4
+ .layout-stack-root {
5
+ background-repeat: no-repeat;
6
+ background-position: center;
7
+ }
8
+
9
+ .layout-container-root.has-gradient-overlay::before,
10
+ .layout-row-root.has-gradient-overlay::before,
11
+ .layout-col-root.has-gradient-overlay::before,
12
+ .layout-stack-root.has-gradient-overlay::before {
13
+ content: '';
14
+ position: absolute;
15
+ inset: 0;
16
+ z-index: 1;
17
+ pointer-events: none;
18
+ }
19
+
20
+ .layout-container-root > *:not(video):not(.video-background-container),
21
+ .layout-row-root > *:not(video):not(.video-background-container),
22
+ .layout-col-root > *:not(video):not(.video-background-container),
23
+ .layout-stack-root > *:not(video):not(.video-background-container) {
24
+ position: relative;
25
+ z-index: 1;
26
+ }
27
+
28
+ .video-background-container {
29
+ position: absolute;
30
+ inset: 0;
31
+ z-index: 0;
32
+ pointer-events: none;
33
+ overflow: hidden;
34
+ }
35
+
36
+ .video-background-container iframe {
37
+ position: absolute;
38
+ top: 50%;
39
+ left: 50%;
40
+ width: 115vw;
41
+ height: 64.68vw;
42
+ min-height: 115vh;
43
+ min-width: 204.44vh;
44
+ transform: translate(-50%, -50%);
45
+ pointer-events: none;
46
+ background: transparent;
47
+ }
48
+
49
+ @media (max-width: 767px) {
50
+ .video-background-container iframe {
51
+ height: 115vh;
52
+ height: 100%;
53
+ width: 204.44vh;
54
+ min-width: 115vw;
55
+ min-height: 64.68vw;
56
+ }
57
+ }