@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,583 @@
1
+ <script setup lang="ts">
2
+ import type { CSSProperties } from "vue";
3
+
4
+ type ConsoleTone =
5
+ "default" | "muted" | "primary" | "info" | "success" | "warning" | "error";
6
+ type ConsoleId = string | number;
7
+
8
+ interface ConsoleSegment {
9
+ text: string;
10
+ tone?: ConsoleTone;
11
+ highlight?: boolean;
12
+ class?: string;
13
+ }
14
+
15
+ interface ConsoleLine {
16
+ id?: ConsoleId;
17
+ text?: string;
18
+ prefix?: string;
19
+ segments?: ConsoleSegment[];
20
+ tone?: ConsoleTone;
21
+ highlight?: boolean;
22
+ delay?: number;
23
+ hideAfter?: number;
24
+ class?: string;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ interface ConsoleClasses {
29
+ root?: string;
30
+ header?: string;
31
+ body?: string;
32
+ line?: string;
33
+ cursor?: string;
34
+ }
35
+
36
+ interface Props {
37
+ lines: Array<ConsoleLine | string>;
38
+ autoplay?: boolean;
39
+ lineDelay?: number;
40
+ startDelay?: number;
41
+ loop?: boolean;
42
+ loopDelay?: number;
43
+ chrome?: boolean;
44
+ title?: string;
45
+ cursor?: boolean;
46
+ autoScroll?: boolean;
47
+ minHeight?: string;
48
+ maxHeight?: string;
49
+ ariaLabel?: string;
50
+ ui?: ConsoleClasses;
51
+ }
52
+
53
+ const props = withDefaults(defineProps<Props>(), {
54
+ autoplay: true,
55
+ lineDelay: 400,
56
+ startDelay: 0,
57
+ loop: false,
58
+ loopDelay: 1200,
59
+ chrome: true,
60
+ title: "",
61
+ cursor: false,
62
+ autoScroll: true,
63
+ minHeight: "12rem",
64
+ maxHeight: "32rem",
65
+ ariaLabel: "Console output",
66
+ ui: () => ({}),
67
+ });
68
+
69
+ const emit = defineEmits<{
70
+ "line-show": [line: ConsoleLine, index: number];
71
+ "line-hide": [line: ConsoleLine, index: number];
72
+ complete: [];
73
+ }>();
74
+
75
+ const normalizedLines = computed<ConsoleLine[]>(() =>
76
+ props.lines.map((line) =>
77
+ typeof line === "string" ? { text: line } : line,
78
+ ),
79
+ );
80
+ const visibleIndices = ref<number[]>(
81
+ normalizedLines.value.map((_, index) => index),
82
+ );
83
+ const body = useTemplateRef<HTMLElement>("body");
84
+ const playing = ref(false);
85
+ const pendingClientPlayback = ref(true);
86
+ const timers = new Set<ReturnType<typeof setTimeout>>();
87
+ let mounted = false;
88
+
89
+ type ConsoleStyles = CSSProperties & Record<`--ui-console-${string}`, string>;
90
+
91
+ const rootStyles = computed<ConsoleStyles>(() => ({
92
+ "--ui-console-min-height": props.minHeight,
93
+ "--ui-console-max-height": props.maxHeight,
94
+ }));
95
+
96
+ const visibleLines = computed(() =>
97
+ visibleIndices.value.flatMap((index) => {
98
+ const line = normalizedLines.value[index];
99
+ return line ? [{ line, index }] : [];
100
+ }),
101
+ );
102
+
103
+ const clearTimers = () => {
104
+ for (const timer of timers) clearTimeout(timer);
105
+ timers.clear();
106
+ };
107
+
108
+ const schedule = (callback: () => void, delay: number) => {
109
+ const timer = setTimeout(
110
+ () => {
111
+ timers.delete(timer);
112
+ callback();
113
+ },
114
+ Math.max(0, delay),
115
+ );
116
+ timers.add(timer);
117
+ };
118
+
119
+ const scrollToLatest = () => {
120
+ if (!props.autoScroll) return;
121
+ nextTick(() => {
122
+ const element = body.value;
123
+ if (element) element.scrollTop = element.scrollHeight;
124
+ });
125
+ };
126
+
127
+ const showLine = (index: number) => {
128
+ const line = normalizedLines.value[index];
129
+ if (!line || visibleIndices.value.includes(index)) return;
130
+
131
+ visibleIndices.value = [...visibleIndices.value, index].sort(
132
+ (a, b) => a - b,
133
+ );
134
+ emit("line-show", line, index);
135
+ scrollToLatest();
136
+ };
137
+
138
+ const hideLine = (index: number) => {
139
+ const line = normalizedLines.value[index];
140
+ if (!line || !visibleIndices.value.includes(index)) return;
141
+
142
+ visibleIndices.value = visibleIndices.value.filter(
143
+ (visibleIndex) => visibleIndex !== index,
144
+ );
145
+ emit("line-hide", line, index);
146
+ };
147
+
148
+ const showAll = () => {
149
+ clearTimers();
150
+ playing.value = false;
151
+ visibleIndices.value = normalizedLines.value.map((_, index) => index);
152
+ scrollToLatest();
153
+ };
154
+
155
+ const reset = () => {
156
+ clearTimers();
157
+ playing.value = false;
158
+ visibleIndices.value = [];
159
+ };
160
+
161
+ const play = (preserveFirstLine = false) => {
162
+ clearTimers();
163
+ visibleIndices.value =
164
+ preserveFirstLine && normalizedLines.value.length ? [0] : [];
165
+ playing.value = true;
166
+
167
+ let elapsed = Math.max(0, props.startDelay);
168
+ normalizedLines.value.forEach((line, index) => {
169
+ if (preserveFirstLine && index === 0) return;
170
+
171
+ const delay = line.delay ?? (index === 0 ? 0 : props.lineDelay);
172
+ elapsed += Math.max(0, delay);
173
+ const showAt = elapsed;
174
+
175
+ schedule(() => showLine(index), showAt);
176
+ if (line.hideAfter !== undefined) {
177
+ schedule(
178
+ () => hideLine(index),
179
+ showAt + Math.max(0, line.hideAfter),
180
+ );
181
+ }
182
+ });
183
+
184
+ schedule(() => {
185
+ playing.value = false;
186
+ emit("complete");
187
+ }, elapsed);
188
+
189
+ if (props.loop) schedule(play, elapsed + Math.max(0, props.loopDelay));
190
+ };
191
+
192
+ const restart = () => {
193
+ if (!mounted) return;
194
+ if (props.autoplay) play();
195
+ else showAll();
196
+ };
197
+
198
+ watch(
199
+ () => props.lines,
200
+ () => restart(),
201
+ { deep: true },
202
+ );
203
+
204
+ watch(
205
+ () => [
206
+ props.autoplay,
207
+ props.lineDelay,
208
+ props.startDelay,
209
+ props.loop,
210
+ props.loopDelay,
211
+ ],
212
+ () => restart(),
213
+ );
214
+
215
+ onMounted(() => {
216
+ mounted = true;
217
+ if (props.autoplay) play(true);
218
+ else showAll();
219
+ pendingClientPlayback.value = false;
220
+ });
221
+
222
+ onBeforeUnmount(() => {
223
+ mounted = false;
224
+ clearTimers();
225
+ });
226
+
227
+ defineExpose({ play: () => play(), reset, showAll, scrollToLatest });
228
+ </script>
229
+
230
+ <template>
231
+ <div
232
+ :class="['ui-console', ui.root]"
233
+ :style="rootStyles"
234
+ :data-console-pending="pendingClientPlayback || undefined"
235
+ >
236
+ <div
237
+ v-if="chrome || title || $slots.header"
238
+ :class="['ui-console__header', ui.header]"
239
+ >
240
+ <slot name="header">
241
+ <div
242
+ v-if="chrome"
243
+ class="ui-console__traffic"
244
+ aria-hidden="true"
245
+ >
246
+ <span />
247
+ <span />
248
+ <span />
249
+ </div>
250
+ <span v-if="title" class="ui-console__title">{{ title }}</span>
251
+ </slot>
252
+ </div>
253
+
254
+ <div
255
+ ref="body"
256
+ :class="['ui-console__body', ui.body]"
257
+ role="log"
258
+ :aria-label="ariaLabel"
259
+ :aria-live="autoplay ? 'polite' : 'off'"
260
+ :aria-busy="playing"
261
+ aria-relevant="additions removals"
262
+ >
263
+ <TransitionGroup
264
+ name="ui-console-line"
265
+ tag="div"
266
+ class="ui-console__lines"
267
+ >
268
+ <div
269
+ v-for="entry in visibleLines"
270
+ :key="entry.line.id ?? entry.index"
271
+ :class="[
272
+ 'ui-console__line',
273
+ `ui-console__line--${entry.line.tone ?? 'default'}`,
274
+ { 'ui-console__line--highlight': entry.line.highlight },
275
+ entry.line.class,
276
+ ui.line,
277
+ ]"
278
+ >
279
+ <slot name="line" :line="entry.line" :index="entry.index">
280
+ <slot
281
+ name="prefix"
282
+ :line="entry.line"
283
+ :index="entry.index"
284
+ >
285
+ <span
286
+ v-if="entry.line.prefix"
287
+ class="ui-console__prefix"
288
+ >{{ entry.line.prefix }}</span
289
+ >
290
+ </slot>
291
+ <template v-if="entry.line.segments?.length">
292
+ <span
293
+ v-for="(segment, segmentIndex) in entry.line
294
+ .segments"
295
+ :key="segmentIndex"
296
+ :class="[
297
+ 'ui-console__segment',
298
+ `ui-console__segment--${segment.tone ?? 'default'}`,
299
+ {
300
+ 'ui-console__segment--highlight':
301
+ segment.highlight,
302
+ },
303
+ segment.class,
304
+ ]"
305
+ >
306
+ {{ segment.text }}
307
+ </span>
308
+ </template>
309
+ <template v-else>{{ entry.line.text }}</template>
310
+ </slot>
311
+ </div>
312
+ </TransitionGroup>
313
+
314
+ <span
315
+ v-if="cursor && !playing"
316
+ :class="['ui-console__cursor', ui.cursor]"
317
+ aria-hidden="true"
318
+ />
319
+ </div>
320
+ </div>
321
+ </template>
322
+
323
+ <style scoped>
324
+ .ui-console {
325
+ --ui-console-github-bg: #f6f8fa;
326
+ --ui-console-github-header: #f6f8fa;
327
+ --ui-console-github-text: #1f2328;
328
+ --ui-console-github-muted: #656d76;
329
+ --ui-console-github-border: #d0d7de;
330
+ --ui-console-github-primary: #8250df;
331
+ --ui-console-github-info: #0969da;
332
+ --ui-console-github-success: #1a7f37;
333
+ --ui-console-github-warning: #9a6700;
334
+ --ui-console-github-error: #d1242f;
335
+
336
+ width: 100%;
337
+ min-width: 0;
338
+ overflow: hidden;
339
+ color: var(--ui-console-text, var(--ui-console-github-text));
340
+ background: var(--ui-console-bg, var(--ui-console-github-bg));
341
+ border: 1px solid var(--ui-console-border, var(--ui-console-github-border));
342
+ border-radius: var(--ui-console-radius, 0.9rem);
343
+ box-shadow: var(--ui-console-shadow, 0 1.25rem 4rem rgb(31 35 40 / 8%));
344
+ }
345
+
346
+ .ui-console__header {
347
+ position: relative;
348
+ display: flex;
349
+ align-items: center;
350
+ min-height: 3.25rem;
351
+ padding: 0 1.25rem;
352
+ background: var(--ui-console-header-bg, var(--ui-console-github-header));
353
+ border-bottom: 1px solid
354
+ var(--ui-console-border, var(--ui-console-github-border));
355
+ }
356
+
357
+ .ui-console__traffic {
358
+ display: flex;
359
+ gap: 0.5rem;
360
+ }
361
+
362
+ .ui-console__traffic span {
363
+ width: 0.75rem;
364
+ height: 0.75rem;
365
+ background: color-mix(
366
+ in srgb,
367
+ var(--ui-console-github-muted) 24%,
368
+ var(--ui-console-github-bg)
369
+ );
370
+ border: 1px solid
371
+ color-mix(in srgb, var(--ui-console-github-muted) 12%, transparent);
372
+ border-radius: 50%;
373
+ }
374
+
375
+ .ui-console__title {
376
+ position: absolute;
377
+ left: 50%;
378
+ max-width: 55%;
379
+ overflow: hidden;
380
+ font-size: 0.75rem;
381
+ font-weight: 600;
382
+ text-overflow: ellipsis;
383
+ white-space: nowrap;
384
+ transform: translateX(-50%);
385
+ opacity: 0.5;
386
+ }
387
+
388
+ .ui-console__body {
389
+ min-height: var(--ui-console-min-height);
390
+ max-height: var(--ui-console-max-height);
391
+ padding: clamp(1.25rem, 4vw, 2rem);
392
+ overflow: auto;
393
+ font-family: var(
394
+ --ui-font-mono,
395
+ ui-monospace,
396
+ SFMono-Regular,
397
+ Menlo,
398
+ Monaco,
399
+ Consolas,
400
+ "Liberation Mono",
401
+ monospace
402
+ );
403
+ font-size: clamp(0.78rem, 1.8vw, 0.95rem);
404
+ line-height: 1.7;
405
+ scrollbar-color: color-mix(
406
+ in srgb,
407
+ var(--ui-console-github-muted) 45%,
408
+ transparent
409
+ )
410
+ transparent;
411
+ }
412
+
413
+ .ui-console__lines {
414
+ display: grid;
415
+ gap: 0.2rem;
416
+ }
417
+
418
+ .ui-console__line {
419
+ min-width: 0;
420
+ white-space: pre-wrap;
421
+ overflow-wrap: anywhere;
422
+ }
423
+
424
+ .ui-console__prefix {
425
+ margin-right: 0.65em;
426
+ opacity: 0.55;
427
+ }
428
+
429
+ .ui-console__line--highlight,
430
+ .ui-console__segment--highlight {
431
+ padding: 0.08em 0.28em;
432
+ background: color-mix(in srgb, currentColor 14%, transparent);
433
+ border-radius: 0.2em;
434
+ }
435
+
436
+ .ui-console__line--muted,
437
+ .ui-console__segment--muted {
438
+ color: var(--ui-console-muted, var(--ui-console-github-muted));
439
+ }
440
+
441
+ .ui-console__segment--default {
442
+ color: var(--ui-console-text, var(--ui-console-github-text));
443
+ }
444
+
445
+ .ui-console__line--primary,
446
+ .ui-console__segment--primary {
447
+ color: var(--ui-console-primary, var(--ui-console-github-primary));
448
+ }
449
+
450
+ .ui-console__line--info,
451
+ .ui-console__segment--info {
452
+ color: var(--ui-console-info, var(--ui-console-github-info));
453
+ }
454
+
455
+ .ui-console__line--success,
456
+ .ui-console__segment--success {
457
+ color: var(--ui-console-success, var(--ui-console-github-success));
458
+ }
459
+
460
+ .ui-console__line--warning,
461
+ .ui-console__segment--warning {
462
+ color: var(--ui-console-warning, var(--ui-console-github-warning));
463
+ }
464
+
465
+ .ui-console__line--error,
466
+ .ui-console__segment--error {
467
+ color: var(
468
+ --ui-console-error,
469
+ var(--ui-error, var(--ui-console-github-error))
470
+ );
471
+ }
472
+
473
+ .ui-console__cursor {
474
+ display: block;
475
+ width: 0.58em;
476
+ height: 1.05em;
477
+ margin-top: 0.35rem;
478
+ background: currentColor;
479
+ animation: ui-console-cursor 900ms steps(1, end) infinite;
480
+ }
481
+
482
+ .ui-console-line-enter-active {
483
+ transition:
484
+ opacity 180ms ease,
485
+ transform 180ms ease;
486
+ }
487
+
488
+ .ui-console-line-move {
489
+ transition: transform 180ms ease;
490
+ }
491
+
492
+ .ui-console-line-enter-from {
493
+ opacity: 0;
494
+ transform: translateY(0.35rem);
495
+ }
496
+
497
+ .ui-console-line-leave-active {
498
+ display: none;
499
+ transition: none;
500
+ }
501
+
502
+ @keyframes ui-console-cursor {
503
+ 50% {
504
+ opacity: 0;
505
+ }
506
+ }
507
+
508
+ @media (max-width: 479px) {
509
+ .ui-console__header {
510
+ min-height: 2.8rem;
511
+ padding-inline: 1rem;
512
+ }
513
+
514
+ .ui-console__body {
515
+ padding: 1rem;
516
+ font-size: 0.76rem;
517
+ }
518
+ }
519
+
520
+ @media (prefers-reduced-motion: reduce) {
521
+ .ui-console__cursor {
522
+ animation: none;
523
+ }
524
+
525
+ .ui-console-line-enter-active,
526
+ .ui-console-line-move {
527
+ transition: none;
528
+ }
529
+
530
+ .ui-console-line-enter-from {
531
+ transform: none;
532
+ }
533
+ }
534
+
535
+ @media (forced-colors: active) {
536
+ .ui-console,
537
+ .ui-console__header {
538
+ border-color: CanvasText;
539
+ }
540
+ }
541
+ </style>
542
+
543
+ <style>
544
+ html.js .ui-console[data-console-pending] .ui-console__line:not(:first-child) {
545
+ display: none;
546
+ }
547
+
548
+ html.js .ui-console[data-console-pending] .ui-console__cursor {
549
+ display: none;
550
+ }
551
+
552
+ html[data-theme="dark"] .ui-console {
553
+ --ui-console-github-bg: #0d1117;
554
+ --ui-console-github-header: #161b22;
555
+ --ui-console-github-text: #e6edf3;
556
+ --ui-console-github-muted: #8b949e;
557
+ --ui-console-github-border: #30363d;
558
+ --ui-console-github-primary: #d2a8ff;
559
+ --ui-console-github-info: #58a6ff;
560
+ --ui-console-github-success: #3fb950;
561
+ --ui-console-github-warning: #d29922;
562
+ --ui-console-github-error: #f85149;
563
+
564
+ box-shadow: var(--ui-console-shadow, 0 1.25rem 4rem rgb(1 4 9 / 38%));
565
+ }
566
+
567
+ @media (prefers-color-scheme: dark) {
568
+ html:not([data-theme="light"]) .ui-console {
569
+ --ui-console-github-bg: #0d1117;
570
+ --ui-console-github-header: #161b22;
571
+ --ui-console-github-text: #e6edf3;
572
+ --ui-console-github-muted: #8b949e;
573
+ --ui-console-github-border: #30363d;
574
+ --ui-console-github-primary: #d2a8ff;
575
+ --ui-console-github-info: #58a6ff;
576
+ --ui-console-github-success: #3fb950;
577
+ --ui-console-github-warning: #d29922;
578
+ --ui-console-github-error: #f85149;
579
+
580
+ box-shadow: var(--ui-console-shadow, 0 1.25rem 4rem rgb(1 4 9 / 38%));
581
+ }
582
+ }
583
+ </style>
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <div v-if="cursorEnabled">
3
+ <div
4
+ ref="cursorDot"
5
+ class="fixed top-0 left-0 w-1 h-1 rounded-full pointer-events-none z-10000 transition-opacity duration-300 ease-out"
6
+ :class="[
7
+ shouldHide ? 'opacity-0' : 'opacity-100',
8
+ 'bg-(--ui-text)',
9
+ ]"
10
+ ></div>
11
+
12
+ <div
13
+ ref="cursorCircle"
14
+ class="fixed top-0 left-0 w-7 h-7 border-2 rounded-full pointer-events-none z-9999 transition-opacity duration-300 ease-out"
15
+ :class="[
16
+ shouldHide ? 'opacity-0' : 'opacity-100',
17
+ 'border-(--ui-text)',
18
+ ]"
19
+ ></div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup>
24
+ const cursorDot = ref(null);
25
+ const cursorCircle = ref(null);
26
+ const cursorEnabled = ref(false);
27
+ const shouldHide = ref(false);
28
+
29
+ const mouse = { x: -100, y: -100 };
30
+ const circlePos = { x: -100, y: -100 };
31
+ const smoothing = 0.15;
32
+ const circleRadius = 20;
33
+ const dotRadius = 4;
34
+ const maxDistance = circleRadius - dotRadius;
35
+
36
+ let animationFrameId = null;
37
+
38
+ const checkHover = (target) => {
39
+ if (!target) return;
40
+
41
+ const isInteractive =
42
+ target.closest &&
43
+ target.closest(
44
+ 'a, button, input, textarea, select, [role="button"], iframe, .tally-component-wrapper',
45
+ );
46
+
47
+ shouldHide.value = !!isInteractive;
48
+ };
49
+
50
+ const onMouseMove = (e) => {
51
+ mouse.x = e.clientX;
52
+ mouse.y = e.clientY;
53
+ checkHover(e.target);
54
+ };
55
+
56
+ const onMouseOut = (e) => {
57
+ if (!e.relatedTarget) {
58
+ shouldHide.value = true;
59
+ } else {
60
+ checkHover(e.relatedTarget);
61
+ }
62
+ };
63
+
64
+ const animate = () => {
65
+ if (!cursorEnabled.value || !cursorDot.value || !cursorCircle.value) return;
66
+
67
+ circlePos.x += (mouse.x - circlePos.x) * smoothing;
68
+ circlePos.y += (mouse.y - circlePos.y) * smoothing;
69
+
70
+ const deltaX = mouse.x - circlePos.x;
71
+ const deltaY = mouse.y - circlePos.y;
72
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
73
+
74
+ let dotX, dotY;
75
+
76
+ if (distance > maxDistance) {
77
+ const angle = Math.atan2(deltaY, deltaX);
78
+ dotX = circlePos.x + Math.cos(angle) * maxDistance;
79
+ dotY = circlePos.y + Math.sin(angle) * maxDistance;
80
+ } else {
81
+ dotX = mouse.x;
82
+ dotY = mouse.y;
83
+ }
84
+
85
+ cursorCircle.value.style.transform = `translate3d(${circlePos.x}px, ${circlePos.y}px, 0) translate(-50%, -50%)`;
86
+ cursorDot.value.style.transform = `translate3d(${dotX}px, ${dotY}px, 0) translate(-50%, -50%)`;
87
+
88
+ animationFrameId = requestAnimationFrame(animate);
89
+ };
90
+
91
+ const checkEnvironment = () => {
92
+ const reducedMotionQuery = window.matchMedia(
93
+ "(prefers-reduced-motion: reduce)",
94
+ );
95
+ const hasFinePointerQuery = window.matchMedia("(pointer: fine)");
96
+
97
+ const shouldEnable =
98
+ !reducedMotionQuery.matches && hasFinePointerQuery.matches;
99
+
100
+ if (shouldEnable !== cursorEnabled.value) {
101
+ cursorEnabled.value = shouldEnable;
102
+
103
+ if (shouldEnable) {
104
+ setTimeout(() => {
105
+ if (typeof window === "undefined") return;
106
+
107
+ mouse.x = window.innerWidth / 2;
108
+ mouse.y = window.innerHeight / 2;
109
+ circlePos.x = mouse.x;
110
+ circlePos.y = mouse.y;
111
+
112
+ window.addEventListener("mousemove", onMouseMove, {
113
+ passive: true,
114
+ });
115
+ document.addEventListener("mouseout", onMouseOut, {
116
+ passive: true,
117
+ });
118
+
119
+ animate();
120
+ }, 50);
121
+ } else {
122
+ window.removeEventListener("mousemove", onMouseMove);
123
+ document.removeEventListener("mouseout", onMouseOut);
124
+ if (animationFrameId) cancelAnimationFrame(animationFrameId);
125
+ }
126
+ }
127
+ };
128
+
129
+ onMounted(() => {
130
+ if (typeof window !== "undefined") {
131
+ checkEnvironment();
132
+ window
133
+ .matchMedia("(prefers-reduced-motion: reduce)")
134
+ .addEventListener("change", checkEnvironment);
135
+ window
136
+ .matchMedia("(pointer: fine)")
137
+ .addEventListener("change", checkEnvironment);
138
+ }
139
+ });
140
+
141
+ onUnmounted(() => {
142
+ if (typeof window !== "undefined") {
143
+ window.removeEventListener("mousemove", onMouseMove);
144
+ document.removeEventListener("mouseout", onMouseOut);
145
+ if (animationFrameId) cancelAnimationFrame(animationFrameId);
146
+ }
147
+ });
148
+ </script>