@joewinke/jatui 0.1.33 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CLAUDE.md +22 -0
  2. package/dist/actions/attention.d.ts +24 -0
  3. package/dist/actions/attention.js +94 -0
  4. package/dist/actions/floating.d.ts +53 -0
  5. package/dist/actions/floating.js +158 -0
  6. package/dist/actions/staggerEntrance.d.ts +28 -0
  7. package/dist/actions/staggerEntrance.js +71 -0
  8. package/dist/actions/tooltip.d.ts +28 -0
  9. package/dist/actions/tooltip.js +186 -0
  10. package/dist/assets/sounds/README.md +47 -0
  11. package/dist/assets/sounds/error.d.ts +1 -0
  12. package/dist/assets/sounds/error.js +4 -0
  13. package/dist/assets/sounds/notify.d.ts +1 -0
  14. package/dist/assets/sounds/notify.js +4 -0
  15. package/dist/assets/sounds/pop.d.ts +1 -0
  16. package/dist/assets/sounds/pop.js +4 -0
  17. package/dist/assets/sounds/send.d.ts +1 -0
  18. package/dist/assets/sounds/send.js +4 -0
  19. package/dist/assets/sounds/success.d.ts +1 -0
  20. package/dist/assets/sounds/success.js +4 -0
  21. package/dist/assets/sounds/toggle.d.ts +1 -0
  22. package/dist/assets/sounds/toggle.js +4 -0
  23. package/dist/components/AnimatedNumber.svelte +112 -0
  24. package/dist/components/AnimatedNumber.svelte.d.ts +8 -0
  25. package/dist/components/Badge.svelte +75 -4
  26. package/dist/components/Badge.svelte.d.ts +6 -1
  27. package/dist/components/Button.svelte +196 -21
  28. package/dist/components/Button.svelte.d.ts +10 -1
  29. package/dist/components/Carousel.svelte +265 -0
  30. package/dist/components/Carousel.svelte.d.ts +21 -0
  31. package/dist/components/ChipInput.svelte +63 -3
  32. package/dist/components/Collapse.svelte +70 -14
  33. package/dist/components/Collapse.svelte.d.ts +11 -0
  34. package/dist/components/ColorSelector.svelte +21 -24
  35. package/dist/components/CommandPalette.svelte +475 -0
  36. package/dist/components/CommandPalette.svelte.d.ts +3 -0
  37. package/dist/components/ContextMenu.svelte +9 -73
  38. package/dist/components/CopyButton.svelte +156 -0
  39. package/dist/components/CopyButton.svelte.d.ts +12 -0
  40. package/dist/components/HoldToConfirm.svelte +183 -0
  41. package/dist/components/HoldToConfirm.svelte.d.ts +11 -0
  42. package/dist/components/KeyboardShortcutsOverlay.svelte +54 -8
  43. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +3 -0
  44. package/dist/components/PasswordMeter.svelte +87 -0
  45. package/dist/components/PasswordMeter.svelte.d.ts +8 -0
  46. package/dist/components/PinInput.svelte +186 -0
  47. package/dist/components/PinInput.svelte.d.ts +13 -0
  48. package/dist/components/QuantityStepper.svelte +185 -0
  49. package/dist/components/QuantityStepper.svelte.d.ts +12 -0
  50. package/dist/components/Skeleton.svelte +123 -0
  51. package/dist/components/Skeleton.svelte.d.ts +12 -0
  52. package/dist/components/SpeechForm.svelte +37 -0
  53. package/dist/components/StatusBadge.svelte +31 -3
  54. package/dist/components/StatusBadge.svelte.d.ts +0 -5
  55. package/dist/components/Tabs.svelte +270 -0
  56. package/dist/components/Tabs.svelte.d.ts +15 -0
  57. package/dist/components/TextInput.svelte +138 -38
  58. package/dist/components/TextInput.svelte.d.ts +9 -1
  59. package/dist/components/VoicePlayer.svelte +51 -0
  60. package/dist/components/basic/Alert.svelte +36 -0
  61. package/dist/components/basic/Alert.svelte.d.ts +9 -0
  62. package/dist/components/basic/Breadcrumb.svelte +27 -0
  63. package/dist/components/basic/Breadcrumb.svelte.d.ts +11 -0
  64. package/dist/components/basic/ButtonGroup.svelte +15 -0
  65. package/dist/components/basic/ButtonGroup.svelte.d.ts +8 -0
  66. package/dist/components/basic/Checkbox.svelte +85 -0
  67. package/dist/components/basic/Checkbox.svelte.d.ts +15 -0
  68. package/dist/components/basic/Empty.svelte +17 -0
  69. package/dist/components/basic/Empty.svelte.d.ts +8 -0
  70. package/dist/components/basic/Kbd.svelte +16 -0
  71. package/dist/components/basic/Kbd.svelte.d.ts +8 -0
  72. package/dist/components/basic/Pagination.svelte +104 -0
  73. package/dist/components/basic/Pagination.svelte.d.ts +12 -0
  74. package/dist/components/basic/RadioGroup.svelte +71 -0
  75. package/dist/components/basic/RadioGroup.svelte.d.ts +18 -0
  76. package/dist/components/basic/Separator.svelte +19 -0
  77. package/dist/components/basic/Separator.svelte.d.ts +8 -0
  78. package/dist/components/basic/Slider.svelte +72 -0
  79. package/dist/components/basic/Slider.svelte.d.ts +17 -0
  80. package/dist/components/basic/Switch.svelte +88 -0
  81. package/dist/components/basic/Switch.svelte.d.ts +14 -0
  82. package/dist/components/basic/Toggle.svelte +43 -0
  83. package/dist/components/basic/Toggle.svelte.d.ts +14 -0
  84. package/dist/components/graph/GraphCanvas.svelte +103 -0
  85. package/dist/components/graph/GraphCanvas.svelte.d.ts +22 -0
  86. package/dist/components/graph/JatuiNode.svelte +128 -0
  87. package/dist/components/graph/JatuiNode.svelte.d.ts +14 -0
  88. package/dist/components/graph/theme.d.ts +27 -0
  89. package/dist/components/graph/theme.js +136 -0
  90. package/dist/components/messaging/MessageItem.svelte +92 -0
  91. package/dist/components/messaging/MessageItem.svelte.d.ts +5 -0
  92. package/dist/components/messaging/TypingIndicator.svelte +63 -0
  93. package/dist/components/messaging/TypingIndicator.svelte.d.ts +7 -0
  94. package/dist/components/pipeline/PipelineColumn.svelte +44 -3
  95. package/dist/components/progress/ProgressBar.svelte +189 -0
  96. package/dist/components/progress/ProgressBar.svelte.d.ts +17 -0
  97. package/dist/components/progress/ProgressRing.svelte +138 -0
  98. package/dist/components/progress/ProgressRing.svelte.d.ts +12 -0
  99. package/dist/components/progress/Steps.svelte +195 -0
  100. package/dist/components/progress/Steps.svelte.d.ts +14 -0
  101. package/dist/components/shell/NavigationMenu.svelte +422 -0
  102. package/dist/components/shell/NavigationMenu.svelte.d.ts +43 -0
  103. package/dist/components/shell/Sidebar.svelte +276 -0
  104. package/dist/components/shell/Sidebar.svelte.d.ts +32 -0
  105. package/dist/components/shell/SidebarGroup.svelte +180 -0
  106. package/dist/components/shell/SidebarGroup.svelte.d.ts +19 -0
  107. package/dist/components/shell/SidebarItem.svelte +170 -0
  108. package/dist/components/shell/SidebarItem.svelte.d.ts +24 -0
  109. package/dist/components/shell/shell.d.ts +30 -0
  110. package/dist/components/shell/shell.js +65 -0
  111. package/dist/components/table/DataTable.svelte +364 -0
  112. package/dist/components/table/DataTable.svelte.d.ts +4 -0
  113. package/dist/components/table/flexRender.d.ts +31 -0
  114. package/dist/components/table/flexRender.js +32 -0
  115. package/dist/components/table/types.d.ts +28 -0
  116. package/dist/components/table/types.js +1 -0
  117. package/dist/components/toast/ToastHost.svelte +21 -0
  118. package/dist/components/toast/ToastHost.svelte.d.ts +3 -0
  119. package/dist/components/toast/ToastItem.svelte +287 -0
  120. package/dist/components/toast/ToastItem.svelte.d.ts +9 -0
  121. package/dist/index.d.ts +59 -0
  122. package/dist/index.js +67 -0
  123. package/dist/showcase/AnimatedBeam.svelte +183 -0
  124. package/dist/showcase/AnimatedBeam.svelte.d.ts +26 -0
  125. package/dist/showcase/AuroraDrift.svelte +74 -0
  126. package/dist/showcase/AuroraDrift.svelte.d.ts +9 -0
  127. package/dist/showcase/BentoCard.svelte +50 -0
  128. package/dist/showcase/BentoCard.svelte.d.ts +14 -0
  129. package/dist/showcase/BentoGrid.svelte +43 -0
  130. package/dist/showcase/BentoGrid.svelte.d.ts +12 -0
  131. package/dist/showcase/BorderBeam.svelte +79 -0
  132. package/dist/showcase/BorderBeam.svelte.d.ts +12 -0
  133. package/dist/showcase/ClipWipe.svelte +82 -0
  134. package/dist/showcase/ClipWipe.svelte.d.ts +12 -0
  135. package/dist/showcase/Confetti.svelte +71 -0
  136. package/dist/showcase/Confetti.svelte.d.ts +10 -0
  137. package/dist/showcase/CursorSpotlight.svelte +85 -0
  138. package/dist/showcase/CursorSpotlight.svelte.d.ts +9 -0
  139. package/dist/showcase/DeviceMock.svelte +186 -0
  140. package/dist/showcase/DeviceMock.svelte.d.ts +11 -0
  141. package/dist/showcase/Dock.svelte +123 -0
  142. package/dist/showcase/Dock.svelte.d.ts +17 -0
  143. package/dist/showcase/FlipCard.svelte +103 -0
  144. package/dist/showcase/FlipCard.svelte.d.ts +10 -0
  145. package/dist/showcase/FocusReveal.svelte +83 -0
  146. package/dist/showcase/FocusReveal.svelte.d.ts +11 -0
  147. package/dist/showcase/Globe.svelte +202 -0
  148. package/dist/showcase/Globe.svelte.d.ts +16 -0
  149. package/dist/showcase/KenBurns.svelte +105 -0
  150. package/dist/showcase/KenBurns.svelte.d.ts +13 -0
  151. package/dist/showcase/Marquee.svelte +89 -0
  152. package/dist/showcase/Marquee.svelte.d.ts +9 -0
  153. package/dist/showcase/ParallaxTilt.svelte +98 -0
  154. package/dist/showcase/ParallaxTilt.svelte.d.ts +10 -0
  155. package/dist/showcase/ScrambleText.svelte +98 -0
  156. package/dist/showcase/ScrambleText.svelte.d.ts +11 -0
  157. package/dist/showcase/ShineSweep.svelte +81 -0
  158. package/dist/showcase/ShineSweep.svelte.d.ts +9 -0
  159. package/dist/showcase/Terminal.svelte +230 -0
  160. package/dist/showcase/Terminal.svelte.d.ts +18 -0
  161. package/dist/showcase/TextSplitReveal.svelte +146 -0
  162. package/dist/showcase/TextSplitReveal.svelte.d.ts +13 -0
  163. package/dist/showcase/TextWave.svelte +55 -0
  164. package/dist/showcase/TextWave.svelte.d.ts +8 -0
  165. package/dist/showcase/TypewriterText.svelte +130 -0
  166. package/dist/showcase/TypewriterText.svelte.d.ts +9 -0
  167. package/dist/showcase/backdrop/GrainGradient.svelte +126 -0
  168. package/dist/showcase/backdrop/GrainGradient.svelte.d.ts +23 -0
  169. package/dist/showcase/backdrop/MeshGradient.svelte +104 -0
  170. package/dist/showcase/backdrop/MeshGradient.svelte.d.ts +20 -0
  171. package/dist/showcase/backdrop/Metaballs.svelte +116 -0
  172. package/dist/showcase/backdrop/Metaballs.svelte.d.ts +18 -0
  173. package/dist/showcase/backdrop/NeuroNoise.svelte +115 -0
  174. package/dist/showcase/backdrop/NeuroNoise.svelte.d.ts +20 -0
  175. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte +48 -0
  176. package/dist/showcase/backdrop/ShowcaseBackdrop.svelte.d.ts +12 -0
  177. package/dist/showcase/generative/GenerativeBackdrop.svelte +234 -0
  178. package/dist/showcase/generative/GenerativeBackdrop.svelte.d.ts +9 -0
  179. package/dist/showcase/generative/intent.d.ts +7 -0
  180. package/dist/showcase/generative/intent.js +67 -0
  181. package/dist/showcase/generative/manifest.d.ts +9 -0
  182. package/dist/showcase/generative/manifest.js +97 -0
  183. package/dist/showcase/generative/types.d.ts +48 -0
  184. package/dist/showcase/generative/types.js +8 -0
  185. package/dist/showcase/index.d.ts +36 -0
  186. package/dist/showcase/index.js +46 -0
  187. package/dist/showcase/scroll/SmoothScroll.svelte +49 -0
  188. package/dist/showcase/scroll/SmoothScroll.svelte.d.ts +12 -0
  189. package/dist/showcase/scroll/scrollProgress.d.ts +24 -0
  190. package/dist/showcase/scroll/scrollProgress.js +65 -0
  191. package/dist/showcase/scroll/scrollReveal.d.ts +30 -0
  192. package/dist/showcase/scroll/scrollReveal.js +70 -0
  193. package/dist/stores/commandPalette.svelte.d.ts +24 -0
  194. package/dist/stores/commandPalette.svelte.js +82 -0
  195. package/dist/stores/toast.svelte.d.ts +28 -0
  196. package/dist/stores/toast.svelte.js +33 -0
  197. package/dist/styles/motion.css +46 -0
  198. package/dist/styles/tokens.css +31 -0
  199. package/dist/transitions/glide.d.ts +31 -0
  200. package/dist/transitions/glide.js +58 -0
  201. package/dist/utils/sound.d.ts +6 -0
  202. package/dist/utils/sound.js +113 -0
  203. package/dist/utils/splitText.d.ts +25 -0
  204. package/dist/utils/splitText.js +94 -0
  205. package/dist/utils/themeWipe.d.ts +21 -0
  206. package/dist/utils/themeWipe.js +94 -0
  207. package/dist/utils/viewTransition.d.ts +23 -0
  208. package/dist/utils/viewTransition.js +75 -0
  209. package/package.json +50 -3
