@spotpatch/runtime 1.12.0 → 1.13.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/dist/motion.js ADDED
@@ -0,0 +1,1092 @@
1
+ import {
2
+ BRAND_MARK_CONTENT,
3
+ createBrandMark,
4
+ getFloatingSurfaceMotionExtension,
5
+ registerFloatingSurfaceMotionExtension
6
+ } from "./chunk-P7W3F4UA.js";
7
+ import {
8
+ createButton,
9
+ createMarkedElement
10
+ } from "./chunk-P6X75LO5.js";
11
+
12
+ // src/ui/motion-controller.ts
13
+ import { gsap } from "gsap";
14
+ var MOTION = Object.freeze({
15
+ contentMilliseconds: 220,
16
+ detailRevealDelaySeconds: 0.1,
17
+ detailRevealSeconds: 0.3,
18
+ detailRetireSeconds: 0.15,
19
+ dispatchSeconds: 0.54,
20
+ expandSeconds: 0.54,
21
+ morphSeconds: 0.48,
22
+ pressSeconds: 0.12,
23
+ revealSeconds: 0.26,
24
+ sceneRevealDelaySeconds: 0.06,
25
+ sweepSeconds: 0.68,
26
+ targetFeedbackDelaySeconds: 0.08,
27
+ targetFeedbackSeconds: 0.22
28
+ });
29
+ var ISLAND_SIZE = Object.freeze({
30
+ checkingWidth: 440,
31
+ compactHeight: 62,
32
+ dispatchingWidth: 448,
33
+ expandedHeight: 164,
34
+ expandedWidth: 520,
35
+ failedWidth: 440,
36
+ receivingWidth: 430,
37
+ runningWidth: 468,
38
+ successWidth: 356
39
+ });
40
+ function surfaceLayer(scene) {
41
+ if (scene === "pill" || scene === "capturing") return "pill";
42
+ if (scene === "planner") return "planner";
43
+ return "execution";
44
+ }
45
+ function setSceneVisibility(elements, projection) {
46
+ const pillActive = projection.scene === "pill" || projection.scene === "capturing";
47
+ const plannerActive = projection.scene === "planner";
48
+ const executionActive = !pillActive && !plannerActive;
49
+ elements.pill.hidden = !pillActive;
50
+ elements.pill.inert = !pillActive;
51
+ elements.planner.hidden = !plannerActive;
52
+ elements.planner.inert = !plannerActive;
53
+ elements.planner.setAttribute("aria-hidden", String(!plannerActive));
54
+ elements.execution.root.hidden = !executionActive;
55
+ elements.execution.root.inert = !executionActive;
56
+ elements.execution.root.setAttribute("aria-hidden", String(!executionActive));
57
+ return pillActive ? elements.pill : plannerActive ? elements.planner : elements.execution.root;
58
+ }
59
+ function supportsMotion(document) {
60
+ return document.defaultView?.matchMedia("(prefers-reduced-motion: reduce)").matches !== true;
61
+ }
62
+ function finiteRect(rect) {
63
+ return Number.isFinite(rect.left) && Number.isFinite(rect.top) && rect.width > 0 && rect.height > 0;
64
+ }
65
+ function geometryChanged(previous, next) {
66
+ return Math.abs(previous.left - next.left) > 0.5 || Math.abs(previous.top - next.top) > 0.5 || Math.abs(previous.width - next.width) > 0.5 || Math.abs(previous.height - next.height) > 0.5;
67
+ }
68
+ function visibleBorderRadius(value, rect) {
69
+ if (value === void 0 || value.length === 0) return "0px";
70
+ const radius = Number.parseFloat(value);
71
+ if (!Number.isFinite(radius) || !finiteRect(rect)) return value;
72
+ return `${String(Math.min(radius, rect.width / 2, rect.height / 2))}px`;
73
+ }
74
+ function compactIslandWidth(projection) {
75
+ if (projection.scene === "agent-charging") return ISLAND_SIZE.receivingWidth;
76
+ if (projection.scene === "handoff") return ISLAND_SIZE.dispatchingWidth;
77
+ if (projection.scene === "success") return ISLAND_SIZE.successWidth;
78
+ if (projection.scene === "failed") return ISLAND_SIZE.failedWidth;
79
+ if (projection.scene === "running" && projection.activity?.kind === "check") {
80
+ return ISLAND_SIZE.checkingWidth;
81
+ }
82
+ return ISLAND_SIZE.runningWidth;
83
+ }
84
+ function createFloatingSurfaceMotionStyles(document) {
85
+ const style = document.createElement("style");
86
+ style.textContent = `
87
+ .spotpatch-floating-surface {
88
+ --spotpatch-island-surface: #050608;
89
+ --spotpatch-island-primary: #f7f7fb;
90
+ --spotpatch-island-subtle: #5f646e;
91
+ --spotpatch-island-line: rgb(255 255 255 / 10%);
92
+ --spotpatch-island-violet: #8b67ff;
93
+ --spotpatch-island-cyan: #4dc8ff;
94
+ --spotpatch-island-mint: #59dcb6;
95
+ --spotpatch-island-error: #ef6f88;
96
+ --spotpatch-island-compact-height: ${String(ISLAND_SIZE.compactHeight)}px;
97
+ --spotpatch-island-compact-width: ${String(ISLAND_SIZE.runningWidth)}px;
98
+ --spotpatch-island-expanded-height: ${String(ISLAND_SIZE.expandedHeight)}px;
99
+ --spotpatch-island-expanded-width: ${String(ISLAND_SIZE.expandedWidth)}px;
100
+ --spotpatch-island-ease: cubic-bezier(.22, 1, .36, 1);
101
+ position: fixed;
102
+ box-sizing: border-box;
103
+ color-scheme: dark;
104
+ -webkit-font-smoothing: antialiased;
105
+ overflow: hidden;
106
+ border: 1px solid var(--spotpatch-island-line);
107
+ background:
108
+ linear-gradient(180deg, rgb(255 255 255 / 2.8%), transparent 24%),
109
+ rgb(5 6 8 / 98.8%);
110
+ box-shadow:
111
+ 0 24px 60px rgb(13 17 28 / 18%),
112
+ 0 3px 10px rgb(13 17 28 / 10%),
113
+ inset 0 1px rgb(255 255 255 / 6.5%),
114
+ inset 0 0 0 1px rgb(255 255 255 / 1.2%);
115
+ transform-origin: 100% 100%;
116
+ }
117
+ .spotpatch-floating-surface::before {
118
+ position: absolute;
119
+ z-index: 0;
120
+ inset: 0;
121
+ background:
122
+ radial-gradient(180px 70px at 0% 50%, rgb(139 103 255 / 7%), transparent 72%),
123
+ radial-gradient(180px 70px at 100% 50%, rgb(77 200 255 / 4.5%), transparent 72%);
124
+ content: "";
125
+ opacity: 0;
126
+ pointer-events: none;
127
+ }
128
+ .spotpatch-floating-surface[data-scene="pill"],
129
+ .spotpatch-floating-surface[data-scene="capturing"] {
130
+ width: max-content;
131
+ border-radius: 999px;
132
+ }
133
+ .spotpatch-floating-surface[data-scene="planner"] { border-radius: 18px; }
134
+ .spotpatch-floating-surface[data-scene="agent-charging"],
135
+ .spotpatch-floating-surface[data-scene="handoff"],
136
+ .spotpatch-floating-surface[data-scene="running"],
137
+ .spotpatch-floating-surface[data-scene="success"],
138
+ .spotpatch-floating-surface[data-scene="failed"] {
139
+ width: min(var(--spotpatch-island-compact-width), calc(100vw - 32px));
140
+ height: var(--spotpatch-island-compact-height);
141
+ max-width: calc(100vw - 32px);
142
+ border-radius: 31px;
143
+ }
144
+ .spotpatch-floating-surface[data-scene="agent-charging"]::before,
145
+ .spotpatch-floating-surface[data-scene="handoff"]::before,
146
+ .spotpatch-floating-surface[data-scene="running"]::before,
147
+ .spotpatch-floating-surface[data-scene="success"]::before,
148
+ .spotpatch-floating-surface[data-scene="failed"]::before { opacity: .82; }
149
+ .spotpatch-floating-surface:has(.spotpatch-execution-island[data-expanded="true"]) {
150
+ width: min(var(--spotpatch-island-expanded-width), calc(100vw - 32px));
151
+ height: min(var(--spotpatch-island-expanded-height), calc(100vh - 32px));
152
+ border-radius: 30px;
153
+ }
154
+ .spotpatch-floating-surface > .spotpatch-trigger,
155
+ .spotpatch-floating-surface > .spotpatch-dialog,
156
+ .spotpatch-floating-surface > .spotpatch-execution-island {
157
+ position: relative;
158
+ z-index: 1;
159
+ }
160
+ .spotpatch-floating-surface > .spotpatch-trigger {
161
+ display: inline-flex;
162
+ min-height: 44px;
163
+ align-items: center;
164
+ gap: 10px;
165
+ border: 0;
166
+ border-radius: 999px;
167
+ padding: 10px 17px;
168
+ color: #f8fafc;
169
+ background: transparent;
170
+ box-shadow: none;
171
+ cursor: pointer;
172
+ font-size: 14px;
173
+ font-weight: 650;
174
+ touch-action: none;
175
+ user-select: none;
176
+ }
177
+ .spotpatch-trigger::before {
178
+ width: 9px;
179
+ height: 9px;
180
+ border-radius: 999px;
181
+ background: var(--spotpatch-accent);
182
+ box-shadow: 0 0 0 4px rgb(99 102 241 / 10%);
183
+ content: "";
184
+ }
185
+ .spotpatch-trigger:hover { transform: translateY(-1px); }
186
+ .spotpatch-trigger[aria-pressed="true"] { background: rgb(139 123 255 / 14%); }
187
+ .spotpatch-trigger[aria-pressed="true"]::before {
188
+ animation: spotpatch-motion-pill-pulse 1.8s ease-in-out infinite;
189
+ }
190
+ .spotpatch-trigger[data-dragging="true"] {
191
+ cursor: grabbing;
192
+ transform: scale(.98);
193
+ }
194
+ .spotpatch-floating-surface .spotpatch-dialog { filter: none; }
195
+ .spotpatch-floating-surface .spotpatch-shell {
196
+ border: 0;
197
+ border-radius: 0;
198
+ background: transparent;
199
+ box-shadow: none;
200
+ }
201
+ .spotpatch-brand-name {
202
+ color: #fff;
203
+ font-size: 13.5px;
204
+ font-weight: 680;
205
+ letter-spacing: -.01em;
206
+ }
207
+ .spotpatch-brand-context {
208
+ color: #8e98aa;
209
+ font-size: 10.5px;
210
+ font-weight: 560;
211
+ letter-spacing: .02em;
212
+ }
213
+ .spotpatch-title {
214
+ margin: 0;
215
+ color: #fff;
216
+ font-size: 18px;
217
+ font-weight: 680;
218
+ letter-spacing: -.015em;
219
+ }
220
+ .spotpatch-subtitle {
221
+ max-width: 390px;
222
+ margin: 4px 0 0;
223
+ color: var(--spotpatch-text-secondary);
224
+ font-size: 12.5px;
225
+ line-height: 1.5;
226
+ }
227
+ .spotpatch-execution-island {
228
+ position: relative;
229
+ display: grid;
230
+ box-sizing: border-box;
231
+ width: 100%;
232
+ height: 100%;
233
+ grid-template-columns: auto minmax(0, 1fr) auto;
234
+ align-items: center;
235
+ gap: 12px;
236
+ overflow: hidden;
237
+ border: 0;
238
+ border-radius: inherit;
239
+ padding: 0 18px;
240
+ color: var(--spotpatch-island-primary);
241
+ background: transparent;
242
+ cursor: pointer;
243
+ text-align: left;
244
+ touch-action: none;
245
+ }
246
+ .spotpatch-execution-island[data-expanded="true"] {
247
+ min-height: var(--spotpatch-island-expanded-height);
248
+ grid-template-columns: auto minmax(0, 1fr) auto;
249
+ grid-template-rows: auto 1fr;
250
+ align-items: start;
251
+ row-gap: 16px;
252
+ border-radius: 30px;
253
+ padding: 18px 20px 16px;
254
+ }
255
+ .spotpatch-execution-mark {
256
+ display: grid;
257
+ width: 23px;
258
+ height: 23px;
259
+ flex: none;
260
+ place-items: center;
261
+ }
262
+ .spotpatch-execution-logo {
263
+ width: 22px;
264
+ height: 22px;
265
+ overflow: visible;
266
+ filter: drop-shadow(0 0 7px rgb(139 103 255 / 16%));
267
+ }
268
+ .spotpatch-execution-content {
269
+ display: flex;
270
+ min-width: 0;
271
+ align-items: baseline;
272
+ gap: 8px;
273
+ }
274
+ [data-expanded="true"] .spotpatch-execution-content {
275
+ display: grid;
276
+ gap: 4px;
277
+ }
278
+ .spotpatch-execution-headline-wrap,
279
+ .spotpatch-execution-action-wrap {
280
+ position: relative;
281
+ display: block;
282
+ min-width: 0;
283
+ }
284
+ .spotpatch-execution-headline,
285
+ .spotpatch-execution-headline-outgoing,
286
+ .spotpatch-execution-action,
287
+ .spotpatch-execution-action-outgoing {
288
+ display: block;
289
+ overflow: hidden;
290
+ text-overflow: ellipsis;
291
+ white-space: nowrap;
292
+ }
293
+ .spotpatch-execution-headline,
294
+ .spotpatch-execution-headline-outgoing {
295
+ color: var(--spotpatch-island-primary);
296
+ font-size: 15px;
297
+ font-weight: 690;
298
+ letter-spacing: -.018em;
299
+ line-height: 1.35;
300
+ }
301
+ .spotpatch-execution-action,
302
+ .spotpatch-execution-action-outgoing {
303
+ color: #696f79;
304
+ font-size: 11px;
305
+ font-weight: 400;
306
+ line-height: 1.35;
307
+ }
308
+ .spotpatch-execution-headline-outgoing,
309
+ .spotpatch-execution-action-outgoing {
310
+ position: absolute;
311
+ inset: 0;
312
+ opacity: 0;
313
+ pointer-events: none;
314
+ }
315
+ .spotpatch-execution-copy-changing .spotpatch-execution-headline,
316
+ .spotpatch-execution-copy-changing .spotpatch-execution-action {
317
+ animation: spotpatch-motion-copy-in ${String(MOTION.contentMilliseconds)}ms var(--spotpatch-island-ease) both;
318
+ }
319
+ .spotpatch-execution-copy-changing .spotpatch-execution-headline-outgoing,
320
+ .spotpatch-execution-copy-changing .spotpatch-execution-action-outgoing {
321
+ animation: spotpatch-motion-copy-out ${String(MOTION.contentMilliseconds)}ms ease-in both;
322
+ }
323
+ .spotpatch-execution-meta {
324
+ display: inline-flex;
325
+ align-items: center;
326
+ justify-content: flex-end;
327
+ gap: 10px;
328
+ flex: none;
329
+ color: #989da8;
330
+ font-size: 11px;
331
+ font-weight: 620;
332
+ white-space: nowrap;
333
+ }
334
+ .spotpatch-execution-meta-dot {
335
+ width: 6px;
336
+ height: 6px;
337
+ flex: none;
338
+ border-radius: 50%;
339
+ background: var(--spotpatch-island-violet);
340
+ box-shadow: 0 0 8px rgb(139 103 255 / 38%);
341
+ }
342
+ [data-execution-scene="agent-charging"] .spotpatch-execution-meta-dot,
343
+ [data-execution-scene="handoff"] .spotpatch-execution-meta-dot,
344
+ [data-execution-scene="running"] .spotpatch-execution-meta-dot {
345
+ animation: spotpatch-motion-status-breathe 1.8s ease-in-out infinite;
346
+ }
347
+ .spotpatch-execution-meta[data-tone="success"] .spotpatch-execution-meta-dot {
348
+ background: var(--spotpatch-island-mint);
349
+ box-shadow: 0 0 8px rgb(89 220 182 / 24%);
350
+ }
351
+ .spotpatch-execution-meta[data-tone="danger"] .spotpatch-execution-meta-dot {
352
+ background: var(--spotpatch-island-error);
353
+ box-shadow: 0 0 8px rgb(239 111 136 / 22%);
354
+ }
355
+ .spotpatch-execution-timer {
356
+ width: 34px;
357
+ color: #737985;
358
+ font: 400 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
359
+ text-align: right;
360
+ }
361
+ .spotpatch-execution-more {
362
+ display: grid;
363
+ width: 24px;
364
+ height: 24px;
365
+ padding: 0;
366
+ border: 0;
367
+ background: transparent;
368
+ color: #686e78;
369
+ font-size: 15px;
370
+ line-height: 1;
371
+ place-items: center;
372
+ }
373
+ [data-expanded="true"] .spotpatch-execution-meta-dot,
374
+ [data-expanded="true"] .spotpatch-execution-meta-label,
375
+ [data-expanded="true"] .spotpatch-execution-timer { display: none; }
376
+ [data-expanded="true"] .spotpatch-execution-more {
377
+ width: 28px;
378
+ height: 28px;
379
+ border-radius: 50%;
380
+ background: rgb(255 255 255 / 4.5%);
381
+ color: #828894;
382
+ }
383
+ .spotpatch-execution-recent {
384
+ position: absolute;
385
+ top: 62px;
386
+ right: 20px;
387
+ left: 20px;
388
+ display: grid;
389
+ margin-top: 0;
390
+ }
391
+ .spotpatch-execution-recent[hidden] { display: none; }
392
+ .spotpatch-execution-recent-item {
393
+ display: grid;
394
+ min-height: 28px;
395
+ grid-template-columns: 52px minmax(0, 1fr) auto;
396
+ align-items: center;
397
+ border-top: 1px solid rgb(255 255 255 / 5.5%);
398
+ }
399
+ .spotpatch-execution-recent-item:first-child { border-top: 0; }
400
+ .spotpatch-execution-recent-kind {
401
+ color: #666c76;
402
+ font: 600 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
403
+ text-transform: uppercase;
404
+ }
405
+ .spotpatch-execution-recent-detail {
406
+ overflow: hidden;
407
+ color: #b2b6bf;
408
+ font-size: 11px;
409
+ text-overflow: ellipsis;
410
+ white-space: nowrap;
411
+ }
412
+ .spotpatch-execution-recent-state {
413
+ color: #626974;
414
+ font: 500 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
415
+ }
416
+ .spotpatch-execution-recent-item[data-state="active"] .spotpatch-execution-recent-kind {
417
+ color: var(--spotpatch-island-violet);
418
+ }
419
+ .spotpatch-execution-recent-item[data-state="active"] .spotpatch-execution-recent-detail {
420
+ color: #f2f3f7;
421
+ }
422
+ .spotpatch-execution-recent-item[data-state="failure"] .spotpatch-execution-recent-kind {
423
+ color: var(--spotpatch-island-error);
424
+ }
425
+ .spotpatch-island-sweep {
426
+ position: absolute;
427
+ z-index: 2;
428
+ bottom: 0;
429
+ left: 44px;
430
+ width: 20%;
431
+ height: 1px;
432
+ border-radius: 999px;
433
+ background: linear-gradient(90deg, transparent, var(--spotpatch-island-violet), var(--spotpatch-island-cyan), transparent);
434
+ box-shadow: 0 0 7px rgb(139 103 255 / 18%);
435
+ opacity: 0;
436
+ pointer-events: none;
437
+ transform: translate3d(-160%, 0, 0);
438
+ }
439
+ @keyframes spotpatch-motion-copy-in {
440
+ from { opacity: 0; filter: blur(2px); transform: translate3d(0, 3px, 0); }
441
+ to { opacity: 1; filter: blur(0); transform: translate3d(0, 0, 0); }
442
+ }
443
+ @keyframes spotpatch-motion-copy-out {
444
+ from { opacity: 1; filter: blur(0); transform: translate3d(0, 0, 0); }
445
+ to { opacity: 0; filter: blur(2px); transform: translate3d(0, -3px, 0); }
446
+ }
447
+ @keyframes spotpatch-motion-status-breathe {
448
+ 50% { opacity: .42; transform: scale(.82); }
449
+ }
450
+ @keyframes spotpatch-motion-pill-pulse {
451
+ 50% { box-shadow: 0 0 0 6px rgb(99 102 241 / 5%); opacity: .72; }
452
+ }
453
+ .spotpatch-floating-surface[data-motion-paused="true"] .spotpatch-execution-meta-dot,
454
+ .spotpatch-floating-surface[data-motion-paused="true"] .spotpatch-trigger::before {
455
+ animation-play-state: paused;
456
+ }
457
+ @media (prefers-reduced-motion: reduce) {
458
+ .spotpatch-trigger::before,
459
+ .spotpatch-execution-meta-dot,
460
+ .spotpatch-execution-headline,
461
+ .spotpatch-execution-headline-outgoing,
462
+ .spotpatch-execution-action,
463
+ .spotpatch-execution-action-outgoing {
464
+ animation: none !important;
465
+ filter: none !important;
466
+ transform: none !important;
467
+ }
468
+ .spotpatch-island-sweep { display: none; }
469
+ }
470
+ @media (max-width: 520px) {
471
+ .spotpatch-execution-island {
472
+ grid-template-columns: auto minmax(0, 1fr) auto;
473
+ gap: 10px;
474
+ padding-right: 14px;
475
+ padding-left: 14px;
476
+ }
477
+ .spotpatch-execution-action-wrap { display: none; }
478
+ .spotpatch-execution-recent-item { grid-template-columns: 48px minmax(0, 1fr); }
479
+ .spotpatch-execution-recent-state { display: none; }
480
+ }
481
+ `;
482
+ return style;
483
+ }
484
+ function createFloatingSurfaceMotionController(document, elements, reconcile) {
485
+ let currentScene;
486
+ let currentActivityKey;
487
+ let morphTimeline;
488
+ let dispatchTimeline;
489
+ let pendingProjection;
490
+ let dispatchSource;
491
+ let dispatchTarget;
492
+ let disposed = false;
493
+ const handleVisibility = () => {
494
+ elements.surface.dataset.motionPaused = String(document.hidden);
495
+ };
496
+ document.addEventListener("visibilitychange", handleVisibility);
497
+ handleVisibility();
498
+ const morphElements = [
499
+ elements.surface,
500
+ elements.pill,
501
+ elements.planner,
502
+ elements.execution.root,
503
+ elements.execution.mark,
504
+ elements.execution.content,
505
+ elements.execution.meta,
506
+ elements.execution.recent
507
+ ];
508
+ const sharedExecutionElements = [
509
+ elements.execution.mark,
510
+ elements.execution.content,
511
+ elements.execution.meta
512
+ ];
513
+ function captureSurfaceLayout(includeSharedOffsets = false) {
514
+ const surfaceRect = elements.surface.getBoundingClientRect();
515
+ const computedRadius = document.defaultView?.getComputedStyle(elements.surface).borderRadius.trim();
516
+ const sharedOffsets = /* @__PURE__ */ new Map();
517
+ if (includeSharedOffsets && finiteRect(surfaceRect)) {
518
+ for (const element of sharedExecutionElements) {
519
+ const elementRect = element.getBoundingClientRect();
520
+ if (!finiteRect(elementRect)) continue;
521
+ sharedOffsets.set(
522
+ element,
523
+ Object.freeze({
524
+ x: elementRect.left - surfaceRect.left,
525
+ y: elementRect.top - surfaceRect.top
526
+ })
527
+ );
528
+ }
529
+ }
530
+ return Object.freeze({
531
+ geometry: Object.freeze({
532
+ borderRadius: visibleBorderRadius(computedRadius, surfaceRect),
533
+ rect: surfaceRect
534
+ }),
535
+ sharedOffsets
536
+ });
537
+ }
538
+ function syncRecentVisibility() {
539
+ elements.execution.recent.hidden = elements.execution.root.dataset.expanded !== "true";
540
+ }
541
+ function clearMorphProperties() {
542
+ gsap.set(morphElements, {
543
+ clearProps: "borderRadius,filter,height,opacity,transform,visibility,width,willChange"
544
+ });
545
+ delete elements.surface.dataset.motionMorphing;
546
+ syncRecentVisibility();
547
+ }
548
+ function clearMorph() {
549
+ morphTimeline?.kill();
550
+ morphTimeline = void 0;
551
+ gsap.killTweensOf(morphElements);
552
+ clearMorphProperties();
553
+ }
554
+ function interruptMorph(includeSharedOffsets = false) {
555
+ morphTimeline?.kill();
556
+ morphTimeline = void 0;
557
+ gsap.killTweensOf(morphElements);
558
+ const layout = captureSurfaceLayout(includeSharedOffsets);
559
+ clearMorphProperties();
560
+ return layout;
561
+ }
562
+ function clearDispatch() {
563
+ dispatchTimeline?.kill();
564
+ dispatchTimeline = void 0;
565
+ if (dispatchSource !== void 0) {
566
+ gsap.killTweensOf(dispatchSource);
567
+ gsap.set(dispatchSource, { clearProps: "filter,transform" });
568
+ dispatchSource = void 0;
569
+ }
570
+ if (dispatchTarget !== void 0) {
571
+ gsap.killTweensOf(dispatchTarget);
572
+ gsap.set(dispatchTarget, { clearProps: "boxShadow" });
573
+ dispatchTarget = void 0;
574
+ }
575
+ elements.surface.dataset.agentCharging = "false";
576
+ }
577
+ function completeDispatch() {
578
+ dispatchTimeline = void 0;
579
+ if (dispatchSource !== void 0) {
580
+ gsap.set(dispatchSource, { clearProps: "filter,transform" });
581
+ dispatchSource = void 0;
582
+ }
583
+ if (dispatchTarget !== void 0) {
584
+ gsap.set(dispatchTarget, { clearProps: "boxShadow" });
585
+ dispatchTarget = void 0;
586
+ }
587
+ elements.surface.dataset.agentCharging = "false";
588
+ }
589
+ function sweepOnce() {
590
+ if (!supportsMotion(document) || document.hidden) return;
591
+ gsap.killTweensOf(elements.execution.sweep);
592
+ gsap.fromTo(
593
+ elements.execution.sweep,
594
+ { autoAlpha: 0, xPercent: -160 },
595
+ {
596
+ autoAlpha: 0.72,
597
+ xPercent: 620,
598
+ duration: MOTION.sweepSeconds,
599
+ ease: "power2.inOut",
600
+ onComplete: () => {
601
+ gsap.set(elements.execution.sweep, {
602
+ clearProps: "opacity,transform,visibility"
603
+ });
604
+ }
605
+ }
606
+ );
607
+ }
608
+ function animateGeometry(previous, next, activeScene, reveal, detailTransition) {
609
+ const geometryHasChanged = geometryChanged(previous.geometry.rect, next.geometry.rect) || previous.geometry.borderRadius !== next.geometry.borderRadius;
610
+ const sharedLayoutHasChanged = sharedExecutionElements.some((element) => {
611
+ const previousOffset = previous.sharedOffsets.get(element);
612
+ const nextOffset = next.sharedOffsets.get(element);
613
+ return previousOffset !== void 0 && nextOffset !== void 0 && (Math.abs(previousOffset.x - nextOffset.x) > 0.5 || Math.abs(previousOffset.y - nextOffset.y) > 0.5);
614
+ });
615
+ if (!supportsMotion(document) || !finiteRect(previous.geometry.rect) || !finiteRect(next.geometry.rect) || !geometryHasChanged && !sharedLayoutHasChanged && detailTransition === void 0) {
616
+ clearMorphProperties();
617
+ return;
618
+ }
619
+ const morphDuration = detailTransition === "expand" ? MOTION.expandSeconds : MOTION.morphSeconds;
620
+ elements.surface.dataset.motionMorphing = "true";
621
+ morphTimeline = gsap.timeline({
622
+ defaults: { overwrite: "auto" },
623
+ onComplete: () => {
624
+ clearMorphProperties();
625
+ reconcile();
626
+ morphTimeline = void 0;
627
+ }
628
+ });
629
+ if (geometryHasChanged) {
630
+ morphTimeline.fromTo(
631
+ elements.surface,
632
+ {
633
+ borderRadius: previous.geometry.borderRadius,
634
+ height: previous.geometry.rect.height,
635
+ width: previous.geometry.rect.width,
636
+ x: previous.geometry.rect.left - next.geometry.rect.left,
637
+ y: previous.geometry.rect.top - next.geometry.rect.top,
638
+ willChange: "width,height,transform"
639
+ },
640
+ {
641
+ borderRadius: next.geometry.borderRadius,
642
+ height: next.geometry.rect.height,
643
+ width: next.geometry.rect.width,
644
+ x: 0,
645
+ y: 0,
646
+ duration: morphDuration,
647
+ ease: "expo.inOut"
648
+ },
649
+ 0
650
+ );
651
+ }
652
+ for (const element of sharedExecutionElements) {
653
+ const previousOffset = previous.sharedOffsets.get(element);
654
+ const nextOffset = next.sharedOffsets.get(element);
655
+ if (previousOffset === void 0 || nextOffset === void 0) continue;
656
+ const x = previousOffset.x - nextOffset.x;
657
+ const y = previousOffset.y - nextOffset.y;
658
+ if (Math.abs(x) <= 0.5 && Math.abs(y) <= 0.5) continue;
659
+ morphTimeline.fromTo(
660
+ element,
661
+ { x, y, willChange: "transform" },
662
+ {
663
+ x: 0,
664
+ y: 0,
665
+ duration: morphDuration,
666
+ ease: "expo.inOut"
667
+ },
668
+ 0
669
+ );
670
+ }
671
+ if (detailTransition === "expand") {
672
+ morphTimeline.fromTo(
673
+ elements.execution.recent,
674
+ { autoAlpha: 0, filter: "blur(4px)", y: 6 },
675
+ {
676
+ autoAlpha: 1,
677
+ filter: "blur(0px)",
678
+ y: 0,
679
+ duration: MOTION.detailRevealSeconds,
680
+ ease: "power3.out"
681
+ },
682
+ MOTION.detailRevealDelaySeconds
683
+ );
684
+ } else if (detailTransition === "collapse") {
685
+ morphTimeline.fromTo(
686
+ elements.execution.recent,
687
+ { autoAlpha: 1, filter: "blur(0px)", y: 0 },
688
+ {
689
+ autoAlpha: 0,
690
+ filter: "blur(3px)",
691
+ y: -4,
692
+ duration: MOTION.detailRetireSeconds,
693
+ ease: "power2.in"
694
+ },
695
+ 0
696
+ );
697
+ }
698
+ if (reveal) {
699
+ morphTimeline.fromTo(
700
+ activeScene,
701
+ { autoAlpha: 0, y: 4 },
702
+ {
703
+ autoAlpha: 1,
704
+ y: 0,
705
+ duration: MOTION.revealSeconds,
706
+ ease: "power2.out",
707
+ clearProps: "transform,opacity,visibility"
708
+ },
709
+ MOTION.sceneRevealDelaySeconds
710
+ );
711
+ }
712
+ }
713
+ function applyProjection(projection, renderContent) {
714
+ if (disposed) return;
715
+ const previousScene = currentScene;
716
+ const sceneChanged = previousScene !== projection.scene;
717
+ const layerChanged = previousScene === void 0 || surfaceLayer(previousScene) !== surfaceLayer(projection.scene);
718
+ const wasExpanded = elements.execution.root.dataset.expanded === "true";
719
+ const executionRemainsActive = previousScene !== void 0 && surfaceLayer(previousScene) === "execution" && surfaceLayer(projection.scene) === "execution";
720
+ const preserveInterruptedSharedLayout = executionRemainsActive && (wasExpanded || morphTimeline?.isActive() === true);
721
+ const previousGeometry = interruptMorph(preserveInterruptedSharedLayout);
722
+ const activityChanged = currentActivityKey !== projection.activity?.key;
723
+ elements.surface.dataset.scene = projection.scene;
724
+ elements.surface.dataset.tone = projection.tone;
725
+ elements.surface.style.setProperty(
726
+ "--spotpatch-island-compact-width",
727
+ `${String(compactIslandWidth(projection))}px`
728
+ );
729
+ renderContent();
730
+ const activeScene = setSceneVisibility(elements, projection);
731
+ const isExpanded = elements.execution.root.dataset.expanded === "true";
732
+ const detailTransition = executionRemainsActive && wasExpanded && !isExpanded ? "collapse" : void 0;
733
+ if (detailTransition === "collapse") {
734
+ elements.execution.recent.hidden = false;
735
+ }
736
+ reconcile();
737
+ const nextGeometry = captureSurfaceLayout(
738
+ preserveInterruptedSharedLayout || detailTransition !== void 0
739
+ );
740
+ currentScene = projection.scene;
741
+ currentActivityKey = projection.activity?.key;
742
+ animateGeometry(
743
+ previousGeometry,
744
+ nextGeometry,
745
+ activeScene,
746
+ layerChanged,
747
+ detailTransition
748
+ );
749
+ const executionActive = projection.scene === "handoff" || projection.scene === "running" || projection.scene === "success" || projection.scene === "failed";
750
+ if (executionActive && (sceneChanged || activityChanged)) sweepOnce();
751
+ }
752
+ function updateLayout(updateContent) {
753
+ if (disposed) return;
754
+ const wasExpanded = elements.execution.root.dataset.expanded === "true";
755
+ const previousGeometry = interruptMorph(true);
756
+ updateContent();
757
+ const isExpanded = elements.execution.root.dataset.expanded === "true";
758
+ const detailTransition = wasExpanded ? isExpanded ? void 0 : "collapse" : isExpanded ? "expand" : void 0;
759
+ if (detailTransition === "collapse") {
760
+ elements.execution.recent.hidden = false;
761
+ }
762
+ reconcile();
763
+ const nextGeometry = captureSurfaceLayout(true);
764
+ const activeScene = elements.execution.root.hidden ? elements.planner.hidden ? elements.pill : elements.planner : elements.execution.root;
765
+ animateGeometry(
766
+ previousGeometry,
767
+ nextGeometry,
768
+ activeScene,
769
+ false,
770
+ detailTransition
771
+ );
772
+ }
773
+ function cancel() {
774
+ pendingProjection = void 0;
775
+ clearMorph();
776
+ clearDispatch();
777
+ gsap.killTweensOf(elements.execution.sweep);
778
+ gsap.set(elements.execution.sweep, {
779
+ clearProps: "opacity,transform,visibility"
780
+ });
781
+ }
782
+ function render(projection, renderContent) {
783
+ if (disposed) return;
784
+ const dispatchActive = dispatchTimeline?.isActive() === true;
785
+ const leavesChargingScene = projection.scene !== "planner" && projection.scene !== "agent-charging";
786
+ if (dispatchActive && currentScene === "agent-charging" && leavesChargingScene) {
787
+ pendingProjection = Object.freeze({ projection, renderContent });
788
+ return;
789
+ }
790
+ if (dispatchActive && projection.scene !== "agent-charging") {
791
+ pendingProjection = void 0;
792
+ clearDispatch();
793
+ }
794
+ applyProjection(projection, renderContent);
795
+ }
796
+ function dispatch(source, target) {
797
+ if (disposed || dispatchTimeline?.isActive() === true) return;
798
+ if (!supportsMotion(document)) return;
799
+ clearDispatch();
800
+ dispatchSource = source;
801
+ dispatchTarget = target;
802
+ elements.surface.dataset.agentCharging = "true";
803
+ dispatchTimeline = gsap.timeline({
804
+ onComplete: () => {
805
+ const nextProjection = pendingProjection;
806
+ pendingProjection = void 0;
807
+ completeDispatch();
808
+ if (nextProjection !== void 0) {
809
+ applyProjection(nextProjection.projection, nextProjection.renderContent);
810
+ }
811
+ }
812
+ });
813
+ dispatchTimeline.fromTo(
814
+ source,
815
+ { filter: "brightness(1)", scaleX: 1, scaleY: 1 },
816
+ {
817
+ filter: "brightness(1.06)",
818
+ scaleX: 0.995,
819
+ scaleY: 0.97,
820
+ duration: MOTION.pressSeconds,
821
+ ease: "power2.out",
822
+ yoyo: true,
823
+ repeat: 1,
824
+ clearProps: "filter,transform"
825
+ },
826
+ 0
827
+ );
828
+ dispatchTimeline.fromTo(
829
+ target,
830
+ { boxShadow: "0 0 0 rgb(100 120 255 / 0%)" },
831
+ {
832
+ boxShadow: "0 0 18px rgb(100 120 255 / 14%)",
833
+ duration: MOTION.targetFeedbackSeconds,
834
+ yoyo: true,
835
+ repeat: 1,
836
+ clearProps: "boxShadow"
837
+ },
838
+ MOTION.targetFeedbackDelaySeconds
839
+ );
840
+ dispatchTimeline.to({}, { duration: MOTION.dispatchSeconds }, 0);
841
+ }
842
+ return Object.freeze({
843
+ cancel,
844
+ dispatch,
845
+ render,
846
+ updateLayout,
847
+ dispose() {
848
+ if (disposed) return;
849
+ disposed = true;
850
+ cancel();
851
+ document.removeEventListener("visibilitychange", handleVisibility);
852
+ }
853
+ });
854
+ }
855
+
856
+ // src/ui/execution-island.ts
857
+ var TIMER_INTERVAL_MS = 1e3;
858
+ function formatElapsed(milliseconds) {
859
+ const totalSeconds = Math.max(0, Math.floor(milliseconds / TIMER_INTERVAL_MS));
860
+ const seconds = totalSeconds % 60;
861
+ const totalMinutes = Math.floor(totalSeconds / 60);
862
+ const minutes = totalMinutes % 60;
863
+ const hours = Math.floor(totalMinutes / 60);
864
+ const twoDigits = (value) => String(value).padStart(2, "0");
865
+ return hours > 0 ? `${twoDigits(hours)}:${twoDigits(minutes)}:${twoDigits(seconds)}` : `${twoDigits(totalMinutes)}:${twoDigits(seconds)}`;
866
+ }
867
+ function validTimestamp(value) {
868
+ if (value === void 0) return void 0;
869
+ const timestamp = Date.parse(value);
870
+ return Number.isFinite(timestamp) ? timestamp : void 0;
871
+ }
872
+ function prefersReducedMotion(window) {
873
+ const matchMedia = Reflect.get(window, "matchMedia");
874
+ if (typeof matchMedia !== "function") return false;
875
+ const result = Reflect.apply(matchMedia, window, [
876
+ "(prefers-reduced-motion: reduce)"
877
+ ]);
878
+ return typeof result === "object" && result !== null && "matches" in result && result.matches === true;
879
+ }
880
+ function createExecutionIsland(document, now = Date.now) {
881
+ const window = document.defaultView;
882
+ if (window === null) {
883
+ throw new Error("SpotPatch Execution Island requires an associated window.");
884
+ }
885
+ const root = createButton(document, "", "spotpatch-execution-island");
886
+ root.hidden = true;
887
+ root.setAttribute("aria-hidden", "true");
888
+ root.setAttribute("aria-expanded", "false");
889
+ const logo = createBrandMark(
890
+ document,
891
+ BRAND_MARK_CONTENT,
892
+ "spotpatch-execution-island"
893
+ );
894
+ logo.classList.add("spotpatch-execution-logo");
895
+ const mark = createMarkedElement(document, "span");
896
+ mark.className = "spotpatch-execution-mark";
897
+ mark.append(logo);
898
+ const content = createMarkedElement(document, "span");
899
+ content.className = "spotpatch-execution-content";
900
+ content.setAttribute("role", "status");
901
+ content.setAttribute("aria-live", "polite");
902
+ content.setAttribute("aria-atomic", "true");
903
+ const headlineWrap = createMarkedElement(document, "span");
904
+ headlineWrap.className = "spotpatch-execution-headline-wrap";
905
+ const headline = createMarkedElement(document, "strong");
906
+ headline.className = "spotpatch-execution-headline";
907
+ const headlineOutgoing = createMarkedElement(document, "strong");
908
+ headlineOutgoing.className = "spotpatch-execution-headline-outgoing";
909
+ headlineOutgoing.setAttribute("aria-hidden", "true");
910
+ headlineWrap.append(headlineOutgoing, headline);
911
+ const actionWrap = createMarkedElement(document, "span");
912
+ actionWrap.className = "spotpatch-execution-action-wrap";
913
+ const action = createMarkedElement(document, "span");
914
+ action.className = "spotpatch-execution-action";
915
+ const actionOutgoing = createMarkedElement(document, "span");
916
+ actionOutgoing.className = "spotpatch-execution-action-outgoing";
917
+ actionOutgoing.setAttribute("aria-hidden", "true");
918
+ actionWrap.append(actionOutgoing, action);
919
+ const recent = createMarkedElement(document, "span");
920
+ recent.className = "spotpatch-execution-recent";
921
+ content.append(headlineWrap, actionWrap);
922
+ const meta = createMarkedElement(document, "span");
923
+ meta.className = "spotpatch-execution-meta";
924
+ const metaDot = createMarkedElement(document, "span");
925
+ metaDot.className = "spotpatch-execution-meta-dot";
926
+ metaDot.setAttribute("aria-hidden", "true");
927
+ const metaLabel = createMarkedElement(document, "span");
928
+ metaLabel.className = "spotpatch-execution-meta-label";
929
+ const timer = createMarkedElement(document, "span");
930
+ timer.className = "spotpatch-execution-timer";
931
+ timer.hidden = true;
932
+ meta.append(metaDot, metaLabel, timer);
933
+ const more = createMarkedElement(document, "span");
934
+ more.className = "spotpatch-execution-more";
935
+ more.textContent = "\u2022\u2022\u2022";
936
+ more.setAttribute("aria-hidden", "true");
937
+ meta.append(more);
938
+ const sweep = createMarkedElement(document, "span");
939
+ sweep.className = "spotpatch-island-sweep";
940
+ sweep.setAttribute("aria-hidden", "true");
941
+ root.append(mark, content, meta, recent, sweep);
942
+ const elements = Object.freeze({
943
+ action,
944
+ actionOutgoing,
945
+ content,
946
+ headline,
947
+ headlineOutgoing,
948
+ logo,
949
+ mark,
950
+ meta,
951
+ metaDot,
952
+ metaLabel,
953
+ more,
954
+ recent,
955
+ root,
956
+ sweep,
957
+ timer
958
+ });
959
+ let currentProjection;
960
+ let expandable = false;
961
+ let expanded = false;
962
+ let timerHandle;
963
+ let startedAt;
964
+ let disposed = false;
965
+ const updateExpandedState = () => {
966
+ expanded = expandable && expanded;
967
+ root.dataset.expandable = String(expandable);
968
+ root.dataset.expanded = String(expanded);
969
+ root.setAttribute("aria-expanded", String(expanded));
970
+ more.textContent = expanded ? "\xD7" : "\u2022\u2022\u2022";
971
+ recent.hidden = !expanded;
972
+ };
973
+ const animateCopyChange = (element, outgoing, value) => {
974
+ if (element.textContent === value) return;
975
+ outgoing.textContent = element.textContent;
976
+ element.textContent = value;
977
+ root.classList.remove("spotpatch-execution-copy-changing");
978
+ if (!prefersReducedMotion(window)) {
979
+ void root.offsetWidth;
980
+ root.classList.add("spotpatch-execution-copy-changing");
981
+ }
982
+ };
983
+ const syncProjectionCopy = (projection) => {
984
+ const visibleHeadline = expanded ? projection.expandedHeadline ?? projection.headline : projection.headline;
985
+ const visibleAction = expanded ? projection.expandedAction ?? projection.action : projection.action;
986
+ animateCopyChange(headline, headlineOutgoing, visibleHeadline);
987
+ animateCopyChange(action, actionOutgoing, visibleAction);
988
+ root.setAttribute(
989
+ "aria-label",
990
+ [visibleHeadline, visibleAction, projection.meta].filter(Boolean).join(". ")
991
+ );
992
+ };
993
+ const stopTimer = () => {
994
+ if (timerHandle === void 0) return;
995
+ window.clearInterval(timerHandle);
996
+ timerHandle = void 0;
997
+ };
998
+ const updateTimer = () => {
999
+ if (startedAt === void 0) return;
1000
+ timer.textContent = formatElapsed(now() - startedAt);
1001
+ };
1002
+ const startTimer = () => {
1003
+ if (timerHandle !== void 0 || startedAt === void 0 || document.hidden) {
1004
+ return;
1005
+ }
1006
+ updateTimer();
1007
+ timerHandle = window.setInterval(updateTimer, TIMER_INTERVAL_MS);
1008
+ };
1009
+ const syncTimer = (projection) => {
1010
+ stopTimer();
1011
+ startedAt = projection.scene === "running" ? validTimestamp(projection.startedAt) : void 0;
1012
+ const timed = startedAt !== void 0;
1013
+ timer.hidden = !timed;
1014
+ metaLabel.textContent = projection.meta;
1015
+ if (timed) startTimer();
1016
+ };
1017
+ const handleVisibility = () => {
1018
+ if (document.hidden) {
1019
+ stopTimer();
1020
+ } else if (currentProjection !== void 0) {
1021
+ syncTimer(currentProjection);
1022
+ }
1023
+ };
1024
+ document.addEventListener("visibilitychange", handleVisibility);
1025
+ return Object.freeze({
1026
+ canExpand: () => expandable,
1027
+ elements,
1028
+ isExpanded: () => expanded,
1029
+ root,
1030
+ setExpanded(nextExpanded) {
1031
+ expanded = nextExpanded;
1032
+ updateExpandedState();
1033
+ if (currentProjection !== void 0) syncProjectionCopy(currentProjection);
1034
+ },
1035
+ render(projection) {
1036
+ if (disposed) return;
1037
+ currentProjection = projection;
1038
+ root.dataset.executionScene = projection.scene;
1039
+ meta.dataset.tone = projection.tone;
1040
+ syncTimer(projection);
1041
+ recent.replaceChildren(
1042
+ ...projection.recentActivities.slice(-3).map((item) => {
1043
+ const row = createMarkedElement(document, "span");
1044
+ row.className = "spotpatch-execution-recent-item";
1045
+ row.dataset.state = item.state;
1046
+ const kind = createMarkedElement(document, "span");
1047
+ kind.className = "spotpatch-execution-recent-kind";
1048
+ kind.textContent = item.kind;
1049
+ const detail = createMarkedElement(document, "span");
1050
+ detail.className = "spotpatch-execution-recent-detail";
1051
+ detail.textContent = item.detail ?? item.label;
1052
+ const state = createMarkedElement(document, "span");
1053
+ state.className = "spotpatch-execution-recent-state";
1054
+ state.textContent = item.state;
1055
+ row.append(kind, detail, state);
1056
+ return row;
1057
+ })
1058
+ );
1059
+ expandable = projection.recentActivities.length > 0;
1060
+ if (projection.scene === "success" || projection.scene === "failed") {
1061
+ expanded = false;
1062
+ }
1063
+ updateExpandedState();
1064
+ syncProjectionCopy(projection);
1065
+ },
1066
+ dispose() {
1067
+ if (disposed) return;
1068
+ disposed = true;
1069
+ stopTimer();
1070
+ document.removeEventListener("visibilitychange", handleVisibility);
1071
+ }
1072
+ });
1073
+ }
1074
+
1075
+ // src/motion-entry.ts
1076
+ var FLOATING_SURFACE_MOTION_EXTENSION = Object.freeze({
1077
+ createExecutionIsland,
1078
+ createController: createFloatingSurfaceMotionController,
1079
+ createStyles: createFloatingSurfaceMotionStyles
1080
+ });
1081
+ function installFloatingSurfaceMotionExtension() {
1082
+ registerFloatingSurfaceMotionExtension(FLOATING_SURFACE_MOTION_EXTENSION);
1083
+ }
1084
+ export {
1085
+ createExecutionIsland,
1086
+ createFloatingSurfaceMotionController,
1087
+ createFloatingSurfaceMotionStyles,
1088
+ getFloatingSurfaceMotionExtension,
1089
+ installFloatingSurfaceMotionExtension,
1090
+ registerFloatingSurfaceMotionExtension
1091
+ };
1092
+ //# sourceMappingURL=motion.js.map