@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.
- package/CLAUDE.md +22 -0
- package/dist/actions/attention.d.ts +24 -0
- package/dist/actions/attention.js +94 -0
- package/dist/actions/floating.d.ts +53 -0
- package/dist/actions/floating.js +158 -0
- package/dist/actions/staggerEntrance.d.ts +28 -0
- package/dist/actions/staggerEntrance.js +71 -0
- package/dist/actions/tooltip.d.ts +28 -0
- package/dist/actions/tooltip.js +186 -0
- package/dist/assets/sounds/README.md +47 -0
- package/dist/assets/sounds/error.d.ts +1 -0
- package/dist/assets/sounds/error.js +4 -0
- package/dist/assets/sounds/notify.d.ts +1 -0
- package/dist/assets/sounds/notify.js +4 -0
- package/dist/assets/sounds/pop.d.ts +1 -0
- package/dist/assets/sounds/pop.js +4 -0
- package/dist/assets/sounds/send.d.ts +1 -0
- package/dist/assets/sounds/send.js +4 -0
- package/dist/assets/sounds/success.d.ts +1 -0
- package/dist/assets/sounds/success.js +4 -0
- package/dist/assets/sounds/toggle.d.ts +1 -0
- package/dist/assets/sounds/toggle.js +4 -0
- package/dist/components/AnimatedNumber.svelte +112 -0
- package/dist/components/AnimatedNumber.svelte.d.ts +8 -0
- package/dist/components/Badge.svelte +75 -4
- package/dist/components/Badge.svelte.d.ts +6 -1
- package/dist/components/Button.svelte +196 -21
- package/dist/components/Button.svelte.d.ts +10 -1
- package/dist/components/Carousel.svelte +265 -0
- package/dist/components/Carousel.svelte.d.ts +21 -0
- package/dist/components/ChipInput.svelte +63 -3
- package/dist/components/Collapse.svelte +70 -14
- package/dist/components/Collapse.svelte.d.ts +11 -0
- package/dist/components/ColorSelector.svelte +21 -24
- package/dist/components/CommandPalette.svelte +475 -0
- package/dist/components/CommandPalette.svelte.d.ts +3 -0
- package/dist/components/ContextMenu.svelte +9 -73
- package/dist/components/CopyButton.svelte +156 -0
- package/dist/components/CopyButton.svelte.d.ts +12 -0
- package/dist/components/HoldToConfirm.svelte +183 -0
- package/dist/components/HoldToConfirm.svelte.d.ts +11 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +54 -8
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +3 -0
- package/dist/components/PasswordMeter.svelte +87 -0
- package/dist/components/PasswordMeter.svelte.d.ts +8 -0
- package/dist/components/PinInput.svelte +186 -0
- package/dist/components/PinInput.svelte.d.ts +13 -0
- package/dist/components/QuantityStepper.svelte +185 -0
- package/dist/components/QuantityStepper.svelte.d.ts +12 -0
- package/dist/components/Skeleton.svelte +123 -0
- package/dist/components/Skeleton.svelte.d.ts +12 -0
- package/dist/components/SpeechForm.svelte +37 -0
- package/dist/components/StatusBadge.svelte +31 -3
- package/dist/components/StatusBadge.svelte.d.ts +0 -5
- package/dist/components/Tabs.svelte +270 -0
- package/dist/components/Tabs.svelte.d.ts +15 -0
- package/dist/components/TextInput.svelte +138 -38
- package/dist/components/TextInput.svelte.d.ts +9 -1
- package/dist/components/VoicePlayer.svelte +51 -0
- package/dist/components/basic/Alert.svelte +36 -0
- package/dist/components/basic/Alert.svelte.d.ts +9 -0
- package/dist/components/basic/Breadcrumb.svelte +27 -0
- package/dist/components/basic/Breadcrumb.svelte.d.ts +11 -0
- package/dist/components/basic/ButtonGroup.svelte +15 -0
- package/dist/components/basic/ButtonGroup.svelte.d.ts +8 -0
- package/dist/components/basic/Checkbox.svelte +85 -0
- package/dist/components/basic/Checkbox.svelte.d.ts +15 -0
- package/dist/components/basic/Empty.svelte +17 -0
- package/dist/components/basic/Empty.svelte.d.ts +8 -0
- package/dist/components/basic/Kbd.svelte +16 -0
- package/dist/components/basic/Kbd.svelte.d.ts +8 -0
- package/dist/components/basic/Pagination.svelte +104 -0
- package/dist/components/basic/Pagination.svelte.d.ts +12 -0
- package/dist/components/basic/RadioGroup.svelte +71 -0
- package/dist/components/basic/RadioGroup.svelte.d.ts +18 -0
- package/dist/components/basic/Separator.svelte +19 -0
- package/dist/components/basic/Separator.svelte.d.ts +8 -0
- package/dist/components/basic/Slider.svelte +72 -0
- package/dist/components/basic/Slider.svelte.d.ts +17 -0
- package/dist/components/basic/Switch.svelte +88 -0
- package/dist/components/basic/Switch.svelte.d.ts +14 -0
- package/dist/components/basic/Toggle.svelte +43 -0
- package/dist/components/basic/Toggle.svelte.d.ts +14 -0
- package/dist/components/graph/GraphCanvas.svelte +103 -0
- package/dist/components/graph/GraphCanvas.svelte.d.ts +22 -0
- package/dist/components/graph/JatuiNode.svelte +128 -0
- package/dist/components/graph/JatuiNode.svelte.d.ts +14 -0
- package/dist/components/graph/theme.d.ts +27 -0
- package/dist/components/graph/theme.js +136 -0
- package/dist/components/messaging/MessageItem.svelte +92 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +5 -0
- package/dist/components/messaging/TypingIndicator.svelte +63 -0
- package/dist/components/messaging/TypingIndicator.svelte.d.ts +7 -0
- package/dist/components/pipeline/PipelineColumn.svelte +44 -3
- package/dist/components/progress/ProgressBar.svelte +189 -0
- package/dist/components/progress/ProgressBar.svelte.d.ts +17 -0
- package/dist/components/progress/ProgressRing.svelte +138 -0
- package/dist/components/progress/ProgressRing.svelte.d.ts +12 -0
- package/dist/components/progress/Steps.svelte +195 -0
- package/dist/components/progress/Steps.svelte.d.ts +14 -0
- package/dist/components/shell/NavigationMenu.svelte +422 -0
- package/dist/components/shell/NavigationMenu.svelte.d.ts +43 -0
- package/dist/components/shell/Sidebar.svelte +276 -0
- package/dist/components/shell/Sidebar.svelte.d.ts +32 -0
- package/dist/components/shell/SidebarGroup.svelte +180 -0
- package/dist/components/shell/SidebarGroup.svelte.d.ts +19 -0
- package/dist/components/shell/SidebarItem.svelte +170 -0
- package/dist/components/shell/SidebarItem.svelte.d.ts +24 -0
- package/dist/components/shell/shell.d.ts +30 -0
- package/dist/components/shell/shell.js +65 -0
- package/dist/components/table/DataTable.svelte +364 -0
- package/dist/components/table/DataTable.svelte.d.ts +4 -0
- package/dist/components/table/flexRender.d.ts +31 -0
- package/dist/components/table/flexRender.js +32 -0
- package/dist/components/table/types.d.ts +28 -0
- package/dist/components/table/types.js +1 -0
- package/dist/components/toast/ToastHost.svelte +21 -0
- package/dist/components/toast/ToastHost.svelte.d.ts +3 -0
- package/dist/components/toast/ToastItem.svelte +287 -0
- package/dist/components/toast/ToastItem.svelte.d.ts +9 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +67 -0
- package/dist/showcase/AnimatedBeam.svelte +183 -0
- package/dist/showcase/AnimatedBeam.svelte.d.ts +26 -0
- package/dist/showcase/AuroraDrift.svelte +74 -0
- package/dist/showcase/AuroraDrift.svelte.d.ts +9 -0
- package/dist/showcase/BentoCard.svelte +50 -0
- package/dist/showcase/BentoCard.svelte.d.ts +14 -0
- package/dist/showcase/BentoGrid.svelte +43 -0
- package/dist/showcase/BentoGrid.svelte.d.ts +12 -0
- package/dist/showcase/BorderBeam.svelte +79 -0
- package/dist/showcase/BorderBeam.svelte.d.ts +12 -0
- package/dist/showcase/ClipWipe.svelte +82 -0
- package/dist/showcase/ClipWipe.svelte.d.ts +12 -0
- package/dist/showcase/Confetti.svelte +71 -0
- package/dist/showcase/Confetti.svelte.d.ts +10 -0
- package/dist/showcase/CursorSpotlight.svelte +85 -0
- package/dist/showcase/CursorSpotlight.svelte.d.ts +9 -0
- package/dist/showcase/DeviceMock.svelte +186 -0
- package/dist/showcase/DeviceMock.svelte.d.ts +11 -0
- package/dist/showcase/Dock.svelte +123 -0
- package/dist/showcase/Dock.svelte.d.ts +17 -0
- package/dist/showcase/FlipCard.svelte +103 -0
- package/dist/showcase/FlipCard.svelte.d.ts +10 -0
- package/dist/showcase/FocusReveal.svelte +83 -0
- package/dist/showcase/FocusReveal.svelte.d.ts +11 -0
- package/dist/showcase/Globe.svelte +202 -0
- package/dist/showcase/Globe.svelte.d.ts +16 -0
- package/dist/showcase/KenBurns.svelte +105 -0
- package/dist/showcase/KenBurns.svelte.d.ts +13 -0
- package/dist/showcase/Marquee.svelte +89 -0
- package/dist/showcase/Marquee.svelte.d.ts +9 -0
- package/dist/showcase/ParallaxTilt.svelte +98 -0
- package/dist/showcase/ParallaxTilt.svelte.d.ts +10 -0
- package/dist/showcase/ScrambleText.svelte +98 -0
- package/dist/showcase/ScrambleText.svelte.d.ts +11 -0
- package/dist/showcase/ShineSweep.svelte +81 -0
- package/dist/showcase/ShineSweep.svelte.d.ts +9 -0
- package/dist/showcase/Terminal.svelte +230 -0
- package/dist/showcase/Terminal.svelte.d.ts +18 -0
- package/dist/showcase/TextSplitReveal.svelte +146 -0
- package/dist/showcase/TextSplitReveal.svelte.d.ts +13 -0
- package/dist/showcase/TextWave.svelte +55 -0
- package/dist/showcase/TextWave.svelte.d.ts +8 -0
- package/dist/showcase/TypewriterText.svelte +130 -0
- package/dist/showcase/TypewriterText.svelte.d.ts +9 -0
- package/dist/showcase/backdrop/GrainGradient.svelte +126 -0
- package/dist/showcase/backdrop/GrainGradient.svelte.d.ts +23 -0
- package/dist/showcase/backdrop/MeshGradient.svelte +104 -0
- package/dist/showcase/backdrop/MeshGradient.svelte.d.ts +20 -0
- package/dist/showcase/backdrop/Metaballs.svelte +116 -0
- package/dist/showcase/backdrop/Metaballs.svelte.d.ts +18 -0
- package/dist/showcase/backdrop/NeuroNoise.svelte +115 -0
- package/dist/showcase/backdrop/NeuroNoise.svelte.d.ts +20 -0
- package/dist/showcase/backdrop/ShowcaseBackdrop.svelte +48 -0
- package/dist/showcase/backdrop/ShowcaseBackdrop.svelte.d.ts +12 -0
- package/dist/showcase/generative/GenerativeBackdrop.svelte +234 -0
- package/dist/showcase/generative/GenerativeBackdrop.svelte.d.ts +9 -0
- package/dist/showcase/generative/intent.d.ts +7 -0
- package/dist/showcase/generative/intent.js +67 -0
- package/dist/showcase/generative/manifest.d.ts +9 -0
- package/dist/showcase/generative/manifest.js +97 -0
- package/dist/showcase/generative/types.d.ts +48 -0
- package/dist/showcase/generative/types.js +8 -0
- package/dist/showcase/index.d.ts +36 -0
- package/dist/showcase/index.js +46 -0
- package/dist/showcase/scroll/SmoothScroll.svelte +49 -0
- package/dist/showcase/scroll/SmoothScroll.svelte.d.ts +12 -0
- package/dist/showcase/scroll/scrollProgress.d.ts +24 -0
- package/dist/showcase/scroll/scrollProgress.js +65 -0
- package/dist/showcase/scroll/scrollReveal.d.ts +30 -0
- package/dist/showcase/scroll/scrollReveal.js +70 -0
- package/dist/stores/commandPalette.svelte.d.ts +24 -0
- package/dist/stores/commandPalette.svelte.js +82 -0
- package/dist/stores/toast.svelte.d.ts +28 -0
- package/dist/stores/toast.svelte.js +33 -0
- package/dist/styles/motion.css +46 -0
- package/dist/styles/tokens.css +31 -0
- package/dist/transitions/glide.d.ts +31 -0
- package/dist/transitions/glide.js +58 -0
- package/dist/utils/sound.d.ts +6 -0
- package/dist/utils/sound.js +113 -0
- package/dist/utils/splitText.d.ts +25 -0
- package/dist/utils/splitText.js +94 -0
- package/dist/utils/themeWipe.d.ts +21 -0
- package/dist/utils/themeWipe.js +94 -0
- package/dist/utils/viewTransition.d.ts +23 -0
- package/dist/utils/viewTransition.js +75 -0
- 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,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;
|