clip-pal 0.1.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/index.cjs ADDED
@@ -0,0 +1,1162 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var src_exports = {};
23
+ __export(src_exports, {
24
+ Bubble: () => Bubble,
25
+ HOVER_ATTR: () => HOVER_ATTR,
26
+ HOVER_EMOTE_ATTR: () => HOVER_EMOTE_ATTR,
27
+ PAL_EMOTES: () => PAL_EMOTES,
28
+ PAL_GESTURES: () => PAL_GESTURES,
29
+ PAL_POSES: () => PAL_POSES,
30
+ PAL_POSE_IN_MS: () => PAL_POSE_IN_MS,
31
+ PAL_POSE_OUT_MS: () => PAL_POSE_OUT_MS,
32
+ PAL_SHAPES: () => PAL_SHAPES,
33
+ PalCompanion: () => PalCompanion_default,
34
+ PalSvg: () => PalSvg,
35
+ ScrollPal: () => ScrollPal_default,
36
+ gestureMs: () => gestureMs,
37
+ isMorphable: () => isMorphable,
38
+ isPose: () => isPose,
39
+ palVars: () => palVars,
40
+ resolveShape: () => resolveShape,
41
+ useHoverSay: () => useHoverSay
42
+ });
43
+ module.exports = __toCommonJS(src_exports);
44
+
45
+ // src/types.ts
46
+ var PAL_GESTURES = {
47
+ lean: 4e3,
48
+ // steps up close and stays there while the line is read
49
+ look: 3200,
50
+ // glances left and right, with a beat on each side
51
+ knock: 3400,
52
+ // walks up, points a finger and taps on the glass
53
+ kick: 4200,
54
+ // a ball rolls in, he boots it away
55
+ crack: 3e3,
56
+ // thumps the screen hard enough to crack it
57
+ dance: 3e3,
58
+ // sways with the leg kicking out
59
+ wave: 2400,
60
+ // waves with his leg
61
+ hop: 2200,
62
+ // a decaying bounce on the spot
63
+ spin: 1400,
64
+ // one pirouette
65
+ nod: 1600,
66
+ // yes
67
+ shake: 1600,
68
+ // no
69
+ bow: 2200,
70
+ // a bow from the foot, held a moment
71
+ backflip: 1600,
72
+ // crouch, one full turn in the air, land
73
+ shiver: 1400,
74
+ // a quick shudder with the eyes squeezed shut
75
+ doze: 3200
76
+ // eyes close, he droops, then jolts awake
77
+ };
78
+ var PAL_POSES = [
79
+ "unbend",
80
+ "curl",
81
+ "heart",
82
+ "question",
83
+ "exclaim",
84
+ "key"
85
+ ];
86
+ var PAL_POSE_IN_MS = 900;
87
+ var PAL_POSE_OUT_MS = 700;
88
+ var PAL_EMOTES = [
89
+ ...Object.keys(PAL_GESTURES),
90
+ ...PAL_POSES
91
+ ];
92
+ function isPose(emote) {
93
+ return PAL_POSES.includes(emote);
94
+ }
95
+ var PAL_SHAPES = {
96
+ /** the everyday Gem clip — the default */
97
+ gem: {
98
+ body: "M15 62 L15 14 C15 7 19 3 25 3 C31 3 35 7 35 14 L35 50 C35 55 32 58 28 58 C24 58 21 55 21 50 L21 20",
99
+ leg: "M15 63 C15 70 20 73 27 73 C34 73 41 69 41 61 L41 54",
100
+ hip: [15, 63],
101
+ eyes: [
102
+ { cx: 22, cy: 12, r: 2.4 },
103
+ { cx: 30, cy: 12, r: 2.4 }
104
+ ]
105
+ },
106
+ /** rounder, chubbier loops */
107
+ round: {
108
+ body: "M13 60 L13 18 C13 8 18 3 25 3 C32 3 37 8 37 18 L37 48 C37 55 33 59 28 59 C23 59 20 55 20 49 L20 24",
109
+ leg: "M13 61 C13 70 19 74 27 74 C35 74 42 69 42 61 L42 55",
110
+ hip: [13, 61],
111
+ eyes: [
112
+ { cx: 21, cy: 13, r: 2.7 },
113
+ { cx: 29, cy: 13, r: 2.7 }
114
+ ]
115
+ },
116
+ /** a boxy office clip with squared corners */
117
+ square: {
118
+ body: "M14 62 L14 8 C14 5 15 4 18 4 C28 4 32 4 32 4 L36 4 C36 20 36 40 36 54 C36 58 34 59 30 59 C22 59 20 58 20 54 L20 18",
119
+ leg: "M14 63 C14 72 18 74 26 74 C34 74 42 72 42 64 L42 56",
120
+ hip: [14, 63],
121
+ eyes: [
122
+ { cx: 21, cy: 12, r: 2.4 },
123
+ { cx: 29, cy: 12, r: 2.4 }
124
+ ]
125
+ },
126
+ /** stretched tall and thin */
127
+ long: {
128
+ body: "M17 64 L17 12 C17 6 20 2 25 2 C30 2 33 6 33 12 L33 52 C33 56 31 59 28 59 C25 59 23 56 23 52 L23 18",
129
+ leg: "M17 65 C17 71 21 74 27 74 C33 74 39 70 39 63 L39 56",
130
+ hip: [17, 65],
131
+ eyes: [
132
+ { cx: 22.5, cy: 10, r: 2 },
133
+ { cx: 27.5, cy: 10, r: 2 }
134
+ ]
135
+ }
136
+ };
137
+ function resolveShape(shape) {
138
+ if (!shape) return PAL_SHAPES.gem;
139
+ if (typeof shape === "string") return PAL_SHAPES[shape] ?? PAL_SHAPES.gem;
140
+ return shape;
141
+ }
142
+ var commands = (d) => d.replace(/[^MLCQSTAHVZmlcqstahvz]/g, "");
143
+ function isMorphable(shape) {
144
+ return commands(shape.body) === commands(PAL_SHAPES.gem.body) && commands(shape.leg) === commands(PAL_SHAPES.gem.leg);
145
+ }
146
+ function gestureMs(gesture, tempo = 1) {
147
+ return Math.round(PAL_GESTURES[gesture] * tempo);
148
+ }
149
+ function palVars(colors, motion) {
150
+ const v = {};
151
+ if (colors?.wire) v["--pal-wire"] = colors.wire;
152
+ if (colors?.face) v["--pal-face"] = colors.face;
153
+ if (colors?.ink) v["--pal-ink"] = colors.ink;
154
+ if (colors?.bubbleBg) v["--pal-bubble-bg"] = colors.bubbleBg;
155
+ if (colors?.bubbleBorder) v["--pal-bubble-border"] = colors.bubbleBorder;
156
+ if (colors?.bubbleText) v["--pal-bubble-text"] = colors.bubbleText;
157
+ if (colors?.bubbleDot) v["--pal-bubble-dot"] = colors.bubbleDot;
158
+ if (motion?.tempo !== void 0) v["--pal-tempo"] = String(motion.tempo);
159
+ if (motion?.stepMs !== void 0) v["--pal-step"] = `${motion.stepMs}ms`;
160
+ if (motion?.swayMs !== void 0) v["--pal-sway"] = `${motion.swayMs}ms`;
161
+ if (motion?.swayDeg !== void 0) v["--pal-sway-deg"] = `${motion.swayDeg}deg`;
162
+ if (motion?.blinkMs !== void 0) v["--pal-blink"] = `${motion.blinkMs}ms`;
163
+ if (motion?.floatMs !== void 0) v["--pal-float"] = `${motion.floatMs}ms`;
164
+ if (motion?.floatPx !== void 0) v["--pal-float-px"] = `${motion.floatPx}px`;
165
+ return v;
166
+ }
167
+
168
+ // src/PalSvg.tsx
169
+ var import_jsx_runtime = require("react/jsx-runtime");
170
+ var wire = { stroke: "var(--pal-wire, #d9a441)" };
171
+ var face = { fill: "var(--pal-face, #f2ede0)" };
172
+ var faceLine = { stroke: "var(--pal-face, #f2ede0)" };
173
+ var ink = { fill: "var(--pal-ink, #131f1a)" };
174
+ var inkLine = { stroke: "var(--pal-ink, #131f1a)" };
175
+ var warned = false;
176
+ function PalSvg({
177
+ width = 88,
178
+ height,
179
+ walking = false,
180
+ emote = null,
181
+ emoteOut = false,
182
+ shape,
183
+ strokeWidth = 5,
184
+ eyes = "dots",
185
+ colors,
186
+ motion,
187
+ className,
188
+ style
189
+ }) {
190
+ const h = height ?? Math.round(width * 80 / 50);
191
+ const sh = resolveShape(shape);
192
+ if (!warned && !isMorphable(sh)) {
193
+ warned = true;
194
+ console.warn(
195
+ "[clip-pal] custom shape does not keep the M L C C L C C L / M C C L command sequence \u2014 the morphing poses will cut instead of tween."
196
+ );
197
+ }
198
+ const classes = [
199
+ "pal",
200
+ walking ? "pal-walking" : "",
201
+ emote ? `pal-em-${emote}${emoteOut ? "-out" : ""}` : "",
202
+ className ?? ""
203
+ ].filter(Boolean).join(" ");
204
+ const vars = palVars(colors, motion);
205
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
206
+ "svg",
207
+ {
208
+ width,
209
+ height: h,
210
+ viewBox: "0 0 50 80",
211
+ preserveAspectRatio: height ? "none" : void 0,
212
+ fill: "none",
213
+ "aria-hidden": true,
214
+ className: classes,
215
+ style: { ...vars, ...style },
216
+ children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
+ "g",
219
+ {
220
+ className: "pal-crack",
221
+ style: faceLine,
222
+ strokeWidth: "1.3",
223
+ strokeLinecap: "round",
224
+ fill: "none",
225
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M42 30 L48.5 30.0 M42 30 L48.5 22.2 M42 30 L42.0 19.0 M42 30 L34.2 22.2 M42 30 L31.0 30.0 M42 30 L34.2 37.8 M42 30 L42.0 41.0 M42 30 L48.5 37.8 M46.5 30.0 L45.2 26.8 L42.0 25.5 L38.8 26.8 L37.5 30.0 L38.8 33.2 L42.0 34.5 L45.2 33.2 Z M48.5 30.0 L48.0 24.0 L42.0 21.5 L36.0 24.0 L33.5 30.0 L36.0 36.0 L42.0 38.5 L48.0 36.0 Z" })
226
+ }
227
+ ),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
229
+ "g",
230
+ {
231
+ style: wire,
232
+ strokeWidth,
233
+ fill: "none",
234
+ strokeLinecap: "round",
235
+ strokeLinejoin: "round",
236
+ children: [
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { className: "pal-body", d: sh.body }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
239
+ "path",
240
+ {
241
+ className: "pal-leg",
242
+ d: sh.leg,
243
+ style: { transformOrigin: `${sh.hip[0]}px ${sh.hip[1]}px` }
244
+ }
245
+ )
246
+ ]
247
+ }
248
+ ),
249
+ eyes === "dots" && sh.eyes.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "pal-eye", cx: e.cx, cy: e.cy, r: e.r, style: face }, i)),
250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "pal-tap", style: faceLine, fill: "none", strokeLinecap: "round", children: [
251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "45", cy: "45", r: "3.8", strokeWidth: "1.9" }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "45", cy: "45", r: "6.2", strokeWidth: "1.3" })
253
+ ] }),
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "pal-ball", children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "42", cy: "71", r: "6.6", style: face }),
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M42 68 L44.8 70 L43.7 73.4 L40.3 73.4 L39.2 70 Z", style: ink }),
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
258
+ "path",
259
+ {
260
+ d: "M42 68 L42 64.4 M44.8 70 L48.3 68.9 M43.7 73.4 L45.9 76.3 M40.3 73.4 L38.1 76.3 M39.2 70 L35.7 68.9",
261
+ style: inkLine,
262
+ strokeWidth: "1",
263
+ fill: "none"
264
+ }
265
+ )
266
+ ] }),
267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { className: "pal-zzz", style: face, children: [
268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "pal-zzz-1", cx: "39", cy: "16", r: "1.6" }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "pal-zzz-2", cx: "43", cy: "11", r: "2.2" }),
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { className: "pal-zzz-3", cx: "46.5", cy: "5.5", r: "2.7" })
271
+ ] })
272
+ ]
273
+ }
274
+ );
275
+ }
276
+
277
+ // src/PalCompanion.tsx
278
+ var import_react2 = require("react");
279
+
280
+ // src/Bubble.tsx
281
+ var import_jsx_runtime2 = require("react/jsx-runtime");
282
+ function Bubble({
283
+ side,
284
+ hidden,
285
+ typing,
286
+ line,
287
+ box,
288
+ contentStyle,
289
+ contentRef,
290
+ tailInset = 10
291
+ }) {
292
+ const tailStyle = side === "left" ? { right: tailInset } : side === "right" ? { left: tailInset } : void 0;
293
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
294
+ "div",
295
+ {
296
+ className: `clip-pal-bubble is-${side} ${hidden ? "pal-bubble-hide" : "pal-say"}`,
297
+ children: [
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
299
+ "div",
300
+ {
301
+ className: "clip-pal-bubble-box",
302
+ style: box ? { width: box.w, height: box.h } : void 0,
303
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: contentRef, className: "clip-pal-bubble-content", style: contentStyle, children: typing ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "clip-pal-dots", children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "clip-pal-dot" }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "clip-pal-dot" }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "clip-pal-dot" })
307
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "pal-line-in", children: line }) })
308
+ }
309
+ ),
310
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "clip-pal-tail", style: tailStyle })
311
+ ]
312
+ }
313
+ );
314
+ }
315
+
316
+ // src/hover.ts
317
+ var import_react = require("react");
318
+ var HOVER_ATTR = "data-pal-say";
319
+ var HOVER_EMOTE_ATTR = "data-pal-emote";
320
+ function useHoverSay(enabled, cooldownMs, onSay) {
321
+ const cb = (0, import_react.useRef)(onSay);
322
+ cb.current = onSay;
323
+ (0, import_react.useEffect)(() => {
324
+ if (!enabled) return;
325
+ let last = 0;
326
+ let lastEl = null;
327
+ const over = (e) => {
328
+ const t = e.target;
329
+ if (!(t instanceof Element)) return;
330
+ const el = t.closest(`[${HOVER_ATTR}]`);
331
+ if (!el) {
332
+ lastEl = null;
333
+ return;
334
+ }
335
+ if (el === lastEl) return;
336
+ const now = performance.now();
337
+ if (now - last < cooldownMs) return;
338
+ const text = el.getAttribute(HOVER_ATTR);
339
+ if (!text) return;
340
+ last = now;
341
+ lastEl = el;
342
+ const raw = el.getAttribute(HOVER_EMOTE_ATTR);
343
+ const emote = raw && PAL_EMOTES.includes(raw) ? raw : null;
344
+ cb.current({ text, emote, el });
345
+ };
346
+ document.addEventListener("pointerover", over, { passive: true });
347
+ return () => document.removeEventListener("pointerover", over);
348
+ }, [enabled, cooldownMs]);
349
+ }
350
+
351
+ // src/PalCompanion.tsx
352
+ var import_jsx_runtime3 = require("react/jsx-runtime");
353
+ var PalCompanion = (0, import_react2.forwardRef)(
354
+ function PalCompanion2({
355
+ lines,
356
+ width = 90,
357
+ height,
358
+ showMs = 1e4,
359
+ gapMs = 2e4,
360
+ firstMs = 1500,
361
+ typingMs = 850,
362
+ emoteDelayMs = 500,
363
+ bubbleMaxWidth = "15rem",
364
+ random = true,
365
+ speak = true,
366
+ emotes = true,
367
+ hover = true,
368
+ hoverCooldownMs = 1200,
369
+ float = true,
370
+ sway = true,
371
+ visibleThreshold = 0.4,
372
+ shape,
373
+ strokeWidth,
374
+ eyes,
375
+ colors,
376
+ motion,
377
+ onSpeak,
378
+ className,
379
+ style
380
+ }, ref) {
381
+ const rootRef = (0, import_react2.useRef)(null);
382
+ const [line, setLine] = (0, import_react2.useState)(lines[0]?.[0] ?? "");
383
+ const [shown, setShown] = (0, import_react2.useState)(false);
384
+ const [typing, setTyping] = (0, import_react2.useState)(true);
385
+ const [emote, setEmote] = (0, import_react2.useState)(
386
+ null
387
+ );
388
+ const pose = (0, import_react2.useRef)(null);
389
+ const [turn, setTurn] = (0, import_react2.useState)(0);
390
+ const lastLine = (0, import_react2.useRef)(-1);
391
+ const onScreen = (0, import_react2.useRef)(false);
392
+ const p = (0, import_react2.useRef)({
393
+ lines,
394
+ random,
395
+ speak,
396
+ emotes,
397
+ typingMs,
398
+ emoteDelayMs,
399
+ tempo: motion?.tempo ?? 1,
400
+ onSpeak,
401
+ showMs,
402
+ gapMs
403
+ });
404
+ p.current = {
405
+ lines,
406
+ random,
407
+ speak,
408
+ emotes,
409
+ typingMs,
410
+ emoteDelayMs,
411
+ tempo: motion?.tempo ?? 1,
412
+ onSpeak,
413
+ showMs,
414
+ gapMs
415
+ };
416
+ const timers = (0, import_react2.useRef)({});
417
+ const reduced = (0, import_react2.useRef)(false);
418
+ const allowed = (name) => {
419
+ const e = p.current.emotes;
420
+ if (!name || e === false) return null;
421
+ if (e === true) return name;
422
+ return e.includes(name) ? name : null;
423
+ };
424
+ const unfold = () => {
425
+ const t = timers.current;
426
+ if (pose.current) {
427
+ setEmote({ name: pose.current, out: true });
428
+ pose.current = null;
429
+ t.emoteEnd = setTimeout(() => setEmote(null), PAL_POSE_OUT_MS);
430
+ } else {
431
+ setEmote(null);
432
+ }
433
+ };
434
+ const sayNow = (text, acts) => {
435
+ const t = timers.current;
436
+ clearTimeout(t.type);
437
+ clearTimeout(t.emote);
438
+ clearTimeout(t.emoteEnd);
439
+ if (p.current.speak) {
440
+ setLine(text);
441
+ setTyping(!reduced.current);
442
+ setTurn((n) => n + 1);
443
+ setShown(true);
444
+ if (!reduced.current) {
445
+ t.type = setTimeout(() => setTyping(false), p.current.typingMs);
446
+ }
447
+ }
448
+ p.current.onSpeak?.(text, acts);
449
+ const name = reduced.current ? null : allowed(acts);
450
+ const begin = () => {
451
+ if (!name) return;
452
+ setEmote({ name, out: false });
453
+ if (isPose(name)) {
454
+ pose.current = name;
455
+ } else {
456
+ t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(name, p.current.tempo));
457
+ }
458
+ };
459
+ if (pose.current) {
460
+ unfold();
461
+ t.emote = setTimeout(begin, PAL_POSE_OUT_MS);
462
+ } else {
463
+ setEmote(null);
464
+ t.emote = setTimeout(begin, p.current.emoteDelayMs);
465
+ }
466
+ };
467
+ const hushNow = () => {
468
+ const t = timers.current;
469
+ clearTimeout(t.type);
470
+ clearTimeout(t.emote);
471
+ clearTimeout(t.emoteEnd);
472
+ setShown(false);
473
+ unfold();
474
+ };
475
+ const schedule = (delay) => {
476
+ const t = timers.current;
477
+ clearTimeout(t.phase);
478
+ t.phase = setTimeout(cycle, delay);
479
+ };
480
+ const cycle = () => {
481
+ if (!onScreen.current) return;
482
+ const all = p.current.lines;
483
+ if (all.length === 0) return;
484
+ let i;
485
+ if (p.current.random) {
486
+ i = Math.floor(Math.random() * all.length);
487
+ if (all.length > 1 && i === lastLine.current) i = (i + 1) % all.length;
488
+ } else {
489
+ i = (lastLine.current + 1) % all.length;
490
+ }
491
+ lastLine.current = i;
492
+ const [text, acts] = all[i];
493
+ sayNow(text, acts);
494
+ const t = timers.current;
495
+ clearTimeout(t.phase);
496
+ t.phase = setTimeout(() => {
497
+ hushNow();
498
+ schedule(p.current.gapMs);
499
+ }, p.current.showMs);
500
+ };
501
+ const interrupt = (text, acts) => {
502
+ sayNow(text, acts);
503
+ const t = timers.current;
504
+ clearTimeout(t.phase);
505
+ t.phase = setTimeout(() => {
506
+ hushNow();
507
+ schedule(p.current.gapMs);
508
+ }, p.current.showMs);
509
+ };
510
+ (0, import_react2.useImperativeHandle)(ref, () => ({
511
+ say: (text, e = null) => interrupt(text, e),
512
+ emote: (name) => {
513
+ const t = timers.current;
514
+ clearTimeout(t.emote);
515
+ clearTimeout(t.emoteEnd);
516
+ const n = allowed(name);
517
+ if (!n) return;
518
+ const go = () => {
519
+ setEmote({ name: n, out: false });
520
+ if (isPose(n)) pose.current = n;
521
+ else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));
522
+ };
523
+ if (pose.current) {
524
+ unfold();
525
+ t.emote = setTimeout(go, PAL_POSE_OUT_MS);
526
+ } else {
527
+ setEmote(null);
528
+ requestAnimationFrame(go);
529
+ }
530
+ },
531
+ hush: () => {
532
+ hushNow();
533
+ schedule(p.current.gapMs);
534
+ }
535
+ }));
536
+ useHoverSay(hover, hoverCooldownMs, ({ text, emote: e }) => {
537
+ if (!onScreen.current) return;
538
+ interrupt(text, e);
539
+ });
540
+ (0, import_react2.useEffect)(() => {
541
+ const el = rootRef.current;
542
+ if (!el) return;
543
+ reduced.current = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
544
+ const observer = new IntersectionObserver(
545
+ ([entry]) => {
546
+ if (entry.isIntersecting) {
547
+ if (onScreen.current) return;
548
+ onScreen.current = true;
549
+ schedule(firstMs);
550
+ } else {
551
+ onScreen.current = false;
552
+ clearTimeout(timers.current.phase);
553
+ hushNow();
554
+ }
555
+ },
556
+ { threshold: visibleThreshold }
557
+ );
558
+ observer.observe(el);
559
+ return () => {
560
+ observer.disconnect();
561
+ onScreen.current = false;
562
+ const t = timers.current;
563
+ clearTimeout(t.phase);
564
+ clearTimeout(t.type);
565
+ clearTimeout(t.emote);
566
+ clearTimeout(t.emoteEnd);
567
+ };
568
+ }, [firstMs, visibleThreshold]);
569
+ const vars = palVars(colors, motion);
570
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
571
+ "div",
572
+ {
573
+ ref: rootRef,
574
+ className: `clip-pal-companion${className ? ` ${className}` : ""}`,
575
+ style: { ...vars, ...style },
576
+ children: [
577
+ speak && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "clip-pal-companion-bubble", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
578
+ Bubble,
579
+ {
580
+ side: "center",
581
+ hidden: !shown,
582
+ typing,
583
+ line,
584
+ contentStyle: { maxWidth: bubbleMaxWidth }
585
+ }
586
+ ) }, turn) }),
587
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: float ? "pal-float" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: sway ? "pal-idle" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
588
+ PalSvg,
589
+ {
590
+ width,
591
+ height,
592
+ emote: emote?.name ?? null,
593
+ emoteOut: emote?.out ?? false,
594
+ shape,
595
+ strokeWidth,
596
+ eyes
597
+ }
598
+ ) }) })
599
+ ]
600
+ }
601
+ );
602
+ }
603
+ );
604
+ var PalCompanion_default = PalCompanion;
605
+
606
+ // src/ScrollPal.tsx
607
+ var import_react3 = require("react");
608
+ var import_jsx_runtime4 = require("react/jsx-runtime");
609
+ var ScrollPal = (0, import_react3.forwardRef)(function ScrollPal2({
610
+ stops,
611
+ idleLines,
612
+ idle: idleOn = true,
613
+ idleAfterMs = 12e3,
614
+ idleWanderMs = 2e4,
615
+ minViewport = 1600,
616
+ contentWidth,
617
+ contentVar = "--content-w",
618
+ minWidth = 46,
619
+ maxWidth = 92,
620
+ gap = 12,
621
+ edge = 8,
622
+ side = "auto",
623
+ offsetY = 0,
624
+ yLimitPad = 150,
625
+ zIndex = 40,
626
+ speed = 480,
627
+ speedY = 540,
628
+ ease: easeRate = 6.32,
629
+ leanMax = 9,
630
+ walkThreshold = 27,
631
+ retargetMs = 330,
632
+ typingMs = 850,
633
+ emoteDelayMs = 500,
634
+ bubbleMaxWidth = 260,
635
+ bubbleMinWidth = 150,
636
+ random = true,
637
+ speak = true,
638
+ emotes = true,
639
+ hover = true,
640
+ hoverCooldownMs = 1200,
641
+ draggable = true,
642
+ shape,
643
+ strokeWidth,
644
+ eyes,
645
+ colors,
646
+ motion,
647
+ onArrive,
648
+ onLeave,
649
+ onSpeak,
650
+ onIdle,
651
+ className,
652
+ style
653
+ }, ref) {
654
+ const wrapRef = (0, import_react3.useRef)(null);
655
+ const palRef = (0, import_react3.useRef)(null);
656
+ const [line, setLine] = (0, import_react3.useState)(stops[0]?.lines[0]?.[0] ?? "");
657
+ const [leftGutter, setLeftGutter] = (0, import_react3.useState)(true);
658
+ const [bubbleOn, setBubbleOn] = (0, import_react3.useState)(true);
659
+ const [typing, setTyping] = (0, import_react3.useState)(true);
660
+ const [arrivalId, setArrivalId] = (0, import_react3.useState)(0);
661
+ const [walking, setWalking] = (0, import_react3.useState)(false);
662
+ const [emote, setEmote] = (0, import_react3.useState)(
663
+ null
664
+ );
665
+ const pose = (0, import_react3.useRef)(null);
666
+ const [palW, setPalW] = (0, import_react3.useState)(88);
667
+ const [bubbleW, setBubbleW] = (0, import_react3.useState)(240);
668
+ const [bubSize, setBubSize] = (0, import_react3.useState)(null);
669
+ const contentRef = (0, import_react3.useRef)(null);
670
+ const target = (0, import_react3.useRef)({ x: 40, y: 0 });
671
+ const cur = (0, import_react3.useRef)({ x: 40, y: 0, lean: 0, facing: 1 });
672
+ const palWRef = (0, import_react3.useRef)(88);
673
+ const nearRef = (0, import_react3.useRef)(stops[0] ?? { id: "", lines: [] });
674
+ const idle = (0, import_react3.useRef)(false);
675
+ const dragging = (0, import_react3.useRef)(false);
676
+ const dropped = (0, import_react3.useRef)(false);
677
+ const grab = (0, import_react3.useRef)({ x: 0, y: 0 });
678
+ const [grabbed, setGrabbed] = (0, import_react3.useState)(false);
679
+ const [roomy, setRoomy] = (0, import_react3.useState)(false);
680
+ const [wide, setWide] = (0, import_react3.useState)(false);
681
+ const lineIdx = (0, import_react3.useRef)({});
682
+ const isWalkingRef = (0, import_react3.useRef)(false);
683
+ const p = (0, import_react3.useRef)({
684
+ stops,
685
+ idleLines,
686
+ random,
687
+ speak,
688
+ emotes,
689
+ tempo: motion?.tempo ?? 1,
690
+ typingMs,
691
+ emoteDelayMs,
692
+ bubbleMaxWidth,
693
+ bubbleMinWidth,
694
+ onArrive,
695
+ onLeave,
696
+ onSpeak,
697
+ onIdle
698
+ });
699
+ p.current = {
700
+ stops,
701
+ idleLines,
702
+ random,
703
+ speak,
704
+ emotes,
705
+ tempo: motion?.tempo ?? 1,
706
+ typingMs,
707
+ emoteDelayMs,
708
+ bubbleMaxWidth,
709
+ bubbleMinWidth,
710
+ onArrive,
711
+ onLeave,
712
+ onSpeak,
713
+ onIdle
714
+ };
715
+ const timers = (0, import_react3.useRef)({});
716
+ const allowed = (name) => {
717
+ const e = p.current.emotes;
718
+ if (!name || e === false) return null;
719
+ if (e === true) return name;
720
+ return e.includes(name) ? name : null;
721
+ };
722
+ const sayNow = (text, acts) => {
723
+ const t = timers.current;
724
+ clearTimeout(t.type);
725
+ clearTimeout(t.emote);
726
+ clearTimeout(t.emoteEnd);
727
+ if (p.current.speak) {
728
+ setLine(text);
729
+ setBubbleOn(true);
730
+ setTyping(true);
731
+ setBubSize(null);
732
+ setArrivalId((n) => n + 1);
733
+ t.type = setTimeout(() => setTyping(false), p.current.typingMs);
734
+ }
735
+ p.current.onSpeak?.(text, acts);
736
+ const name = allowed(acts);
737
+ const begin = () => {
738
+ if (!name) return;
739
+ setEmote({ name, out: false });
740
+ if (isPose(name)) {
741
+ pose.current = name;
742
+ } else {
743
+ t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(name, p.current.tempo));
744
+ }
745
+ };
746
+ if (pose.current) {
747
+ setEmote({ name: pose.current, out: true });
748
+ pose.current = null;
749
+ t.emote = setTimeout(begin, PAL_POSE_OUT_MS);
750
+ } else {
751
+ setEmote(null);
752
+ t.emote = setTimeout(begin, p.current.emoteDelayMs);
753
+ }
754
+ };
755
+ const hushNow = () => {
756
+ const t = timers.current;
757
+ clearTimeout(t.type);
758
+ clearTimeout(t.emote);
759
+ clearTimeout(t.emoteEnd);
760
+ if (pose.current) {
761
+ setEmote({ name: pose.current, out: true });
762
+ pose.current = null;
763
+ t.emoteEnd = setTimeout(() => setEmote(null), PAL_POSE_OUT_MS);
764
+ } else {
765
+ setEmote(null);
766
+ }
767
+ setBubbleOn(false);
768
+ };
769
+ (0, import_react3.useImperativeHandle)(ref, () => ({
770
+ say: (text, e = null) => sayNow(text, e),
771
+ emote: (name) => {
772
+ const t = timers.current;
773
+ clearTimeout(t.emote);
774
+ clearTimeout(t.emoteEnd);
775
+ const n = allowed(name);
776
+ if (!n) return;
777
+ if (pose.current) {
778
+ setEmote({ name: pose.current, out: true });
779
+ pose.current = null;
780
+ t.emote = setTimeout(() => {
781
+ setEmote({ name: n, out: false });
782
+ if (isPose(n)) pose.current = n;
783
+ else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));
784
+ }, PAL_POSE_OUT_MS);
785
+ return;
786
+ }
787
+ setEmote(null);
788
+ requestAnimationFrame(() => {
789
+ setEmote({ name: n, out: false });
790
+ if (isPose(n)) pose.current = n;
791
+ else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));
792
+ });
793
+ },
794
+ hush: hushNow,
795
+ current: () => nearRef.current.id
796
+ }));
797
+ useHoverSay(hover && wide && roomy, hoverCooldownMs, ({ text, emote: e }) => {
798
+ if (isWalkingRef.current || dragging.current) return;
799
+ sayNow(text, e);
800
+ });
801
+ (0, import_react3.useLayoutEffect)(() => {
802
+ const el = contentRef.current;
803
+ if (!el) return;
804
+ setBubSize({ w: el.offsetWidth + 2, h: el.offsetHeight + 2 });
805
+ }, [typing, line, bubbleW, palW, arrivalId]);
806
+ (0, import_react3.useEffect)(() => {
807
+ const mq = window.matchMedia(`(min-width: ${minViewport}px)`);
808
+ const onMq = () => setWide(mq.matches);
809
+ onMq();
810
+ mq.addEventListener("change", onMq);
811
+ return () => mq.removeEventListener("change", onMq);
812
+ }, [minViewport]);
813
+ (0, import_react3.useEffect)(() => {
814
+ if (!wide) return;
815
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
816
+ if (p.current.stops.length === 0) return;
817
+ const contentW = () => {
818
+ if (typeof contentWidth === "number") return contentWidth;
819
+ if (typeof contentWidth === "function") return contentWidth();
820
+ const root = getComputedStyle(document.documentElement);
821
+ const raw = root.getPropertyValue(contentVar).trim();
822
+ const n = parseFloat(raw);
823
+ if (!Number.isFinite(n)) return 72 * parseFloat(root.fontSize);
824
+ return raw.endsWith("px") ? n : n * parseFloat(root.fontSize);
825
+ };
826
+ const gutter = () => Math.max(0, (window.innerWidth - contentW()) / 2);
827
+ const sizePal = () => {
828
+ const room = gutter() - gap - edge;
829
+ setRoomy(room >= minWidth);
830
+ const w = Math.max(minWidth, Math.min(maxWidth, Math.floor(room)));
831
+ if (w !== palWRef.current) {
832
+ palWRef.current = w;
833
+ setPalW(w);
834
+ }
835
+ };
836
+ const parkLeft = () => Math.max(edge, gutter() - gap - palWRef.current);
837
+ const parkRight = () => Math.min(
838
+ window.innerWidth - edge - palWRef.current,
839
+ window.innerWidth - gutter() + gap
840
+ );
841
+ const fitBubble = (x, onLeft) => {
842
+ const pw = palWRef.current;
843
+ const space = onLeft ? x + pw - edge : window.innerWidth - x - pw - edge;
844
+ const { bubbleMinWidth: lo, bubbleMaxWidth: hi } = p.current;
845
+ setBubbleW(Math.round(Math.max(lo, Math.min(hi, space))));
846
+ };
847
+ const computeTarget = () => {
848
+ sizePal();
849
+ const all = p.current.stops;
850
+ const h = window.innerHeight;
851
+ const vc = h / 2;
852
+ let near = null;
853
+ let fallback = all[0];
854
+ let bestD = Infinity;
855
+ for (const s of all) {
856
+ const el = document.getElementById(s.id);
857
+ if (!el) continue;
858
+ const r = el.getBoundingClientRect();
859
+ if (vc >= r.top && vc <= r.bottom) {
860
+ near = s;
861
+ break;
862
+ }
863
+ const d = Math.min(Math.abs(r.top - vc), Math.abs(r.bottom - vc));
864
+ if (d < bestD) {
865
+ bestD = d;
866
+ fallback = s;
867
+ }
868
+ }
869
+ if (near === null) near = fallback;
870
+ const atBottom = h + window.scrollY >= document.documentElement.scrollHeight - 2;
871
+ if (atBottom) near = all[all.length - 1];
872
+ const pw = palWRef.current;
873
+ const a = document.getElementById(near.anchor)?.getBoundingClientRect();
874
+ const place = side === "left" ? "gutter-left" : side === "right" ? "gutter-right" : near.place;
875
+ let x;
876
+ if (place === "gutter-left") x = parkLeft();
877
+ else if (place === "gutter-right") x = parkRight();
878
+ else if (a) x = Math.min(window.innerWidth - edge - pw, a.right + (near.gap ?? 26));
879
+ else x = parkLeft();
880
+ const yCenter = a ? a.top + a.height / 2 : vc;
881
+ const yLimit = h / 2 - yLimitPad;
882
+ const y = Math.max(-yLimit, Math.min(yLimit, yCenter - vc + offsetY));
883
+ target.current = { x, y };
884
+ nearRef.current = near;
885
+ const onLeft = place === "gutter-left";
886
+ setLeftGutter(onLeft);
887
+ fitBubble(x, onLeft);
888
+ };
889
+ let idleTimer;
890
+ const wanderSpot = () => {
891
+ sizePal();
892
+ const onLeftNow = cur.current.x < window.innerWidth / 2;
893
+ const x = onLeftNow ? parkRight() : parkLeft();
894
+ const yLimit = window.innerHeight / 2 - yLimitPad - 30;
895
+ target.current = {
896
+ x,
897
+ y: Math.round((Math.random() * 2 - 1) * Math.max(0, yLimit))
898
+ };
899
+ const left = !onLeftNow;
900
+ setLeftGutter(left);
901
+ fitBubble(x, left);
902
+ nearRef.current = { id: "idle", lines: p.current.idleLines ?? [] };
903
+ };
904
+ const idleTick = () => {
905
+ if (!idle.current) return;
906
+ wanderSpot();
907
+ idleTimer = setTimeout(idleTick, idleWanderMs);
908
+ };
909
+ const enterIdle = () => {
910
+ if (idle.current || !idleOn || !p.current.idleLines?.length) return;
911
+ idle.current = true;
912
+ p.current.onIdle?.();
913
+ wanderSpot();
914
+ idleTimer = setTimeout(idleTick, idleWanderMs);
915
+ };
916
+ const exitIdle = () => {
917
+ if (!idle.current) return;
918
+ idle.current = false;
919
+ computeTarget();
920
+ };
921
+ const activity = () => {
922
+ clearTimeout(idleTimer);
923
+ exitIdle();
924
+ idleTimer = setTimeout(enterIdle, idleAfterMs);
925
+ };
926
+ let raf = 0;
927
+ let wasWalking = false;
928
+ let shownStop = null;
929
+ let last = performance.now();
930
+ let lastRetarget = 0;
931
+ const loop = (now) => {
932
+ const c = cur.current;
933
+ const dt = Math.min(0.05, Math.max(1e-3, (now - last) / 1e3));
934
+ last = now;
935
+ if (dragging.current) {
936
+ target.current.x = c.x;
937
+ target.current.y = c.y;
938
+ if (wrapRef.current) {
939
+ wrapRef.current.style.transform = `translate(${c.x}px, calc(-50% + ${c.y}px))`;
940
+ }
941
+ raf = requestAnimationFrame(loop);
942
+ return;
943
+ }
944
+ if (now - lastRetarget > retargetMs) {
945
+ lastRetarget = now;
946
+ if (!idle.current && !dropped.current) computeTarget();
947
+ }
948
+ const ease = 1 - Math.exp(-easeRate * dt);
949
+ const dx = target.current.x - c.x;
950
+ let vx = dx * ease / dt;
951
+ if (vx > speed) vx = speed;
952
+ if (vx < -speed) vx = -speed;
953
+ if (Math.abs(dx) > 0.4) c.x += vx * dt;
954
+ let vy = (target.current.y - c.y) * ease / dt;
955
+ if (vy > speedY) vy = speedY;
956
+ if (vy < -speedY) vy = -speedY;
957
+ c.y += vy * dt;
958
+ const leanTarget = Math.max(-leanMax, Math.min(leanMax, vx / speed * leanMax * 1.42));
959
+ c.lean += (leanTarget - c.lean) * (1 - Math.exp(-(easeRate * 1.21) * dt));
960
+ if (Math.abs(vx) > walkThreshold * 1.33) c.facing = vx > 0 ? 1 : -1;
961
+ const isWalking = Math.abs(vx) > walkThreshold;
962
+ isWalkingRef.current = isWalking;
963
+ if (isWalking !== wasWalking) {
964
+ wasWalking = isWalking;
965
+ setWalking(isWalking);
966
+ if (isWalking) {
967
+ hushNow();
968
+ if (shownStop) p.current.onLeave?.(shownStop);
969
+ shownStop = null;
970
+ }
971
+ }
972
+ if (!isWalking && shownStop !== nearRef.current.id) {
973
+ const s = nearRef.current;
974
+ shownStop = s.id;
975
+ p.current.onArrive?.(s.id);
976
+ if (s.lines.length > 0) {
977
+ const prev = lineIdx.current[s.id];
978
+ let i;
979
+ if (p.current.random) {
980
+ i = Math.floor(Math.random() * s.lines.length);
981
+ if (s.lines.length > 1 && i === prev) i = (i + 1) % s.lines.length;
982
+ } else {
983
+ i = prev === void 0 ? 0 : (prev + 1) % s.lines.length;
984
+ }
985
+ lineIdx.current[s.id] = i;
986
+ const [text, acts] = s.lines[i];
987
+ sayNow(text, acts);
988
+ }
989
+ }
990
+ if (wrapRef.current) {
991
+ wrapRef.current.style.transform = `translate(${c.x}px, calc(-50% + ${c.y}px))`;
992
+ }
993
+ if (palRef.current) {
994
+ palRef.current.style.transform = `rotate(${c.lean}deg) scaleX(${c.facing})`;
995
+ }
996
+ raf = requestAnimationFrame(loop);
997
+ };
998
+ computeTarget();
999
+ cur.current.x = target.current.x;
1000
+ cur.current.y = target.current.y;
1001
+ raf = requestAnimationFrame(loop);
1002
+ const onScroll = () => {
1003
+ activity();
1004
+ dropped.current = false;
1005
+ computeTarget();
1006
+ };
1007
+ window.addEventListener("scroll", onScroll, { passive: true });
1008
+ window.addEventListener("resize", computeTarget);
1009
+ window.addEventListener("mousemove", activity, { passive: true });
1010
+ window.addEventListener("keydown", activity);
1011
+ window.addEventListener("pointerdown", activity, { passive: true });
1012
+ window.addEventListener("touchstart", activity, { passive: true });
1013
+ window.addEventListener("wheel", activity, { passive: true });
1014
+ idleTimer = setTimeout(enterIdle, idleAfterMs);
1015
+ return () => {
1016
+ cancelAnimationFrame(raf);
1017
+ const t = timers.current;
1018
+ clearTimeout(t.type);
1019
+ clearTimeout(idleTimer);
1020
+ clearTimeout(t.emote);
1021
+ clearTimeout(t.emoteEnd);
1022
+ window.removeEventListener("scroll", onScroll);
1023
+ window.removeEventListener("resize", computeTarget);
1024
+ window.removeEventListener("mousemove", activity);
1025
+ window.removeEventListener("keydown", activity);
1026
+ window.removeEventListener("pointerdown", activity);
1027
+ window.removeEventListener("touchstart", activity);
1028
+ window.removeEventListener("wheel", activity);
1029
+ };
1030
+ }, [
1031
+ wide,
1032
+ contentWidth,
1033
+ contentVar,
1034
+ minWidth,
1035
+ maxWidth,
1036
+ gap,
1037
+ edge,
1038
+ side,
1039
+ offsetY,
1040
+ yLimitPad,
1041
+ speed,
1042
+ speedY,
1043
+ easeRate,
1044
+ leanMax,
1045
+ walkThreshold,
1046
+ retargetMs,
1047
+ idleOn,
1048
+ idleAfterMs,
1049
+ idleWanderMs
1050
+ ]);
1051
+ const onPointerDown = (e) => {
1052
+ if (!draggable) return;
1053
+ e.preventDefault();
1054
+ e.currentTarget.setPointerCapture(e.pointerId);
1055
+ grab.current = {
1056
+ x: e.clientX - cur.current.x,
1057
+ y: e.clientY - (window.innerHeight / 2 + cur.current.y)
1058
+ };
1059
+ dragging.current = true;
1060
+ dropped.current = true;
1061
+ setGrabbed(true);
1062
+ };
1063
+ const onPointerMove = (e) => {
1064
+ if (!dragging.current) return;
1065
+ cur.current.x = e.clientX - grab.current.x;
1066
+ cur.current.y = e.clientY - grab.current.y - window.innerHeight / 2;
1067
+ };
1068
+ const endDrag = (e) => {
1069
+ if (!dragging.current) return;
1070
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
1071
+ e.currentTarget.releasePointerCapture(e.pointerId);
1072
+ }
1073
+ dragging.current = false;
1074
+ setGrabbed(false);
1075
+ };
1076
+ const bubbleSide = leftGutter ? "left" : "right";
1077
+ const vars = palVars(colors, motion);
1078
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1079
+ "div",
1080
+ {
1081
+ ref: wrapRef,
1082
+ className: `clip-pal-scroll${className ? ` ${className}` : ""}`,
1083
+ "data-on": wide && roomy ? "" : void 0,
1084
+ style: { ...vars, transform: "translate(40px, -50%)", zIndex, ...style },
1085
+ "aria-hidden": true,
1086
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "clip-pal-anchor", children: [
1087
+ speak && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1088
+ "div",
1089
+ {
1090
+ className: "clip-pal-bubble-wrap",
1091
+ style: leftGutter ? { right: 0 } : { left: 0 },
1092
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1093
+ Bubble,
1094
+ {
1095
+ side: bubbleSide,
1096
+ hidden: !bubbleOn,
1097
+ typing,
1098
+ line,
1099
+ box: bubSize,
1100
+ contentRef,
1101
+ contentStyle: {
1102
+ maxWidth: bubbleW,
1103
+ // never narrower than the pal — keeps the tail well inside
1104
+ // the bubble even with just the typing dots
1105
+ minWidth: palW + 8
1106
+ },
1107
+ tailInset: Math.max(10, palW / 2 - 7)
1108
+ }
1109
+ ) }, arrivalId)
1110
+ }
1111
+ ),
1112
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1113
+ "div",
1114
+ {
1115
+ ref: palRef,
1116
+ onPointerDown,
1117
+ onPointerMove,
1118
+ onPointerUp: endDrag,
1119
+ onPointerCancel: endDrag,
1120
+ className: `clip-pal-grab${grabbed ? " is-grabbed" : ""}`,
1121
+ style: draggable ? void 0 : { cursor: "default" },
1122
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: walking ? "pal-walk" : "pal-idle", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1123
+ PalSvg,
1124
+ {
1125
+ width: palW,
1126
+ walking,
1127
+ emote: emote?.name ?? null,
1128
+ emoteOut: emote?.out ?? false,
1129
+ shape,
1130
+ strokeWidth,
1131
+ eyes
1132
+ }
1133
+ ) })
1134
+ }
1135
+ )
1136
+ ] })
1137
+ }
1138
+ );
1139
+ });
1140
+ var ScrollPal_default = ScrollPal;
1141
+ // Annotate the CommonJS export names for ESM import in node:
1142
+ 0 && (module.exports = {
1143
+ Bubble,
1144
+ HOVER_ATTR,
1145
+ HOVER_EMOTE_ATTR,
1146
+ PAL_EMOTES,
1147
+ PAL_GESTURES,
1148
+ PAL_POSES,
1149
+ PAL_POSE_IN_MS,
1150
+ PAL_POSE_OUT_MS,
1151
+ PAL_SHAPES,
1152
+ PalCompanion,
1153
+ PalSvg,
1154
+ ScrollPal,
1155
+ gestureMs,
1156
+ isMorphable,
1157
+ isPose,
1158
+ palVars,
1159
+ resolveShape,
1160
+ useHoverSay
1161
+ });
1162
+ //# sourceMappingURL=index.cjs.map