@@ -0,0 +1,81 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { onMount } from 'svelte';
4
+
5
+ interface Props {
6
+ interval?: number;
7
+ class?: string;
8
+ children?: Snippet;
9
+ }
10
+
11
+ let { interval, class: className = '', children }: Props = $props();
12
+
13
+ let el = $state<HTMLDivElement | null>(null);
14
+ let sweeping = $state(false);
15
+
16
+ onMount(() => {
17
+ if (!interval) return;
18
+
19
+ let timer: ReturnType<typeof setInterval>;
20
+
21
+ function trigger() {
22
+ sweeping = true;
23
+ setTimeout(() => {
24
+ sweeping = false;
25
+ }, 1000);
26
+ }
27
+
28
+ timer = setInterval(trigger, interval * 1000);
29
+ return () => clearInterval(timer);
30
+ });
31
+ </script>
32
+
33
+ <div
34
+ bind:this={el}
35
+ class="shine-sweep-root {sweeping ? 'sweeping' : ''} {className}"
36
+ >
37
+ {#if children}
38
+ {@render children()}
39
+ {/if}
40
+ </div>
41
+
42
+ <style>
43
+ .shine-sweep-root {
44
+ position: relative;
45
+ overflow: hidden;
46
+ }
47
+
48
+ .shine-sweep-root::before {
49
+ content: '';
50
+ position: absolute;
51
+ top: 0;
52
+ left: -120%;
53
+ width: 60%;
54
+ height: 100%;
55
+ transform: skewX(-20deg);
56
+ background: linear-gradient(
57
+ 90deg,
58
+ transparent,
59
+ color-mix(in srgb, white 25%, transparent),
60
+ transparent
61
+ );
62
+ pointer-events: none;
63
+ }
64
+
65
+ .shine-sweep-root:hover::before,
66
+ .shine-sweep-root.sweeping::before {
67
+ left: 120%;
68
+ transition: left 0.9s var(--motion-glide, cubic-bezier(0.16, 1, 0.3, 1));
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ .shine-sweep-root::before {
73
+ display: none;
74
+ }
75
+
76
+ .shine-sweep-root:hover::before,
77
+ .shine-sweep-root.sweeping::before {
78
+ display: none;
79
+ }
80
+ }
81
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ interval?: number;
4
+ class?: string;
5
+ children?: Snippet;
6
+ }
7
+ declare const ShineSweep: import("svelte").Component<Props, {}, "">;
8
+ type ShineSweep = ReturnType<typeof ShineSweep>;
9
+ export default ShineSweep;
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ // pattern after magicui.design (MIT)
3
+ import { onMount, onDestroy } from 'svelte';
4
+
5
+ export interface TerminalLine {
6
+ /** Shell prompt prefix. Falsy = output line (no prompt). */
7
+ prompt?: string;
8
+ text: string;
9
+ /** Delay in ms before this line starts rendering (default 0 for first, 400 for rest) */
10
+ delay?: number;
11
+ }
12
+
13
+ interface Props {
14
+ lines: TerminalLine[];
15
+ loop?: boolean;
16
+ /** Typing speed in characters per second */
17
+ speed?: number;
18
+ title?: string;
19
+ class?: string;
20
+ }
21
+
22
+ let {
23
+ lines,
24
+ loop = false,
25
+ speed = 40,
26
+ title = 'Terminal',
27
+ class: className = ''
28
+ }: Props = $props();
29
+
30
+ interface RenderedLine {
31
+ prompt?: string;
32
+ text: string;
33
+ done: boolean;
34
+ }
35
+
36
+ let rendered = $state<RenderedLine[]>([]);
37
+ let reducedMotion = $state(false);
38
+ let timer: ReturnType<typeof setTimeout> | null = null;
39
+ let raf: number | null = null;
40
+
41
+ function clear() {
42
+ if (timer) { clearTimeout(timer); timer = null; }
43
+ if (raf) { cancelAnimationFrame(raf); raf = null; }
44
+ }
45
+
46
+ function typeLine(lineIdx: number, charIdx: number, startTime: number) {
47
+ if (lineIdx >= lines.length) {
48
+ if (loop) {
49
+ timer = setTimeout(() => {
50
+ rendered = [];
51
+ timer = setTimeout(() => runFrom(0), 400);
52
+ }, 1200);
53
+ }
54
+ return;
55
+ }
56
+
57
+ const src = lines[lineIdx];
58
+ const msPerChar = 1000 / speed;
59
+
60
+ if (charIdx === 0) {
61
+ rendered = [
62
+ ...rendered,
63
+ { prompt: src.prompt, text: '', done: false }
64
+ ];
65
+ }
66
+
67
+ const now = performance.now();
68
+ const elapsed = now - startTime;
69
+ const targetChar = Math.min(Math.floor(elapsed / msPerChar), src.text.length);
70
+
71
+ // Update the current line's text
72
+ rendered = rendered.map((l, i) =>
73
+ i === rendered.length - 1
74
+ ? { ...l, text: src.text.slice(0, targetChar), done: targetChar >= src.text.length }
75
+ : l
76
+ );
77
+
78
+ if (targetChar < src.text.length) {
79
+ raf = requestAnimationFrame(() => typeLine(lineIdx, targetChar, startTime));
80
+ } else {
81
+ // Done with this line, schedule next
82
+ const nextLine = lines[lineIdx + 1];
83
+ const nextDelay = nextLine?.delay ?? (lineIdx === 0 ? 0 : 400);
84
+ timer = setTimeout(() => {
85
+ runLine(lineIdx + 1);
86
+ }, nextDelay);
87
+ }
88
+ }
89
+
90
+ function runLine(idx: number) {
91
+ if (idx >= lines.length) {
92
+ if (loop) {
93
+ timer = setTimeout(() => {
94
+ rendered = [];
95
+ timer = setTimeout(() => runFrom(0), 400);
96
+ }, 1200);
97
+ }
98
+ return;
99
+ }
100
+ typeLine(idx, 0, performance.now());
101
+ }
102
+
103
+ function runFrom(startIdx: number) {
104
+ const firstDelay = lines[startIdx]?.delay ?? 0;
105
+ timer = setTimeout(() => runLine(startIdx), firstDelay);
106
+ }
107
+
108
+ onMount(() => {
109
+ reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
110
+ if (reducedMotion) {
111
+ rendered = lines.map((l) => ({ prompt: l.prompt, text: l.text, done: true }));
112
+ } else {
113
+ runFrom(0);
114
+ }
115
+ });
116
+
117
+ onDestroy(clear);
118
+ </script>
119
+
120
+ <div class="terminal {className}" role="region" aria-label={title}>
121
+ <!-- Chrome header -->
122
+ <div class="terminal-header" aria-hidden="true">
123
+ <span class="traffic-dot" style="background:#ff5f57;"></span>
124
+ <span class="traffic-dot" style="background:#febc2e;"></span>
125
+ <span class="traffic-dot" style="background:#28c840;"></span>
126
+ <span class="terminal-title">{title}</span>
127
+ </div>
128
+
129
+ <!-- Output area -->
130
+ <div class="terminal-body" aria-live="polite" aria-atomic="false">
131
+ {#each rendered as line, i}
132
+ <div class="terminal-line">
133
+ {#if line.prompt}
134
+ <span class="terminal-prompt">{line.prompt}</span>
135
+ {/if}
136
+ <span class="terminal-text" class:terminal-output={!line.prompt}>{line.text}</span>
137
+ {#if i === rendered.length - 1 && !line.done && !reducedMotion}
138
+ <span class="terminal-cursor" aria-hidden="true"></span>
139
+ {/if}
140
+ </div>
141
+ {/each}
142
+ </div>
143
+ </div>
144
+
145
+ <style>
146
+ .terminal {
147
+ font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
148
+ font-size: 0.8125rem;
149
+ line-height: 1.6;
150
+ background: oklch(var(--n));
151
+ color: oklch(var(--nc));
152
+ border-radius: var(--jatui-radius-box);
153
+ border: var(--jatui-border-w) solid oklch(var(--bc) / 0.12);
154
+ overflow: hidden;
155
+ min-width: 320px;
156
+ }
157
+
158
+ .terminal-header {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 6px;
162
+ padding: 10px 14px;
163
+ background: oklch(var(--n) / 0.8);
164
+ border-bottom: var(--jatui-border-w) solid oklch(var(--bc) / 0.1);
165
+ }
166
+
167
+ .traffic-dot {
168
+ display: inline-block;
169
+ width: 12px;
170
+ height: 12px;
171
+ border-radius: 50%;
172
+ flex-shrink: 0;
173
+ }
174
+
175
+ .terminal-title {
176
+ margin-left: 8px;
177
+ font-size: 0.75rem;
178
+ opacity: 0.6;
179
+ flex: 1;
180
+ text-align: center;
181
+ }
182
+
183
+ .terminal-body {
184
+ padding: 16px;
185
+ display: flex;
186
+ flex-direction: column;
187
+ gap: 2px;
188
+ min-height: 80px;
189
+ }
190
+
191
+ .terminal-line {
192
+ display: flex;
193
+ align-items: baseline;
194
+ gap: 6px;
195
+ white-space: pre-wrap;
196
+ word-break: break-all;
197
+ }
198
+
199
+ .terminal-prompt {
200
+ color: oklch(var(--su, 0.75 0.18 142));
201
+ flex-shrink: 0;
202
+ user-select: none;
203
+ }
204
+
205
+ .terminal-output {
206
+ color: oklch(var(--nc) / 0.7);
207
+ }
208
+
209
+ .terminal-cursor {
210
+ display: inline-block;
211
+ width: 8px;
212
+ height: 1.1em;
213
+ background: currentColor;
214
+ vertical-align: text-bottom;
215
+ animation: term-blink 1s steps(1) infinite;
216
+ flex-shrink: 0;
217
+ }
218
+
219
+ @keyframes term-blink {
220
+ 0%, 100% { opacity: 1; }
221
+ 50% { opacity: 0; }
222
+ }
223
+
224
+ @media (prefers-reduced-motion: reduce) {
225
+ .terminal-cursor {
226
+ animation: none;
227
+ display: none;
228
+ }
229
+ }
230
+ </style>
@@ -0,0 +1,18 @@
1
+ export interface TerminalLine {
2
+ /** Shell prompt prefix. Falsy = output line (no prompt). */
3
+ prompt?: string;
4
+ text: string;
5
+ /** Delay in ms before this line starts rendering (default 0 for first, 400 for rest) */
6
+ delay?: number;
7
+ }
8
+ interface Props {
9
+ lines: TerminalLine[];
10
+ loop?: boolean;
11
+ /** Typing speed in characters per second */
12
+ speed?: number;
13
+ title?: string;
14
+ class?: string;
15
+ }
16
+ declare const Terminal: import("svelte").Component<Props, {}, "">;
17
+ type Terminal = ReturnType<typeof Terminal>;
18
+ export default Terminal;
@@ -0,0 +1,146 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { splitText } from '../utils/splitText.js';
4
+
5
+ type Split = 'char' | 'word';
6
+ type TriggerMode = 'mount' | 'intersect';
7
+
8
+ let {
9
+ text,
10
+ split = 'char' as Split,
11
+ triggerMode = 'mount' as TriggerMode,
12
+ class: className = ''
13
+ }: {
14
+ text: string;
15
+ split?: Split;
16
+ triggerMode?: TriggerMode;
17
+ /** @deprecated staggerMs is now a CSS-only calc — this prop is ignored. */
18
+ staggerMs?: number;
19
+ class?: string;
20
+ } = $props();
21
+
22
+ let elRef: HTMLSpanElement | undefined = $state();
23
+ let revealed = $state(false);
24
+ let prefersReducedMotion = $state(false);
25
+
26
+ onMount(() => {
27
+ if (!elRef) return;
28
+
29
+ prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
30
+
31
+ if (prefersReducedMotion) {
32
+ revealed = true;
33
+ return;
34
+ }
35
+
36
+ // Apply split; stagger delay is pure CSS via calc(var(--char-index) * 30ms)
37
+ splitText(elRef, { by: split });
38
+
39
+ if (triggerMode === 'mount') {
40
+ // Single rAF so the initial translateY(110%) is painted before we set revealed
41
+ requestAnimationFrame(() => {
42
+ revealed = true;
43
+ });
44
+ } else if (triggerMode === 'intersect') {
45
+ const observer = new IntersectionObserver(
46
+ (entries) => {
47
+ if (entries[0].isIntersecting) {
48
+ revealed = true;
49
+ observer.disconnect();
50
+ }
51
+ },
52
+ { threshold: 0.1 }
53
+ );
54
+ observer.observe(elRef);
55
+ return () => observer.disconnect();
56
+ }
57
+ });
58
+
59
+ // Re-split when text or split mode changes after mount.
60
+ $effect(() => {
61
+ if (!elRef || prefersReducedMotion) return;
62
+ void text;
63
+ void split;
64
+ splitText(elRef, { by: split });
65
+ // Reset so re-reveal fires when the text actually changes
66
+ revealed = false;
67
+ requestAnimationFrame(() => {
68
+ revealed = true;
69
+ });
70
+ });
71
+ </script>
72
+
73
+ <!--
74
+ splitText stamps aria-label on the container with the original text.
75
+ Injected spans are aria-hidden so screenreaders hear the label only.
76
+ prefers-reduced-motion: render plain text immediately, skip split.
77
+ -->
78
+ <span
79
+ bind:this={elRef}
80
+ class="text-split-reveal {className}"
81
+ class:revealed
82
+ data-split={split}
83
+ aria-label={prefersReducedMotion ? text : undefined}
84
+ >
85
+ {#if prefersReducedMotion}
86
+ {text}
87
+ {/if}
88
+ </span>
89
+
90
+ <style>
91
+ .text-split-reveal {
92
+ display: inline;
93
+ }
94
+
95
+ /*
96
+ * Each injected span gets --char-index / --word-index from splitText.
97
+ * Stagger delay is pure CSS — no JS timeout loop needed.
98
+ *
99
+ * Clip trick: overflow:hidden masks the initial translateY(110%);
100
+ * .revealed removes the transform via the sibling rule below.
101
+ */
102
+
103
+ /* char mode */
104
+ .text-split-reveal[data-split='char'] :global(span[aria-hidden='true']) {
105
+ display: inline-block;
106
+ overflow: hidden;
107
+ vertical-align: bottom;
108
+ transform: translateY(110%);
109
+ transition: transform var(--motion-move, 300ms)
110
+ var(--motion-spring, cubic-bezier(0.34, 1.56, 0.64, 1))
111
+ calc(var(--char-index, 0) * 30ms);
112
+ }
113
+
114
+ .text-split-reveal[data-split='char'].revealed :global(span[aria-hidden='true']) {
115
+ transform: translateY(0);
116
+ }
117
+
118
+ /* word mode */
119
+ .text-split-reveal[data-split='word'] :global(span[aria-hidden='true']) {
120
+ display: inline-block;
121
+ overflow: hidden;
122
+ vertical-align: bottom;
123
+ transform: translateY(110%);
124
+ transition: transform var(--motion-move, 300ms)
125
+ var(--motion-spring, cubic-bezier(0.34, 1.56, 0.64, 1))
126
+ calc(var(--word-index, 0) * 60ms);
127
+ }
128
+
129
+ .text-split-reveal[data-split='word'].revealed :global(span[aria-hidden='true']) {
130
+ transform: translateY(0);
131
+ }
132
+
133
+ /* whitespace spacer spans between words — never animate */
134
+ .text-split-reveal :global(span[style*='white-space: pre']) {
135
+ overflow: visible;
136
+ transform: none !important;
137
+ transition: none !important;
138
+ }
139
+
140
+ @media (prefers-reduced-motion: reduce) {
141
+ .text-split-reveal :global(span[aria-hidden='true']) {
142
+ transform: none !important;
143
+ transition: none !important;
144
+ }
145
+ }
146
+ </style>
@@ -0,0 +1,13 @@
1
+ type Split = 'char' | 'word';
2
+ type TriggerMode = 'mount' | 'intersect';
3
+ type $$ComponentProps = {
4
+ text: string;
5
+ split?: Split;
6
+ triggerMode?: TriggerMode;
7
+ /** @deprecated staggerMs is now a CSS-only calc — this prop is ignored. */
8
+ staggerMs?: number;
9
+ class?: string;
10
+ };
11
+ declare const TextSplitReveal: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type TextSplitReveal = ReturnType<typeof TextSplitReveal>;
13
+ export default TextSplitReveal;
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+
4
+ interface Props {
5
+ text: string;
6
+ paused?: boolean;
7
+ class?: string;
8
+ }
9
+
10
+ let { text, paused = false, class: className = '' }: Props = $props();
11
+
12
+ // Cleanup hook — animation is CSS-driven so nothing to cancel,
13
+ // but we expose the pattern for future imperative needs.
14
+ onDestroy(() => {});
15
+ </script>
16
+
17
+ <span class="text-wave {className}" aria-label={text}>
18
+ {#each text.split('') as char, i}
19
+ <span
20
+ class="wave-char"
21
+ aria-hidden="true"
22
+ style="animation-delay: -{(i * 0.09).toFixed(2)}s; animation-play-state: {paused
23
+ ? 'paused'
24
+ : 'running'};"
25
+ >{char === ' ' ? ' ' : char}</span>
26
+ {/each}
27
+ </span>
28
+
29
+ <style>
30
+ @keyframes bob {
31
+ 0%,
32
+ 100% {
33
+ transform: translateY(0);
34
+ }
35
+ 28% {
36
+ transform: translateY(-13px);
37
+ }
38
+ }
39
+
40
+ .text-wave {
41
+ display: inline-flex;
42
+ flex-wrap: wrap;
43
+ }
44
+
45
+ .wave-char {
46
+ display: inline-block;
47
+ animation: bob 1.5s ease-in-out infinite;
48
+ }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .wave-char {
52
+ animation: none;
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ text: string;
3
+ paused?: boolean;
4
+ class?: string;
5
+ }
6
+ declare const TextWave: import("svelte").Component<Props, {}, "">;
7
+ type TextWave = ReturnType<typeof TextWave>;
8
+ export default TextWave;
@@ -0,0 +1,130 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from 'svelte';
3
+
4
+ interface Props {
5
+ phrases: string[];
6
+ cursor?: boolean;
7
+ loop?: boolean;
8
+ class?: string;
9
+ }
10
+
11
+ let {
12
+ phrases,
13
+ cursor = true,
14
+ loop = false,
15
+ class: className = ''
16
+ }: Props = $props();
17
+
18
+ let displayed = $state('');
19
+ let caretVisible = $state(true);
20
+ let reducedMotion = $state(false);
21
+ let done = $state(false);
22
+
23
+ let timeout: ReturnType<typeof setTimeout> | null = null;
24
+
25
+ function schedule(fn: () => void, ms: number) {
26
+ timeout = setTimeout(fn, ms);
27
+ }
28
+
29
+ function clearScheduled() {
30
+ if (timeout !== null) {
31
+ clearTimeout(timeout);
32
+ timeout = null;
33
+ }
34
+ }
35
+
36
+ function run(phraseIdx: number, charIdx: number, deleting: boolean) {
37
+ if (reducedMotion) return;
38
+
39
+ const phrase = phrases[phraseIdx];
40
+
41
+ if (!deleting) {
42
+ // Typing
43
+ if (charIdx <= phrase.length) {
44
+ displayed = phrase.slice(0, charIdx);
45
+ if (charIdx < phrase.length) {
46
+ schedule(() => run(phraseIdx, charIdx + 1, false), 55);
47
+ } else {
48
+ // Finished typing full phrase
49
+ schedule(() => {
50
+ // Pause at full phrase
51
+ const isLast = phraseIdx === phrases.length - 1;
52
+ if (!loop && isLast) {
53
+ done = true;
54
+ return;
55
+ }
56
+ // Start deleting
57
+ schedule(() => run(phraseIdx, phrase.length, true), 0);
58
+ }, 1100);
59
+ }
60
+ }
61
+ } else {
62
+ // Deleting
63
+ if (charIdx >= 0) {
64
+ displayed = phrase.slice(0, charIdx);
65
+ if (charIdx > 0) {
66
+ schedule(() => run(phraseIdx, charIdx - 1, true), 30);
67
+ } else {
68
+ // Finished deleting — pause then move to next phrase
69
+ const nextIdx = (phraseIdx + 1) % phrases.length;
70
+ schedule(() => run(nextIdx, 0, false), 320);
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ onMount(() => {
77
+ reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
78
+ if (reducedMotion) {
79
+ displayed = phrases[0] ?? '';
80
+ } else {
81
+ run(0, 0, false);
82
+ }
83
+ });
84
+
85
+ onDestroy(() => {
86
+ clearScheduled();
87
+ });
88
+ </script>
89
+
90
+ <span class="typewriter {className}" class:no-caret={!cursor || done || reducedMotion}>
91
+ {displayed}<span class="caret" aria-hidden="true"></span>
92
+ </span>
93
+
94
+ <style>
95
+ .typewriter {
96
+ display: inline;
97
+ }
98
+
99
+ .caret::after {
100
+ content: '';
101
+ display: inline-block;
102
+ width: 2px;
103
+ height: 1.1em;
104
+ background: currentColor;
105
+ margin-left: 1px;
106
+ vertical-align: text-bottom;
107
+ animation: caret-blink 1s steps(1) infinite;
108
+ }
109
+
110
+ .no-caret .caret::after {
111
+ display: none;
112
+ }
113
+
114
+ @keyframes caret-blink {
115
+ 0%,
116
+ 100% {
117
+ opacity: 1;
118
+ }
119
+ 50% {
120
+ opacity: 0;
121
+ }
122
+ }
123
+
124
+ @media (prefers-reduced-motion: reduce) {
125
+ .caret::after {
126
+ animation: none;
127
+ display: none;
128
+ }
129
+ }
130
+ </style>
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ phrases: string[];
3
+ cursor?: boolean;
4
+ loop?: boolean;
5
+ class?: string;
6
+ }
7
+ declare const TypewriterText: import("svelte").Component<Props, {}, "">;
8
+ type TypewriterText = ReturnType<typeof TypewriterText>;
9
+ export default TypewriterText;