@impractical-ai/motion 0.1.5 → 0.1.7
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/README.md +19 -0
- package/assets/engine.d.ts +793 -5
- package/assets/impractical/SKILL.md +53 -6
- package/assets/kits/actions.ts +803 -0
- package/assets/kits/ensemble.ts +483 -0
- package/assets/kits/highlight.ts +374 -0
- package/assets/kits/impact.ts +103 -0
- package/assets/kits/kits.ts +343 -0
- package/assets/kits/looks.ts +935 -0
- package/assets/kits/morphkit.ts +438 -0
- package/assets/kits/reveal.ts +1364 -0
- package/assets/kits/sentence.ts +218 -0
- package/assets/kits/streamkit.ts +195 -0
- package/dist/{api-KD6BNKBQ.js → api-DR3NJ7TI.js} +5 -2
- package/dist/chunk-77HVPD4G.js +43 -0
- package/dist/{chunk-DMAQPSA4.js → chunk-M7SHFM25.js} +222 -27
- package/dist/{chunk-7724464Z.js → chunk-MQKW7NFI.js} +65 -14
- package/dist/chunk-Z74SLKEU.js +67 -0
- package/dist/{commands-Y35XPDVH.js → commands-R3LJU2BW.js} +29 -4
- package/dist/index.js +6 -5
- package/dist/main-BEONOBG5.js +2218 -0
- package/dist/{stdio-ZFPBM6Z6.js → stdio-XUVQKDCB.js} +3169 -431
- package/package.json +4 -3
- package/dist/chunk-JDN3QHCX.js +0 -98
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// The live-sentence kit: a sentence is a stateful object, not a static block.
|
|
2
|
+
// Words land into it, slots cycle, one accent color stresses the pivot, words
|
|
3
|
+
// morph, and a word slot can hold a media chip. Evidence: Dropbox Dash,
|
|
4
|
+
// Pitch 2.0, Apple News+ (see references/cards/ + docs/motion-vocabulary-log.md).
|
|
5
|
+
//
|
|
6
|
+
// Determinism: every state change is a tween on the master timeline. New
|
|
7
|
+
// words are mounted at build time (hidden, holding layout space) so seeking
|
|
8
|
+
// is pure; optional `recenter` shifts the container by half the added width
|
|
9
|
+
// instead of re-flowing live.
|
|
10
|
+
import { resolveTarget } from './kits'
|
|
11
|
+
|
|
12
|
+
type Position = number | string | undefined
|
|
13
|
+
|
|
14
|
+
const ENTER_EASE = 'ease-out-quint'
|
|
15
|
+
const MOVE_EASE = 'ease-in-out-cubic'
|
|
16
|
+
const DEFAULT_ACCENT = '#2f6bff'
|
|
17
|
+
|
|
18
|
+
export type SentenceHandle = {
|
|
19
|
+
el: HTMLElement
|
|
20
|
+
append: (text: string, opts?: { stagger?: number; duration?: number; recenter?: boolean; stress?: boolean }, position?: Position) => { duration: number }
|
|
21
|
+
swap: (slot: string, alternatives: string[], opts?: { interval?: number; stress?: boolean; align?: 'anchor' | 'recenter' }, position?: Position) => { duration: number }
|
|
22
|
+
stress: (word: string, opts?: { color?: string; duration?: number }, position?: Position) => { duration: number }
|
|
23
|
+
morphWord: (from: string, to: string, opts?: { duration?: number }, position?: Position) => { duration: number }
|
|
24
|
+
chip: (word: string, media: HTMLElement, opts?: { height?: number; radius?: number }, position?: Position) => HTMLElement
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createSentenceKit(deps: {
|
|
28
|
+
tl: any
|
|
29
|
+
gsap: any
|
|
30
|
+
stage: HTMLElement
|
|
31
|
+
accent?: string
|
|
32
|
+
}) {
|
|
33
|
+
const { tl, gsap, stage } = deps
|
|
34
|
+
const accent = deps.accent ?? DEFAULT_ACCENT
|
|
35
|
+
const at = (position: Position): number => (typeof position === 'number' ? position : tl.duration())
|
|
36
|
+
|
|
37
|
+
const measureWith = (el: HTMLElement): ((s: string) => number) => {
|
|
38
|
+
const style = getComputedStyle(el)
|
|
39
|
+
const ctx = document.createElement('canvas').getContext('2d')!
|
|
40
|
+
ctx.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`
|
|
41
|
+
return (s) => ctx.measureText(s).width
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const wordSpan = (word: string): HTMLElement => {
|
|
45
|
+
const span = document.createElement('span')
|
|
46
|
+
span.dataset.word = word
|
|
47
|
+
span.style.cssText = 'display:inline-block;white-space:pre;'
|
|
48
|
+
span.textContent = word
|
|
49
|
+
return span
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const sentence = (target: string | Element): SentenceHandle => {
|
|
53
|
+
const el = resolveTarget(stage, target, 'sentence')
|
|
54
|
+
// Take over: rebuild existing text as word spans so every word is a target.
|
|
55
|
+
const initial = (el.textContent ?? '').trim()
|
|
56
|
+
el.textContent = ''
|
|
57
|
+
const spans: HTMLElement[] = []
|
|
58
|
+
const mountWords = (text: string): HTMLElement[] => {
|
|
59
|
+
const out: HTMLElement[] = []
|
|
60
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
61
|
+
if (spans.length || out.length) {
|
|
62
|
+
const gap = document.createElement('span')
|
|
63
|
+
gap.textContent = ' '
|
|
64
|
+
gap.style.whiteSpace = 'pre'
|
|
65
|
+
el.appendChild(gap)
|
|
66
|
+
}
|
|
67
|
+
const span = wordSpan(word)
|
|
68
|
+
el.appendChild(span)
|
|
69
|
+
out.push(span)
|
|
70
|
+
spans.push(span)
|
|
71
|
+
}
|
|
72
|
+
return out
|
|
73
|
+
}
|
|
74
|
+
if (initial) mountWords(initial)
|
|
75
|
+
|
|
76
|
+
const find = (word: string): HTMLElement | null =>
|
|
77
|
+
spans.find((s) => s.dataset.word === word || s.textContent === word) ?? null
|
|
78
|
+
|
|
79
|
+
const handle: SentenceHandle = {
|
|
80
|
+
el,
|
|
81
|
+
|
|
82
|
+
append: (text, opts = {}, position) => {
|
|
83
|
+
const start = at(position)
|
|
84
|
+
const added = mountWords(text)
|
|
85
|
+
const stagger = opts.stagger ?? 0.14
|
|
86
|
+
const duration = opts.duration ?? 0.45
|
|
87
|
+
const measure = measureWith(el)
|
|
88
|
+
// Hidden from frame 0, holding space — entrance is a pure tween.
|
|
89
|
+
for (const [i, span] of added.entries()) {
|
|
90
|
+
gsap.set(span, { autoAlpha: 0, yPercent: 55 })
|
|
91
|
+
tl.to(span, { autoAlpha: 1, yPercent: 0, duration, ease: ENTER_EASE }, start + i * stagger)
|
|
92
|
+
if (opts.stress) tl.set(span, { color: accent }, start + i * stagger)
|
|
93
|
+
}
|
|
94
|
+
if (opts.recenter) {
|
|
95
|
+
// Visually re-center without live reflow: pre-shift by half the
|
|
96
|
+
// added width, then ease back to rest as the words land.
|
|
97
|
+
const addedWidth = added.reduce((w, s) => w + measure(`${s.textContent} `), 0)
|
|
98
|
+
gsap.set(el, { x: addedWidth / 2 })
|
|
99
|
+
tl.to(el, { x: 0, duration: duration + stagger * added.length, ease: MOVE_EASE }, start)
|
|
100
|
+
}
|
|
101
|
+
return { duration: duration + stagger * Math.max(0, added.length - 1) }
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
swap: (slot, alternatives, opts = {}, position) => {
|
|
105
|
+
const span = find(slot)
|
|
106
|
+
if (!span) return { duration: 0 }
|
|
107
|
+
const start = at(position)
|
|
108
|
+
const interval = opts.interval ?? 0.7
|
|
109
|
+
// 'anchor' (default): the slot reserves the WIDEST alternative up
|
|
110
|
+
// front, so a centered sentence never shifts as words cycle.
|
|
111
|
+
// 'recenter': the slot width animates and the line re-flows live.
|
|
112
|
+
const align = opts.align ?? 'anchor'
|
|
113
|
+
const measure = measureWith(el)
|
|
114
|
+
// The slot becomes a fixed-height window; alternates rise through it.
|
|
115
|
+
const holder = document.createElement('span')
|
|
116
|
+
holder.style.cssText = 'display:inline-block;position:relative;white-space:pre;vertical-align:baseline;'
|
|
117
|
+
span.parentElement?.insertBefore(holder, span)
|
|
118
|
+
holder.appendChild(span)
|
|
119
|
+
span.style.display = 'inline-block'
|
|
120
|
+
const layers: HTMLElement[] = [span]
|
|
121
|
+
for (const alt of alternatives) {
|
|
122
|
+
const layer = wordSpan(alt)
|
|
123
|
+
layer.style.cssText += 'position:absolute;left:0;top:0;'
|
|
124
|
+
if (opts.stress !== false) layer.style.color = accent
|
|
125
|
+
gsap.set(layer, { autoAlpha: 0 })
|
|
126
|
+
holder.appendChild(layer)
|
|
127
|
+
layers.push(layer)
|
|
128
|
+
// Later verbs must be able to find the word that ends up visible.
|
|
129
|
+
spans.push(layer)
|
|
130
|
+
}
|
|
131
|
+
if (opts.stress !== false) span.style.color = accent
|
|
132
|
+
const widths = [measure(span.textContent ?? ''), ...alternatives.map((a) => measure(a))]
|
|
133
|
+
if (align === 'anchor') {
|
|
134
|
+
gsap.set(holder, { width: Math.max(...widths) })
|
|
135
|
+
} else {
|
|
136
|
+
gsap.set(holder, { width: widths[0] })
|
|
137
|
+
}
|
|
138
|
+
for (let i = 1; i < layers.length; i += 1) {
|
|
139
|
+
const t = start + (i - 1) * interval
|
|
140
|
+
tl.to(layers[i - 1], { autoAlpha: 0, yPercent: -50, duration: 0.28, ease: 'ease-in-quad' }, t)
|
|
141
|
+
tl.fromTo(layers[i], { autoAlpha: 0, yPercent: 55 }, { autoAlpha: 1, yPercent: 0, duration: 0.34, ease: ENTER_EASE }, t + 0.06)
|
|
142
|
+
if (align === 'recenter') {
|
|
143
|
+
tl.to(holder, { width: widths[i], duration: 0.3, ease: MOVE_EASE }, t)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { duration: (layers.length - 1) * interval + 0.4 }
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
stress: (word, opts = {}, position) => {
|
|
150
|
+
const span = find(word)
|
|
151
|
+
if (!span) return { duration: 0 }
|
|
152
|
+
const duration = opts.duration ?? 0.3
|
|
153
|
+
tl.to(span, { color: opts.color ?? accent, duration, ease: MOVE_EASE }, at(position))
|
|
154
|
+
return { duration }
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
morphWord: (from, to, opts = {}, position) => {
|
|
158
|
+
const span = find(from)
|
|
159
|
+
if (!span) return { duration: 0 }
|
|
160
|
+
const start = at(position)
|
|
161
|
+
const duration = opts.duration ?? 0.32
|
|
162
|
+
// Keep the shared prefix; crossfade only the differing tail, so
|
|
163
|
+
// "it!" → "it?" reads as the punctuation flipping.
|
|
164
|
+
const a = span.textContent ?? ''
|
|
165
|
+
let p = 0
|
|
166
|
+
while (p < a.length && p < to.length && a[p] === to[p]) p += 1
|
|
167
|
+
const prefix = a.slice(0, p)
|
|
168
|
+
span.textContent = ''
|
|
169
|
+
span.dataset.word = to
|
|
170
|
+
const stay = document.createElement('span')
|
|
171
|
+
stay.textContent = prefix
|
|
172
|
+
stay.style.whiteSpace = 'pre'
|
|
173
|
+
const holder = document.createElement('span')
|
|
174
|
+
holder.style.cssText = 'display:inline-block;position:relative;white-space:pre;'
|
|
175
|
+
const oldTail = document.createElement('span')
|
|
176
|
+
oldTail.textContent = a.slice(p)
|
|
177
|
+
oldTail.style.cssText = 'display:inline-block;white-space:pre;'
|
|
178
|
+
const newTail = document.createElement('span')
|
|
179
|
+
newTail.textContent = to.slice(p)
|
|
180
|
+
newTail.style.cssText = 'display:inline-block;white-space:pre;position:absolute;left:0;top:0;'
|
|
181
|
+
gsap.set(newTail, { autoAlpha: 0, yPercent: 45 })
|
|
182
|
+
holder.appendChild(oldTail)
|
|
183
|
+
holder.appendChild(newTail)
|
|
184
|
+
span.appendChild(stay)
|
|
185
|
+
span.appendChild(holder)
|
|
186
|
+
tl.to(oldTail, { autoAlpha: 0, yPercent: -45, duration, ease: 'ease-in-quad' }, start)
|
|
187
|
+
tl.to(newTail, { autoAlpha: 1, yPercent: 0, duration, ease: ENTER_EASE }, start + 0.05)
|
|
188
|
+
return { duration: duration + 0.05 }
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
chip: (word, media, opts = {}, position) => {
|
|
192
|
+
const span = find(word)
|
|
193
|
+
const start = at(position)
|
|
194
|
+
const height = opts.height ?? 1.05
|
|
195
|
+
const chipEl = document.createElement('span')
|
|
196
|
+
chipEl.dataset.chip = ''
|
|
197
|
+
chipEl.style.cssText =
|
|
198
|
+
`display:inline-block;vertical-align:-0.18em;height:${height}em;` +
|
|
199
|
+
`border-radius:${opts.radius ?? 0.28}em;overflow:hidden;position:relative;`
|
|
200
|
+
media.style.cssText += ';display:block;height:100%;width:auto;'
|
|
201
|
+
chipEl.appendChild(media)
|
|
202
|
+
if (span) {
|
|
203
|
+
span.parentElement?.insertBefore(chipEl, span)
|
|
204
|
+
// The word makes room, the chip pops in its place.
|
|
205
|
+
gsap.set(span, { display: 'none' })
|
|
206
|
+
} else {
|
|
207
|
+
el.appendChild(chipEl)
|
|
208
|
+
}
|
|
209
|
+
gsap.set(chipEl, { width: 0, autoAlpha: 0 })
|
|
210
|
+
tl.to(chipEl, { width: 'auto', autoAlpha: 1, duration: 0.4, ease: ENTER_EASE }, start)
|
|
211
|
+
return chipEl
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
return handle
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { sentence }
|
|
218
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Streaming states — the choreographed WAITING that AI products live in.
|
|
2
|
+
// Mined from Dropbox Dash, Pitch 2.0 and Notion AI (3/3 films treat the wait
|
|
3
|
+
// as a beat, not a gap): an answer arrives word by word, placeholders shimmer
|
|
4
|
+
// while work happens, and a status line names what is being done.
|
|
5
|
+
//
|
|
6
|
+
// Determinism: every state is a pure function of timeline position — word
|
|
7
|
+
// reveal runs on a stepped proxy, the shimmer is a tweened background offset,
|
|
8
|
+
// status phases are value tweens. Nothing reads a clock, nothing is random,
|
|
9
|
+
// and every element ends at an exact final state (fully revealed / removed).
|
|
10
|
+
import { resolveTarget } from './kits'
|
|
11
|
+
|
|
12
|
+
type Position = number | string | undefined
|
|
13
|
+
|
|
14
|
+
export type StreamKit = {
|
|
15
|
+
/** An answer arrives WORD BY WORD, LLM-style. The element's text is split
|
|
16
|
+
* into word spans at build time (layout is therefore stable from frame 0);
|
|
17
|
+
* the reveal is a stepped proxy, so seeking anywhere lands on the exact
|
|
18
|
+
* word count for that time. No caret — this is a machine writing, not a
|
|
19
|
+
* person typing (use `typeInto` for a person). */
|
|
20
|
+
answer: (
|
|
21
|
+
target: string | Element,
|
|
22
|
+
text?: string,
|
|
23
|
+
opts?: { wps?: number; duration?: number; rise?: number },
|
|
24
|
+
position?: Position
|
|
25
|
+
) => { duration: number }
|
|
26
|
+
/** Placeholder lines that shimmer while work happens, then resolve into
|
|
27
|
+
* the real content. Mounts over the target, so the target's own layout is
|
|
28
|
+
* untouched. `lines` defaults to 3; `resolve` is how long the crossfade to
|
|
29
|
+
* real content takes. */
|
|
30
|
+
skeleton: (
|
|
31
|
+
target: string | Element,
|
|
32
|
+
opts?: { lines?: number; duration?: number; resolve?: number; widths?: number[] },
|
|
33
|
+
position?: Position
|
|
34
|
+
) => { duration: number }
|
|
35
|
+
/** A status line naming the work: 'Analyzing' → 'Searching 3 sources' →
|
|
36
|
+
* 'Writing'. Each phase holds `interval` seconds; the last one stays until
|
|
37
|
+
* the caller hides it. Phases rise in place, so the line never jumps. */
|
|
38
|
+
status: (
|
|
39
|
+
target: string | Element,
|
|
40
|
+
phases: string[],
|
|
41
|
+
opts?: { interval?: number; done?: boolean },
|
|
42
|
+
position?: Position
|
|
43
|
+
) => { duration: number }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createStreamKit(deps: {
|
|
47
|
+
tl: any
|
|
48
|
+
gsap: any
|
|
49
|
+
stage: HTMLElement
|
|
50
|
+
}): StreamKit {
|
|
51
|
+
const { tl, gsap, stage } = deps
|
|
52
|
+
const at = (position: Position): number => (typeof position === 'number' ? position : tl.duration())
|
|
53
|
+
|
|
54
|
+
const answer: StreamKit['answer'] = (target, text, opts = {}, position) => {
|
|
55
|
+
const el = resolveTarget(stage, target, 'stream.answer')
|
|
56
|
+
const source = (text ?? el.textContent ?? '').trim()
|
|
57
|
+
const words = source.split(/\s+/).filter(Boolean)
|
|
58
|
+
if (!words.length) return { duration: 0 }
|
|
59
|
+
const wps = opts.wps ?? 9
|
|
60
|
+
const duration = opts.duration ?? words.length / wps
|
|
61
|
+
const rise = opts.rise ?? 6
|
|
62
|
+
const start = at(position)
|
|
63
|
+
|
|
64
|
+
// Words are mounted (and therefore laid out) at build time; only their
|
|
65
|
+
// visibility animates, so nothing reflows mid-stream.
|
|
66
|
+
el.textContent = ''
|
|
67
|
+
const spans: HTMLElement[] = words.map((w, i) => {
|
|
68
|
+
const span = document.createElement('span')
|
|
69
|
+
span.textContent = i === 0 ? w : ` ${w}`
|
|
70
|
+
span.style.cssText = 'display:inline-block;white-space:pre;'
|
|
71
|
+
el.appendChild(span)
|
|
72
|
+
return span
|
|
73
|
+
})
|
|
74
|
+
for (const s of spans) gsap.set(s, { autoAlpha: 0, y: rise })
|
|
75
|
+
|
|
76
|
+
const proxy = { n: 0 }
|
|
77
|
+
let shown = 0
|
|
78
|
+
tl.to(
|
|
79
|
+
proxy,
|
|
80
|
+
{
|
|
81
|
+
n: words.length,
|
|
82
|
+
duration,
|
|
83
|
+
ease: `steps(${words.length})`,
|
|
84
|
+
onUpdate: () => {
|
|
85
|
+
const n = Math.max(0, Math.min(words.length, Math.round(proxy.n)))
|
|
86
|
+
if (n === shown) return
|
|
87
|
+
// Seek-safe: recompute every word's state from n, never increment.
|
|
88
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
89
|
+
gsap.set(spans[i], { autoAlpha: i < n ? 1 : 0, y: i < n ? 0 : rise })
|
|
90
|
+
}
|
|
91
|
+
shown = n
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
start
|
|
95
|
+
)
|
|
96
|
+
// Exact end state for cold-seeking export workers.
|
|
97
|
+
tl.set(spans, { autoAlpha: 1, y: 0 }, start + duration)
|
|
98
|
+
return { duration }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Nearest painted background, so placeholders can pick a contrasting tone.
|
|
102
|
+
* A dark-surface skeleton drawn in black ink is invisible — the bug this
|
|
103
|
+
* exists to prevent. */
|
|
104
|
+
const surfaceIsDark = (el: HTMLElement): boolean => {
|
|
105
|
+
let node: HTMLElement | null = el
|
|
106
|
+
while (node) {
|
|
107
|
+
const bg = getComputedStyle(node).backgroundColor
|
|
108
|
+
const m = bg.match(/rgba?\(([^)]+)\)/)
|
|
109
|
+
if (m) {
|
|
110
|
+
const [r, g, b, a] = m[1].split(',').map((v) => parseFloat(v))
|
|
111
|
+
if (a === undefined || a > 0.05) {
|
|
112
|
+
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 < 0.5
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
node = node.parentElement
|
|
116
|
+
}
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const skeleton: StreamKit['skeleton'] = (target, opts = {}, position) => {
|
|
121
|
+
const el = resolveTarget(stage, target, 'stream.skeleton')
|
|
122
|
+
const ink = surfaceIsDark(el) ? '255,255,255' : '0,0,0'
|
|
123
|
+
const lines = opts.lines ?? 3
|
|
124
|
+
const duration = opts.duration ?? 1.4
|
|
125
|
+
const resolve = opts.resolve ?? 0.3
|
|
126
|
+
const widths = opts.widths ?? [1, 0.82, 0.64]
|
|
127
|
+
const start = at(position)
|
|
128
|
+
|
|
129
|
+
if (getComputedStyle(el).position === 'static') el.style.position = 'relative'
|
|
130
|
+
const holder = document.createElement('div')
|
|
131
|
+
holder.dataset.skeleton = ''
|
|
132
|
+
holder.style.cssText = 'position:absolute;inset:0;pointer-events:none;'
|
|
133
|
+
const bars: HTMLElement[] = []
|
|
134
|
+
for (let i = 0; i < lines; i += 1) {
|
|
135
|
+
const bar = document.createElement('div')
|
|
136
|
+
bar.style.cssText =
|
|
137
|
+
`position:absolute;left:0;top:${i * 34}px;height:16px;border-radius:8px;` +
|
|
138
|
+
`width:${Math.round((widths[i % widths.length] ?? 0.7) * 100)}%;` +
|
|
139
|
+
// A soft moving gradient — never a 1px pattern (those alias in H.264).
|
|
140
|
+
`background:linear-gradient(90deg, rgba(${ink},0.10) 0%, rgba(${ink},0.22) 45%, rgba(${ink},0.10) 100%);` +
|
|
141
|
+
`background-size:220% 100%;`
|
|
142
|
+
holder.appendChild(bar)
|
|
143
|
+
bars.push(bar)
|
|
144
|
+
}
|
|
145
|
+
el.appendChild(holder)
|
|
146
|
+
|
|
147
|
+
// Real content is hidden behind the skeleton, then crossfaded in.
|
|
148
|
+
const content = Array.from(el.children).filter((c) => c !== holder) as HTMLElement[]
|
|
149
|
+
gsap.set(content, { autoAlpha: 0 })
|
|
150
|
+
gsap.set(holder, { autoAlpha: 1 })
|
|
151
|
+
for (const [i, bar] of bars.entries()) {
|
|
152
|
+
tl.fromTo(
|
|
153
|
+
bar,
|
|
154
|
+
{ backgroundPosition: '120% 0' },
|
|
155
|
+
{ backgroundPosition: '-120% 0', duration: duration * 0.55, ease: 'none', repeat: 1 },
|
|
156
|
+
start + i * 0.08
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
tl.to(holder, { autoAlpha: 0, duration: resolve, ease: 'ease-in-quad' }, start + duration)
|
|
160
|
+
tl.to(content, { autoAlpha: 1, duration: resolve, ease: 'ease-out-quad' }, start + duration)
|
|
161
|
+
return { duration: duration + resolve }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const status: StreamKit['status'] = (target, phases, opts = {}, position) => {
|
|
165
|
+
const el = resolveTarget(stage, target, 'stream.status')
|
|
166
|
+
if (!phases.length) return { duration: 0 }
|
|
167
|
+
const interval = opts.interval ?? 1.1
|
|
168
|
+
const start = at(position)
|
|
169
|
+
el.textContent = ''
|
|
170
|
+
if (getComputedStyle(el).position === 'static') el.style.position = 'relative'
|
|
171
|
+
const layers = phases.map((p) => {
|
|
172
|
+
const span = document.createElement('span')
|
|
173
|
+
span.textContent = p
|
|
174
|
+
span.style.cssText = 'position:absolute;left:0;top:0;white-space:pre;'
|
|
175
|
+
el.appendChild(span)
|
|
176
|
+
return span
|
|
177
|
+
})
|
|
178
|
+
// The line keeps its box: the element holds the widest phase.
|
|
179
|
+
el.style.minHeight = '1em'
|
|
180
|
+
gsap.set(layers, { autoAlpha: 0, y: 8 })
|
|
181
|
+
gsap.set(layers[0], { autoAlpha: 1, y: 0 })
|
|
182
|
+
for (let i = 1; i < layers.length; i += 1) {
|
|
183
|
+
const t = start + i * interval
|
|
184
|
+
tl.to(layers[i - 1], { autoAlpha: 0, y: -8, duration: 0.22, ease: 'ease-in-quad' }, t)
|
|
185
|
+
tl.fromTo(layers[i], { autoAlpha: 0, y: 8 }, { autoAlpha: 1, y: 0, duration: 0.26, ease: 'ease-out-quint' }, t + 0.05)
|
|
186
|
+
}
|
|
187
|
+
const total = phases.length * interval
|
|
188
|
+
if (opts.done !== false) {
|
|
189
|
+
tl.to(layers[layers.length - 1], { autoAlpha: 0, duration: 0.22, ease: 'ease-in-quad' }, start + total)
|
|
190
|
+
}
|
|
191
|
+
return { duration: total }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { answer, skeleton, status }
|
|
195
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ApiContractError,
|
|
4
4
|
ApiError,
|
|
5
|
+
ApiTransportError,
|
|
5
6
|
admitMcpCall,
|
|
6
7
|
delay,
|
|
7
8
|
invoke,
|
|
@@ -12,11 +13,13 @@ import {
|
|
|
12
13
|
waitForJobs,
|
|
13
14
|
withAdmissionReceipt,
|
|
14
15
|
withMeteredCall
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-MQKW7NFI.js";
|
|
17
|
+
import "./chunk-Z74SLKEU.js";
|
|
18
|
+
import "./chunk-77HVPD4G.js";
|
|
17
19
|
export {
|
|
18
20
|
ApiContractError,
|
|
19
21
|
ApiError,
|
|
22
|
+
ApiTransportError,
|
|
20
23
|
admitMcpCall,
|
|
21
24
|
delay,
|
|
22
25
|
invoke,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
__require,
|
|
40
|
+
__commonJS,
|
|
41
|
+
__export,
|
|
42
|
+
__toESM
|
|
43
|
+
};
|