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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/PalSvg.tsx","../src/PalCompanion.tsx","../src/Bubble.tsx","../src/hover.ts","../src/ScrollPal.tsx"],"sourcesContent":["// How long each gesture runs, in milliseconds — must match the animation\n// shorthands in styles.css. The POSES below are absent on purpose: they have\n// no duration because they hold until something tells them to unfold.\nexport const PAL_GESTURES = {\n lean: 4000, // steps up close and stays there while the line is read\n look: 3200, // glances left and right, with a beat on each side\n knock: 3400, // walks up, points a finger and taps on the glass\n kick: 4200, // a ball rolls in, he boots it away\n crack: 3000, // thumps the screen hard enough to crack it\n dance: 3000, // sways with the leg kicking out\n wave: 2400, // waves with his leg\n hop: 2200, // a decaying bounce on the spot\n spin: 1400, // one pirouette\n nod: 1600, // yes\n shake: 1600, // no\n bow: 2200, // a bow from the foot, held a moment\n backflip: 1600, // crouch, one full turn in the air, land\n shiver: 1400, // a quick shudder with the eyes squeezed shut\n doze: 3200, // eyes close, he droops, then jolts awake\n} as const;\n\n// Shapes he bends into and stays in. They fold in, hold for as long as he is\n// standing there, and only unfold when he sets off again — which is why they\n// are timed by the caller rather than by a fixed duration.\nexport const PAL_POSES = [\n \"unbend\",\n \"curl\",\n \"heart\",\n \"question\",\n \"exclaim\",\n \"key\",\n] as const;\n\nexport const PAL_POSE_IN_MS = 900; // matches the -in animations\nexport const PAL_POSE_OUT_MS = 700; // matches the -out animations\n\nexport type PalGesture = keyof typeof PAL_GESTURES;\nexport type PalPose = (typeof PAL_POSES)[number];\nexport type PalEmote = PalGesture | PalPose;\n\nexport const PAL_EMOTES: readonly PalEmote[] = [\n ...(Object.keys(PAL_GESTURES) as PalGesture[]),\n ...PAL_POSES,\n];\n\nexport function isPose(emote: PalEmote): emote is PalPose {\n return (PAL_POSES as readonly string[]).includes(emote);\n}\n\n// A line and the trick that goes with it. Pairing them rather than keeping a\n// separate lookup is deliberate: an emote is part of how a line reads, so the\n// two have to be written together and can never drift apart.\nexport type PalLine = readonly [text: string, emote: PalEmote];\n\n// A place the walking pal stops at. `id` is the element whose vertical span\n// owns him (he belongs to whichever stop's element the middle of the screen\n// is inside); `anchor` is the element he stands next to and rides along with.\nexport type PalStop = {\n id: string;\n anchor: string;\n /** beside the anchor, or parked in one of the gutters either side of the content column */\n place: \"beside\" | \"gutter-left\" | \"gutter-right\";\n /** horizontal clearance from the anchor when `place` is \"beside\" (default 26) */\n gap?: number;\n lines: readonly PalLine[];\n};\n\n// ---------- shape ----------\n\nexport type PalEye = { cx: number; cy: number; r: number };\n\n// The wire itself. `body` must be M L C C L C C L and `leg` M C C L — the\n// same command sequence as the default — or the morphing poses cut instead\n// of tweening. `hip` is where the leg leaves the body: the leg pivots there.\nexport type PalShape = {\n body: string;\n leg: string;\n hip: readonly [x: number, y: number];\n eyes: readonly [PalEye, PalEye];\n};\n\nexport const PAL_SHAPES = {\n /** the everyday Gem clip — the default */\n gem: {\n 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\",\n leg: \"M15 63 C15 70 20 73 27 73 C34 73 41 69 41 61 L41 54\",\n hip: [15, 63],\n eyes: [\n { cx: 22, cy: 12, r: 2.4 },\n { cx: 30, cy: 12, r: 2.4 },\n ],\n },\n /** rounder, chubbier loops */\n round: {\n 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\",\n leg: \"M13 61 C13 70 19 74 27 74 C35 74 42 69 42 61 L42 55\",\n hip: [13, 61],\n eyes: [\n { cx: 21, cy: 13, r: 2.7 },\n { cx: 29, cy: 13, r: 2.7 },\n ],\n },\n /** a boxy office clip with squared corners */\n square: {\n 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\",\n leg: \"M14 63 C14 72 18 74 26 74 C34 74 42 72 42 64 L42 56\",\n hip: [14, 63],\n eyes: [\n { cx: 21, cy: 12, r: 2.4 },\n { cx: 29, cy: 12, r: 2.4 },\n ],\n },\n /** stretched tall and thin */\n long: {\n 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\",\n leg: \"M17 65 C17 71 21 74 27 74 C33 74 39 70 39 63 L39 56\",\n hip: [17, 65],\n eyes: [\n { cx: 22.5, cy: 10, r: 2 },\n { cx: 27.5, cy: 10, r: 2 },\n ],\n },\n} as const satisfies Record<string, PalShape>;\n\nexport type PalShapeName = keyof typeof PAL_SHAPES;\n\nexport function resolveShape(shape?: PalShapeName | PalShape): PalShape {\n if (!shape) return PAL_SHAPES.gem;\n if (typeof shape === \"string\") return PAL_SHAPES[shape] ?? PAL_SHAPES.gem;\n return shape;\n}\n\nconst commands = (d: string) => d.replace(/[^MLCQSTAHVZmlcqstahvz]/g, \"\");\n\n/** true when a custom shape keeps the command sequence the morphs need */\nexport function isMorphable(shape: PalShape): boolean {\n return (\n commands(shape.body) === commands(PAL_SHAPES.gem.body) &&\n commands(shape.leg) === commands(PAL_SHAPES.gem.leg)\n );\n}\n\n// ---------- colours & motion ----------\n\nexport type PalColors = {\n /** the paperclip wire */\n wire?: string;\n /** eyes, ball, crack, rings, sleep bubbles */\n face?: string;\n /** the ball's dark panel */\n ink?: string;\n bubbleBg?: string;\n bubbleBorder?: string;\n bubbleText?: string;\n bubbleDot?: string;\n};\n\nexport type PalMotion = {\n /** multiplies every emote's duration; 1 = as authored, 2 = half speed */\n tempo?: number;\n /** one stride while walking, ms (default 360) */\n stepMs?: number;\n /** idle sway period, ms (default 5000) */\n swayMs?: number;\n /** idle sway amplitude, degrees (default 2.5) */\n swayDeg?: number;\n /** blink period, ms (default 5000) */\n blinkMs?: number;\n /** the companion's float period, ms (default 6000) */\n floatMs?: number;\n /** the companion's float height, px (default 8) */\n floatPx?: number;\n};\n\n/** a gesture's duration in ms at the given tempo */\nexport function gestureMs(gesture: PalGesture, tempo = 1): number {\n return Math.round(PAL_GESTURES[gesture] * tempo);\n}\n\n/** inline style carrying the --pal-* variables for the given colours/motion */\nexport function palVars(\n colors?: PalColors,\n motion?: PalMotion,\n): Record<string, string> {\n const v: Record<string, string> = {};\n if (colors?.wire) v[\"--pal-wire\"] = colors.wire;\n if (colors?.face) v[\"--pal-face\"] = colors.face;\n if (colors?.ink) v[\"--pal-ink\"] = colors.ink;\n if (colors?.bubbleBg) v[\"--pal-bubble-bg\"] = colors.bubbleBg;\n if (colors?.bubbleBorder) v[\"--pal-bubble-border\"] = colors.bubbleBorder;\n if (colors?.bubbleText) v[\"--pal-bubble-text\"] = colors.bubbleText;\n if (colors?.bubbleDot) v[\"--pal-bubble-dot\"] = colors.bubbleDot;\n if (motion?.tempo !== undefined) v[\"--pal-tempo\"] = String(motion.tempo);\n if (motion?.stepMs !== undefined) v[\"--pal-step\"] = `${motion.stepMs}ms`;\n if (motion?.swayMs !== undefined) v[\"--pal-sway\"] = `${motion.swayMs}ms`;\n if (motion?.swayDeg !== undefined) v[\"--pal-sway-deg\"] = `${motion.swayDeg}deg`;\n if (motion?.blinkMs !== undefined) v[\"--pal-blink\"] = `${motion.blinkMs}ms`;\n if (motion?.floatMs !== undefined) v[\"--pal-float\"] = `${motion.floatMs}ms`;\n if (motion?.floatPx !== undefined) v[\"--pal-float-px\"] = `${motion.floatPx}px`;\n return v;\n}\n","import type { CSSProperties } from \"react\";\nimport {\n isMorphable,\n palVars,\n resolveShape,\n type PalColors,\n type PalEmote,\n type PalMotion,\n type PalShape,\n type PalShapeName,\n} from \"./types\";\n\n// The clip pal as one reusable SVG. The body and the leg are separate paths\n// so the leg can swing on its own — its pivot sits at the hip, where the leg\n// wire leaves the body (15,63 in viewBox units). `walking` turns the step on.\n//\n// `emote` plays one of his party tricks. The morphing ones rewrite the body\n// and leg paths from styles.css; every target shape there keeps the exact\n// command sequence of the paths below (M L C C L C C L for the body, M C C L\n// for the leg), because same commands in the same order is what lets the\n// browser tween one shape into the other instead of cutting between them.\n//\n// The ball, the crack, the tap rings and the sleep bubbles are props:\n// invisible until an emote calls for them, and kept inside the viewBox so\n// nothing ever paints over the page.\n//\n// Colours come from CSS custom properties so a host page can restyle him\n// without touching the markup; the defaults are the ClipSpace desk set.\nconst wire: CSSProperties = { stroke: \"var(--pal-wire, #d9a441)\" };\nconst face: CSSProperties = { fill: \"var(--pal-face, #f2ede0)\" };\nconst faceLine: CSSProperties = { stroke: \"var(--pal-face, #f2ede0)\" };\nconst ink: CSSProperties = { fill: \"var(--pal-ink, #131f1a)\" };\nconst inkLine: CSSProperties = { stroke: \"var(--pal-ink, #131f1a)\" };\n\nexport type PalSvgProps = {\n /** rendered width in px; the height follows the 50:80 viewBox unless given */\n width?: number;\n /** rendered height in px — set it to squash or stretch him */\n height?: number;\n /** swing the leg as if walking */\n walking?: boolean;\n /** the gesture or pose to play */\n emote?: PalEmote | null;\n /** play the pose's unfolding half rather than holding it */\n emoteOut?: boolean;\n /** a preset name or your own wire (see PalShape) */\n shape?: PalShapeName | PalShape;\n /** wire thickness in viewBox units (default 5) */\n strokeWidth?: number;\n /** \"dots\" (default) or \"none\" for a faceless clip */\n eyes?: \"dots\" | \"none\";\n /** colours as props — the same --pal-* variables, set inline */\n colors?: PalColors;\n /** tempo, stride, sway, blink — the same --pal-* variables, set inline */\n motion?: PalMotion;\n className?: string;\n style?: CSSProperties;\n};\n\nlet warned = false;\n\nexport default function PalSvg({\n width = 88,\n height,\n walking = false,\n emote = null,\n emoteOut = false,\n shape,\n strokeWidth = 5,\n eyes = \"dots\",\n colors,\n motion,\n className,\n style,\n}: PalSvgProps) {\n const h = height ?? Math.round((width * 80) / 50);\n const sh = resolveShape(shape);\n if (!warned && !isMorphable(sh)) {\n warned = true;\n console.warn(\n \"[clip-pal] custom shape does not keep the M L C C L C C L / M C C L command sequence — the morphing poses will cut instead of tween.\",\n );\n }\n const classes = [\n \"pal\",\n walking ? \"pal-walking\" : \"\",\n emote ? `pal-em-${emote}${emoteOut ? \"-out\" : \"\"}` : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n const vars = palVars(colors, motion);\n return (\n <svg\n width={width}\n height={h}\n viewBox=\"0 0 50 80\"\n preserveAspectRatio={height ? \"none\" : undefined}\n fill=\"none\"\n aria-hidden\n className={classes}\n style={{ ...(vars as CSSProperties), ...style }}\n >\n {/* The crack he leaves in the screen. It radiates from (42,30) — the\n exact point his leg reaches on the strike — and is drawn behind him,\n so he stands in front of the damage. */}\n <g\n className=\"pal-crack\"\n style={faceLine}\n strokeWidth=\"1.3\"\n strokeLinecap=\"round\"\n fill=\"none\"\n >\n <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\" />\n </g>\n\n <g\n style={wire}\n strokeWidth={strokeWidth}\n fill=\"none\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path className=\"pal-body\" d={sh.body} />\n <path\n className=\"pal-leg\"\n d={sh.leg}\n style={{ transformOrigin: `${sh.hip[0]}px ${sh.hip[1]}px` }}\n />\n </g>\n {eyes === \"dots\" &&\n sh.eyes.map((e, i) => (\n <circle key={i} className=\"pal-eye\" cx={e.cx} cy={e.cy} r={e.r} style={face} />\n ))}\n\n {/* Impact rings where his fingertip meets the glass. Without these a tap\n toward the viewer has no direction to move in and just reads as\n pointing sideways — the rings are what say there is a surface there. */}\n <g className=\"pal-tap\" style={faceLine} fill=\"none\" strokeLinecap=\"round\">\n <circle cx=\"45\" cy=\"45\" r=\"3.8\" strokeWidth=\"1.9\" />\n <circle cx=\"45\" cy=\"45\" r=\"6.2\" strokeWidth=\"1.3\" />\n </g>\n\n {/* The football. Parked clear of the leg loop — it used to sit underneath\n the leg, which is why the kick never read. Solid with a dark panel; a\n bare ring looked like a hoop. */}\n <g className=\"pal-ball\">\n <circle cx=\"42\" cy=\"71\" r=\"6.6\" style={face} />\n <path d=\"M42 68 L44.8 70 L43.7 73.4 L40.3 73.4 L39.2 70 Z\" style={ink} />\n <path\n 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\"\n style={inkLine}\n strokeWidth=\"1\"\n fill=\"none\"\n />\n </g>\n\n {/* Sleep bubbles: three dots that drift up from his head while he dozes,\n each a little bigger than the one below. */}\n <g className=\"pal-zzz\" style={face}>\n <circle className=\"pal-zzz-1\" cx=\"39\" cy=\"16\" r=\"1.6\" />\n <circle className=\"pal-zzz-2\" cx=\"43\" cy=\"11\" r=\"2.2\" />\n <circle className=\"pal-zzz-3\" cx=\"46.5\" cy=\"5.5\" r=\"2.7\" />\n </g>\n </svg>\n );\n}\n","import {\n type CSSProperties,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\nimport { Bubble } from \"./Bubble\";\nimport { useHoverSay } from \"./hover\";\nimport PalSvg from \"./PalSvg\";\nimport {\n gestureMs,\n isPose,\n palVars,\n PAL_POSE_OUT_MS,\n type PalColors,\n type PalEmote,\n type PalLine,\n type PalMotion,\n type PalShape,\n type PalShapeName,\n} from \"./types\";\n\n// The standing pal, for phones and narrower desktops where the walking guide\n// never runs — or for anywhere you just want him around. Every so often a\n// bubble appears over his head, he says one line and acts it out, and it\n// goes away again. He only talks while he is actually on screen.\n\nexport type PalCompanionHandle = {\n /** interrupt and say this, optionally acting it out */\n say: (text: string, emote?: PalEmote | null) => void;\n /** play a gesture or fold into a pose without saying anything */\n emote: (name: PalEmote) => void;\n /** put the bubble away and unfold any held pose */\n hush: () => void;\n};\n\nexport type PalCompanionProps = {\n /** what he says — a line and the emote that goes with it */\n lines: readonly PalLine[];\n /** rendered width in px (default 90) */\n width?: number;\n /** rendered height — set it to squash or stretch him */\n height?: number;\n /** how long a bubble stays up, ms (default 10 000) */\n showMs?: number;\n /** silence between bubbles, ms (default 20 000) */\n gapMs?: number;\n /** beat after he scrolls into view before the first line, ms (default 1 500) */\n firstMs?: number;\n /** typing dots before the line lands, ms (default 850) */\n typingMs?: number;\n /** beat between the bubble and the trick, ms (default 500) */\n emoteDelayMs?: number;\n /** widest the bubble grows (default \"15rem\") */\n bubbleMaxWidth?: number | string;\n /** pick lines at random (default) or in order */\n random?: boolean;\n /** show the bubble at all (default true) */\n speak?: boolean;\n /** act lines out (default true); or a whitelist of the emotes he may use */\n emotes?: boolean | readonly PalEmote[];\n /** react to `data-pal-say` elements under the pointer (default true) */\n hover?: boolean;\n /** minimum gap between two hover lines, ms (default 1 200) */\n hoverCooldownMs?: number;\n /** the gentle up-and-down float (default true) */\n float?: boolean;\n /** the idle sway (default true) */\n sway?: boolean;\n /** only talk while at least this much of him is on screen (default 0.4) */\n visibleThreshold?: number;\n\n /** a preset name or your own wire */\n shape?: PalShapeName | PalShape;\n /** wire thickness in viewBox units (default 5) */\n strokeWidth?: number;\n /** \"dots\" (default) or \"none\" */\n eyes?: \"dots\" | \"none\";\n /** colours as props — the same --pal-* variables, set inline */\n colors?: PalColors;\n /** tempo, stride, sway, blink, float — the same --pal-* variables, set inline */\n motion?: PalMotion;\n\n onSpeak?: (text: string, emote: PalEmote | null) => void;\n\n className?: string;\n style?: CSSProperties;\n};\n\nconst PalCompanion = forwardRef<PalCompanionHandle, PalCompanionProps>(\n function PalCompanion(\n {\n lines,\n width = 90,\n height,\n showMs = 10_000,\n gapMs = 20_000,\n firstMs = 1_500,\n typingMs = 850,\n emoteDelayMs = 500,\n bubbleMaxWidth = \"15rem\",\n random = true,\n speak = true,\n emotes = true,\n hover = true,\n hoverCooldownMs = 1200,\n float = true,\n sway = true,\n visibleThreshold = 0.4,\n shape,\n strokeWidth,\n eyes,\n colors,\n motion,\n onSpeak,\n className,\n style,\n },\n ref,\n ) {\n const rootRef = useRef<HTMLDivElement>(null);\n const [line, setLine] = useState<string>(lines[0]?.[0] ?? \"\");\n const [shown, setShown] = useState(false);\n const [typing, setTyping] = useState(true);\n const [emote, setEmote] = useState<{ name: PalEmote; out: boolean } | null>(\n null,\n );\n const pose = useRef<PalEmote | null>(null);\n // remounts the bubble so its entrance animation replays each time\n const [turn, setTurn] = useState(0);\n const lastLine = useRef(-1);\n const onScreen = useRef(false);\n\n const p = useRef({\n lines,\n random,\n speak,\n emotes,\n typingMs,\n emoteDelayMs,\n tempo: motion?.tempo ?? 1,\n onSpeak,\n showMs,\n gapMs,\n });\n p.current = {\n lines,\n random,\n speak,\n emotes,\n typingMs,\n emoteDelayMs,\n tempo: motion?.tempo ?? 1,\n onSpeak,\n showMs,\n gapMs,\n };\n\n // Two handles rather than a list: the typing timer and the phase timer\n // are the only two that can ever be pending at once, and reusing the\n // slots keeps the cycle from accumulating handles as it runs.\n const timers = useRef<{\n type?: ReturnType<typeof setTimeout>;\n phase?: ReturnType<typeof setTimeout>;\n emote?: ReturnType<typeof setTimeout>;\n emoteEnd?: ReturnType<typeof setTimeout>;\n }>({});\n const reduced = useRef(false);\n\n const allowed = (name: PalEmote | null): PalEmote | null => {\n const e = p.current.emotes;\n if (!name || e === false) return null;\n if (e === true) return name;\n return e.includes(name) ? name : null;\n };\n\n const unfold = () => {\n const t = timers.current;\n if (pose.current) {\n setEmote({ name: pose.current, out: true });\n pose.current = null;\n t.emoteEnd = setTimeout(() => setEmote(null), PAL_POSE_OUT_MS);\n } else {\n setEmote(null);\n }\n };\n\n // Say a line now: bubble types, the text lands, then the trick. Under\n // reduced motion the line just appears and he stays still.\n const sayNow = (text: string, acts: PalEmote | null) => {\n const t = timers.current;\n clearTimeout(t.type);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n if (p.current.speak) {\n setLine(text);\n setTyping(!reduced.current);\n setTurn((n) => n + 1);\n setShown(true);\n if (!reduced.current) {\n t.type = setTimeout(() => setTyping(false), p.current.typingMs);\n }\n }\n p.current.onSpeak?.(text, acts);\n const name = reduced.current ? null : allowed(acts);\n const begin = () => {\n if (!name) return;\n setEmote({ name, out: false });\n if (isPose(name)) {\n // a pose stays until the bubble goes away again\n pose.current = name;\n } else {\n t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(name, p.current.tempo));\n }\n };\n if (pose.current) {\n unfold();\n t.emote = setTimeout(begin, PAL_POSE_OUT_MS);\n } else {\n setEmote(null);\n t.emote = setTimeout(begin, p.current.emoteDelayMs);\n }\n };\n\n const hushNow = () => {\n const t = timers.current;\n clearTimeout(t.type);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n setShown(false);\n unfold();\n };\n\n // the regular cycle: a line, a pause, the next line — restarted after an\n // interruption (hover, say()) so the gap is measured from that instead\n const schedule = (delay: number) => {\n const t = timers.current;\n clearTimeout(t.phase);\n t.phase = setTimeout(cycle, delay);\n };\n const cycle = () => {\n if (!onScreen.current) return;\n const all = p.current.lines;\n if (all.length === 0) return;\n let i: number;\n if (p.current.random) {\n i = Math.floor(Math.random() * all.length);\n if (all.length > 1 && i === lastLine.current) i = (i + 1) % all.length;\n } else {\n i = (lastLine.current + 1) % all.length;\n }\n lastLine.current = i;\n const [text, acts] = all[i];\n sayNow(text, acts);\n const t = timers.current;\n clearTimeout(t.phase);\n t.phase = setTimeout(() => {\n // he comes out of the shape as the bubble goes, never by cutting to\n // a paperclip\n hushNow();\n schedule(p.current.gapMs);\n }, p.current.showMs);\n };\n const interrupt = (text: string, acts: PalEmote | null) => {\n sayNow(text, acts);\n const t = timers.current;\n clearTimeout(t.phase);\n t.phase = setTimeout(() => {\n hushNow();\n schedule(p.current.gapMs);\n }, p.current.showMs);\n };\n\n useImperativeHandle(ref, () => ({\n say: (text, e = null) => interrupt(text, e),\n emote: (name) => {\n const t = timers.current;\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n const n = allowed(name);\n if (!n) return;\n const go = () => {\n setEmote({ name: n, out: false });\n if (isPose(n)) pose.current = n;\n else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));\n };\n if (pose.current) {\n unfold();\n t.emote = setTimeout(go, PAL_POSE_OUT_MS);\n } else {\n setEmote(null);\n requestAnimationFrame(go);\n }\n },\n hush: () => {\n hushNow();\n schedule(p.current.gapMs);\n },\n }));\n\n useHoverSay(hover, hoverCooldownMs, ({ text, emote: e }) => {\n if (!onScreen.current) return;\n interrupt(text, e);\n });\n\n useEffect(() => {\n const el = rootRef.current;\n if (!el) return;\n // The dots are the only motion here; without them the line just appears.\n reduced.current = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n // He only talks while he's actually on screen — otherwise the cycle\n // burns through lines in a part of the page nobody is looking at.\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n if (onScreen.current) return;\n onScreen.current = true;\n schedule(firstMs);\n } else {\n onScreen.current = false;\n clearTimeout(timers.current.phase);\n hushNow();\n }\n },\n { threshold: visibleThreshold },\n );\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n onScreen.current = false;\n const t = timers.current;\n clearTimeout(t.phase);\n clearTimeout(t.type);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [firstMs, visibleThreshold]);\n\n const vars = palVars(colors, motion) as CSSProperties;\n\n return (\n <div\n ref={rootRef}\n className={`clip-pal-companion${className ? ` ${className}` : \"\"}`}\n style={{ ...vars, ...style }}\n >\n {/* Absolutely positioned so the layout never shifts when he starts or\n stops talking. */}\n {speak && (\n <div className=\"clip-pal-companion-bubble\">\n <div key={turn}>\n <Bubble\n side=\"center\"\n hidden={!shown}\n typing={typing}\n line={line}\n contentStyle={{ maxWidth: bubbleMaxWidth }}\n />\n </div>\n </div>\n )}\n\n <div className={float ? \"pal-float\" : undefined}>\n <div className={sway ? \"pal-idle\" : undefined}>\n <PalSvg\n width={width}\n height={height}\n emote={emote?.name ?? null}\n emoteOut={emote?.out ?? false}\n shape={shape}\n strokeWidth={strokeWidth}\n eyes={eyes}\n />\n </div>\n </div>\n </div>\n );\n },\n);\n\nexport default PalCompanion;\n","import type { CSSProperties, ReactNode } from \"react\";\n\n// The speech bubble both pals talk through. It pops in (`pal-say`), shows the\n// typing dots first, then the line; `hidden` fades it away again. The box can\n// be given an explicit size so it morphs smoothly from the dots into the\n// full line — the content inside is always rendered at its natural size and\n// is what the caller measures.\nexport type BubbleProps = {\n /** which way the bubble grows — the side its tail is on */\n side: \"left\" | \"right\" | \"center\";\n hidden: boolean;\n typing: boolean;\n line: string;\n /** measured box size, or undefined to let it size naturally */\n box?: { w: number; h: number } | null;\n contentStyle?: CSSProperties;\n contentRef?: React.Ref<HTMLDivElement>;\n /** offset of the tail from the near edge, in px (ignored for \"center\") */\n tailInset?: number;\n children?: ReactNode;\n};\n\nexport function Bubble({\n side,\n hidden,\n typing,\n line,\n box,\n contentStyle,\n contentRef,\n tailInset = 10,\n}: BubbleProps) {\n const tailStyle: CSSProperties | undefined =\n side === \"left\"\n ? { right: tailInset }\n : side === \"right\"\n ? { left: tailInset }\n : undefined;\n return (\n <div\n className={`clip-pal-bubble is-${side} ${hidden ? \"pal-bubble-hide\" : \"pal-say\"}`}\n >\n <div\n className=\"clip-pal-bubble-box\"\n style={box ? { width: box.w, height: box.h } : undefined}\n >\n <div ref={contentRef} className=\"clip-pal-bubble-content\" style={contentStyle}>\n {typing ? (\n <span className=\"clip-pal-dots\">\n <span className=\"clip-pal-dot\" />\n <span className=\"clip-pal-dot\" />\n <span className=\"clip-pal-dot\" />\n </span>\n ) : (\n <span className=\"pal-line-in\">{line}</span>\n )}\n </div>\n </div>\n <span className=\"clip-pal-tail\" style={tailStyle} />\n </div>\n );\n}\n","import { useEffect, useRef } from \"react\";\nimport { PAL_EMOTES, type PalEmote } from \"./types\";\n\n// Hover comments. Any element on the page can carry\n// data-pal-say=\"what he says\" data-pal-emote=\"nod\"\n// and when the pointer lands on it he says that line. The listener is one\n// `pointerover` on the document, so the page can add and remove such\n// elements freely. A cooldown keeps a busy mouse from making him stutter,\n// and the same element doesn't re-trigger until the pointer has left it.\nexport const HOVER_ATTR = \"data-pal-say\";\nexport const HOVER_EMOTE_ATTR = \"data-pal-emote\";\n\nexport type HoverLine = { text: string; emote: PalEmote | null; el: Element };\n\nexport function useHoverSay(\n enabled: boolean,\n cooldownMs: number,\n onSay: (line: HoverLine) => void,\n) {\n const cb = useRef(onSay);\n cb.current = onSay;\n useEffect(() => {\n if (!enabled) return;\n let last = 0;\n let lastEl: Element | null = null;\n const over = (e: PointerEvent) => {\n const t = e.target;\n if (!(t instanceof Element)) return;\n const el = t.closest(`[${HOVER_ATTR}]`);\n if (!el) {\n lastEl = null;\n return;\n }\n if (el === lastEl) return;\n const now = performance.now();\n if (now - last < cooldownMs) return;\n const text = el.getAttribute(HOVER_ATTR);\n if (!text) return;\n last = now;\n lastEl = el;\n const raw = el.getAttribute(HOVER_EMOTE_ATTR);\n const emote =\n raw && (PAL_EMOTES as readonly string[]).includes(raw)\n ? (raw as PalEmote)\n : null;\n cb.current({ text, emote, el });\n };\n document.addEventListener(\"pointerover\", over, { passive: true });\n return () => document.removeEventListener(\"pointerover\", over);\n }, [enabled, cooldownMs]);\n}\n","import {\n type CSSProperties,\n type PointerEvent as ReactPointerEvent,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n} from \"react\";\nimport { Bubble } from \"./Bubble\";\nimport { useHoverSay } from \"./hover\";\nimport PalSvg from \"./PalSvg\";\nimport {\n gestureMs,\n isPose,\n palVars,\n PAL_POSE_OUT_MS,\n type PalColors,\n type PalEmote,\n type PalLine,\n type PalMotion,\n type PalShape,\n type PalShapeName,\n type PalStop,\n} from \"./types\";\n\n// The scroll guide, for wide screens.\n//\n// Every stop is ANCHORED to a real element on the page — he stands next to a\n// headline, beside a card, in the gutter by a paragraph — and rides along\n// with it as the page scrolls. Walking from one anchor to the next is what\n// gives the run a reason: he's off to the next thing worth pointing at.\n//\n// He only ever *targets* a resting spot (never a point in between), so\n// stopping mid-scroll can't strand him on top of content. The bubble sits\n// above his head and grows away from whatever he's standing next to.\n//\n// He needs a gutter to stand in: the space between the edge of the screen\n// and the content column. Below `minViewport` — or when the gutter is too\n// narrow to hold him — he is not rendered at all, and a PalCompanion placed\n// in the page is the usual stand-in.\n//\n// Everything that was a magic number is a prop; the defaults are the values\n// the ClipSpace site was tuned with.\n\nexport type ScrollPalHandle = {\n /** interrupt and say this, optionally acting it out */\n say: (text: string, emote?: PalEmote | null) => void;\n /** play a gesture or fold into a pose without saying anything */\n emote: (name: PalEmote) => void;\n /** put the bubble away and unfold any held pose */\n hush: () => void;\n /** the stop he is currently at (or \"idle\" while wandering) */\n current: () => string;\n};\n\nexport type ScrollPalProps = {\n /** where he stops, top to bottom */\n stops: readonly PalStop[];\n /**\n * Lines for when the page sits still. After `idleAfterMs` without any\n * input he wanders to the far side of the screen and says one of these;\n * omit (or set `idle={false}`) to keep him at his post.\n */\n idleLines?: readonly PalLine[];\n /** wander when the page sits still (default true when idleLines given) */\n idle?: boolean;\n /** stillness before he wanders off, ms (default 12 000) */\n idleAfterMs?: number;\n /** pause between idle wanders, ms (default 20 000) */\n idleWanderMs?: number;\n\n /** narrowest viewport he appears on, px (default 1600) */\n minViewport?: number;\n /**\n * Width of the content column he must stay clear of. A number in px, a\n * function returning px, or omitted: then the CSS custom property named by\n * `contentVar` on <html> is read (in rem or px), falling back to 72rem.\n */\n contentWidth?: number | (() => number);\n /** custom property that holds the content width (default \"--content-w\") */\n contentVar?: string;\n /** his smallest rendered width — narrower gutters hide him (default 46) */\n minWidth?: number;\n /** his largest rendered width, px (default 92) */\n maxWidth?: number;\n /** clearance between him and the content column, px (default 12) */\n gap?: number;\n /** clearance from the screen edge, px (default 8) */\n edge?: number;\n /** force a gutter for every stop instead of following `place` */\n side?: \"auto\" | \"left\" | \"right\";\n /** shift his resting height, px (positive = lower) */\n offsetY?: number;\n /** how close to the top/bottom edge he may stand, px (default 150) */\n yLimitPad?: number;\n /** z-index of the fixed wrapper (default 40) */\n zIndex?: number;\n\n /** walking speed, px per second (default 480) */\n speed?: number;\n /** vertical catch-up speed, px per second (default 540) */\n speedY?: number;\n /** ease rate toward the target — bigger snaps, smaller drifts (default 6.32) */\n ease?: number;\n /** how far he leans into a stride, degrees (default 9) */\n leanMax?: number;\n /** px/s above which he counts as walking (default 27) */\n walkThreshold?: number;\n /** how often the anchors are re-measured, ms (default 330) */\n retargetMs?: number;\n\n /** typing dots before the line lands, ms (default 850) */\n typingMs?: number;\n /** beat between the bubble and the trick, ms (default 500) */\n emoteDelayMs?: number;\n /** widest the bubble grows, px (default 260) */\n bubbleMaxWidth?: number;\n /** narrowest the bubble is allowed, px (default 150) */\n bubbleMinWidth?: number;\n /** pick lines at random (default) or in order */\n random?: boolean;\n /** show the bubble at all (default true) — false makes him mime */\n speak?: boolean;\n /** act lines out (default true); or a whitelist of the emotes he may use */\n emotes?: boolean | readonly PalEmote[];\n /** react to `data-pal-say` elements under the pointer (default true) */\n hover?: boolean;\n /** minimum gap between two hover lines, ms (default 1 200) */\n hoverCooldownMs?: number;\n /** let visitors pick him up and drop him (default true) */\n draggable?: boolean;\n\n /** a preset name or your own wire */\n shape?: PalShapeName | PalShape;\n /** wire thickness in viewBox units (default 5) */\n strokeWidth?: number;\n /** \"dots\" (default) or \"none\" */\n eyes?: \"dots\" | \"none\";\n /** colours as props — the same --pal-* variables, set inline */\n colors?: PalColors;\n /** tempo, stride, sway, blink — the same --pal-* variables, set inline */\n motion?: PalMotion;\n\n onArrive?: (stopId: string) => void;\n onLeave?: (stopId: string) => void;\n onSpeak?: (text: string, emote: PalEmote | null) => void;\n onIdle?: () => void;\n\n className?: string;\n style?: CSSProperties;\n};\n\ntype Near = { id: string; lines: readonly PalLine[] };\n\nconst ScrollPal = forwardRef<ScrollPalHandle, ScrollPalProps>(function ScrollPal(\n {\n stops,\n idleLines,\n idle: idleOn = true,\n idleAfterMs = 12_000,\n idleWanderMs = 20_000,\n minViewport = 1600,\n contentWidth,\n contentVar = \"--content-w\",\n minWidth = 46,\n maxWidth = 92,\n gap = 12,\n edge = 8,\n side = \"auto\",\n offsetY = 0,\n yLimitPad = 150,\n zIndex = 40,\n speed = 480,\n speedY = 540,\n ease: easeRate = 6.32,\n leanMax = 9,\n walkThreshold = 27,\n retargetMs = 330,\n typingMs = 850,\n emoteDelayMs = 500,\n bubbleMaxWidth = 260,\n bubbleMinWidth = 150,\n random = true,\n speak = true,\n emotes = true,\n hover = true,\n hoverCooldownMs = 1200,\n draggable = true,\n shape,\n strokeWidth,\n eyes,\n colors,\n motion,\n onArrive,\n onLeave,\n onSpeak,\n onIdle,\n className,\n style,\n },\n ref,\n) {\n const wrapRef = useRef<HTMLDivElement>(null);\n const palRef = useRef<HTMLDivElement>(null);\n const [line, setLine] = useState(stops[0]?.lines[0]?.[0] ?? \"\");\n const [leftGutter, setLeftGutter] = useState(true);\n const [bubbleOn, setBubbleOn] = useState(true);\n const [typing, setTyping] = useState(true);\n const [arrivalId, setArrivalId] = useState(0);\n const [walking, setWalking] = useState(false);\n // `out` flips a held pose into its unfolding half, so he never snaps back\n // into a paperclip mid-shape\n const [emote, setEmote] = useState<{ name: PalEmote; out: boolean } | null>(\n null,\n );\n // the pose he is currently holding, tracked synchronously so the walk\n // handler can unfold it without waiting on a render\n const pose = useRef<PalEmote | null>(null);\n const [palW, setPalW] = useState(88);\n const [bubbleW, setBubbleW] = useState(240);\n const [bubSize, setBubSize] = useState<{ w: number; h: number } | null>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n\n const target = useRef({ x: 40, y: 0 });\n const cur = useRef({ x: 40, y: 0, lean: 0, facing: 1 as 1 | -1 });\n const palWRef = useRef(88);\n const nearRef = useRef<Near>(stops[0] ?? { id: \"\", lines: [] });\n const idle = useRef(false);\n // drag state: while `dragging` he follows the pointer instead of his target,\n // and `dropped` keeps him where he was let go until the next scroll\n const dragging = useRef(false);\n const dropped = useRef(false);\n const grab = useRef({ x: 0, y: 0 });\n const [grabbed, setGrabbed] = useState(false);\n // false when the side gutter is too narrow to hold him — better to hide\n // him entirely than to have him stand on top of the text\n const [roomy, setRoomy] = useState(false);\n const [wide, setWide] = useState(false);\n // the line last played at each stop, so a random pick can avoid repeating it\n const lineIdx = useRef<Record<string, number>>({});\n const isWalkingRef = useRef(false);\n\n // props the animation loop reads — kept in refs so the loop is set up once\n const p = useRef({\n stops,\n idleLines,\n random,\n speak,\n emotes,\n tempo: motion?.tempo ?? 1,\n typingMs,\n emoteDelayMs,\n bubbleMaxWidth,\n bubbleMinWidth,\n onArrive,\n onLeave,\n onSpeak,\n onIdle,\n });\n p.current = {\n stops,\n idleLines,\n random,\n speak,\n emotes,\n tempo: motion?.tempo ?? 1,\n typingMs,\n emoteDelayMs,\n bubbleMaxWidth,\n bubbleMinWidth,\n onArrive,\n onLeave,\n onSpeak,\n onIdle,\n };\n\n // timers shared by the loop and the imperative handle\n const timers = useRef<{\n type?: ReturnType<typeof setTimeout>;\n emote?: ReturnType<typeof setTimeout>;\n emoteEnd?: ReturnType<typeof setTimeout>;\n }>({});\n\n const allowed = (name: PalEmote | null): PalEmote | null => {\n const e = p.current.emotes;\n if (!name || e === false) return null;\n if (e === true) return name;\n return e.includes(name) ? name : null;\n };\n\n // Say a line now: bubble types, then the text lands, then the trick. Any\n // held pose unfolds first so he never cuts from one shape to another.\n const sayNow = (text: string, acts: PalEmote | null) => {\n const t = timers.current;\n clearTimeout(t.type);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n if (p.current.speak) {\n setLine(text);\n setBubbleOn(true);\n setTyping(true);\n setBubSize(null); // fresh bubble starts at the dots' natural size\n setArrivalId((n) => n + 1);\n t.type = setTimeout(() => setTyping(false), p.current.typingMs);\n }\n p.current.onSpeak?.(text, acts);\n const name = allowed(acts);\n const begin = () => {\n if (!name) return;\n setEmote({ name, out: false });\n if (isPose(name)) {\n pose.current = name;\n } else {\n t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(name, p.current.tempo));\n }\n };\n if (pose.current) {\n setEmote({ name: pose.current, out: true });\n pose.current = null;\n t.emote = setTimeout(begin, PAL_POSE_OUT_MS);\n } else {\n setEmote(null);\n t.emote = setTimeout(begin, p.current.emoteDelayMs);\n }\n };\n\n const hushNow = () => {\n const t = timers.current;\n clearTimeout(t.type);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n if (pose.current) {\n setEmote({ name: pose.current, out: true });\n pose.current = null;\n t.emoteEnd = setTimeout(() => setEmote(null), PAL_POSE_OUT_MS);\n } else {\n setEmote(null);\n }\n setBubbleOn(false);\n };\n\n useImperativeHandle(ref, () => ({\n say: (text, e = null) => sayNow(text, e),\n emote: (name) => {\n const t = timers.current;\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n const n = allowed(name);\n if (!n) return;\n if (pose.current) {\n setEmote({ name: pose.current, out: true });\n pose.current = null;\n t.emote = setTimeout(() => {\n setEmote({ name: n, out: false });\n if (isPose(n)) pose.current = n;\n else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));\n }, PAL_POSE_OUT_MS);\n return;\n }\n setEmote(null);\n requestAnimationFrame(() => {\n setEmote({ name: n, out: false });\n if (isPose(n)) pose.current = n;\n else t.emoteEnd = setTimeout(() => setEmote(null), gestureMs(n, p.current.tempo));\n });\n },\n hush: hushNow,\n current: () => nearRef.current.id,\n }));\n\n // hover comments: only while he is standing still and on screen\n useHoverSay(hover && wide && roomy, hoverCooldownMs, ({ text, emote: e }) => {\n if (isWalkingRef.current || dragging.current) return;\n sayNow(text, e);\n });\n\n // measure the bubble's content so the box itself can smoothly grow from\n // the little typing bubble into the full line (width/height transition)\n useLayoutEffect(() => {\n const el = contentRef.current;\n if (!el) return;\n setBubSize({ w: el.offsetWidth + 2, h: el.offsetHeight + 2 }); // +2 for the border\n }, [typing, line, bubbleW, palW, arrivalId]);\n\n // Below the breakpoint he is display:none — and nothing else runs either:\n // no rAF loop, no listeners, no re-measuring, on every phone that opens\n // the page. The loop effect below is keyed on this.\n useEffect(() => {\n const mq = window.matchMedia(`(min-width: ${minViewport}px)`);\n const onMq = () => setWide(mq.matches);\n onMq();\n mq.addEventListener(\"change\", onMq);\n return () => mq.removeEventListener(\"change\", onMq);\n }, [minViewport]);\n\n useEffect(() => {\n if (!wide) return;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n if (p.current.stops.length === 0) return;\n\n // The content column's width and the root font size can both change\n // across breakpoints, so read them each time rather than caching.\n const contentW = () => {\n if (typeof contentWidth === \"number\") return contentWidth;\n if (typeof contentWidth === \"function\") return contentWidth();\n const root = getComputedStyle(document.documentElement);\n const raw = root.getPropertyValue(contentVar).trim();\n const n = parseFloat(raw);\n if (!Number.isFinite(n)) return 72 * parseFloat(root.fontSize);\n return raw.endsWith(\"px\") ? n : n * parseFloat(root.fontSize);\n };\n const gutter = () => Math.max(0, (window.innerWidth - contentW()) / 2);\n\n const sizePal = () => {\n // as big as the gutter can hold, within a sensible range\n const room = gutter() - gap - edge;\n // Below his minimum width there is no gutter left to stand in, and\n // clamping would park him on top of the content instead.\n setRoomy(room >= minWidth);\n const w = Math.max(minWidth, Math.min(maxWidth, Math.floor(room)));\n if (w !== palWRef.current) {\n palWRef.current = w;\n setPalW(w);\n }\n };\n\n // park just outside the content box: his content-facing edge stops a\n // gap short of it, so text and buttons are never covered\n const parkLeft = () => Math.max(edge, gutter() - gap - palWRef.current);\n const parkRight = () =>\n Math.min(\n window.innerWidth - edge - palWRef.current,\n window.innerWidth - gutter() + gap,\n );\n\n const fitBubble = (x: number, onLeft: boolean) => {\n const pw = palWRef.current;\n // the bubble grows away from whatever he stands next to\n const space = onLeft ? x + pw - edge : window.innerWidth - x - pw - edge;\n const { bubbleMinWidth: lo, bubbleMaxWidth: hi } = p.current;\n setBubbleW(Math.round(Math.max(lo, Math.min(hi, space))));\n };\n\n const computeTarget = () => {\n sizePal();\n const all = p.current.stops;\n const h = window.innerHeight;\n const vc = h / 2;\n\n // deterministic: he belongs to whichever section the middle of the\n // screen is inside — the divider between two sections is the exact\n // switch point, the same in both scroll directions, no in-between zone\n let near: PalStop | null = null;\n let fallback = all[0];\n let bestD = Infinity;\n for (const s of all) {\n const el = document.getElementById(s.id);\n if (!el) continue;\n const r = el.getBoundingClientRect();\n if (vc >= r.top && vc <= r.bottom) {\n near = s;\n break;\n }\n // center sits in a gap or past the ends — nearest section edge wins\n const d = Math.min(Math.abs(r.top - vc), Math.abs(r.bottom - vc));\n if (d < bestD) {\n bestD = d;\n fallback = s;\n }\n }\n if (near === null) near = fallback;\n // the footer can never reach the viewport center, so treat \"scrolled\n // to the bottom\" as arriving at the last stop\n const atBottom =\n h + window.scrollY >= document.documentElement.scrollHeight - 2;\n if (atBottom) near = all[all.length - 1];\n\n const pw = palWRef.current;\n const a = document.getElementById(near.anchor)?.getBoundingClientRect();\n\n // where he stands: next to his anchor element, or in a gutter beside it\n const place =\n side === \"left\" ? \"gutter-left\" : side === \"right\" ? \"gutter-right\" : near.place;\n let x: number;\n if (place === \"gutter-left\") x = parkLeft();\n else if (place === \"gutter-right\") x = parkRight();\n else if (a) x = Math.min(window.innerWidth - edge - pw, a.right + (near.gap ?? 26));\n else x = parkLeft();\n\n // ...and at his anchor's height, riding along as the page scrolls,\n // but never drifting off screen\n const yCenter = a ? a.top + a.height / 2 : vc;\n const yLimit = h / 2 - yLimitPad;\n const y = Math.max(-yLimit, Math.min(yLimit, yCenter - vc + offsetY));\n\n target.current = { x, y };\n nearRef.current = near;\n const onLeft = place === \"gutter-left\";\n setLeftGutter(onLeft);\n fitBubble(x, onLeft);\n };\n\n let idleTimer: ReturnType<typeof setTimeout> | undefined;\n\n // pick a resting spot on the far side of the screen from where he stands,\n // at a random height, and grow the bubble toward the near screen edge\n const wanderSpot = () => {\n sizePal();\n const onLeftNow = cur.current.x < window.innerWidth / 2;\n const x = onLeftNow ? parkRight() : parkLeft();\n const yLimit = window.innerHeight / 2 - yLimitPad - 30;\n target.current = {\n x,\n y: Math.round((Math.random() * 2 - 1) * Math.max(0, yLimit)),\n };\n const left = !onLeftNow; // ends up in the left gutter?\n setLeftGutter(left);\n fitBubble(x, left);\n nearRef.current = { id: \"idle\", lines: p.current.idleLines ?? [] };\n };\n\n const idleTick = () => {\n if (!idle.current) return;\n wanderSpot();\n idleTimer = setTimeout(idleTick, idleWanderMs);\n };\n const enterIdle = () => {\n if (idle.current || !idleOn || !p.current.idleLines?.length) return;\n idle.current = true;\n p.current.onIdle?.();\n wanderSpot();\n idleTimer = setTimeout(idleTick, idleWanderMs);\n };\n const exitIdle = () => {\n if (!idle.current) return;\n idle.current = false;\n computeTarget(); // straight back to his scroll anchor\n };\n // any interaction wakes him and restarts the stillness countdown\n const activity = () => {\n clearTimeout(idleTimer);\n exitIdle();\n idleTimer = setTimeout(enterIdle, idleAfterMs);\n };\n\n let raf = 0;\n let wasWalking = false;\n let shownStop: string | null = null; // stop whose line the bubble shows\n let last = performance.now();\n let lastRetarget = 0;\n\n const loop = (now: number) => {\n const c = cur.current;\n // Seconds since the previous frame, floored at 20fps: coming back to a\n // backgrounded tab hands us one enormous gap, and without the clamp he\n // would teleport across the screen on the first frame back.\n const dt = Math.min(0.05, Math.max(0.001, (now - last) / 1000));\n last = now;\n\n // being dragged: he goes exactly where the pointer puts him, and his\n // target follows so he doesn't snap back the instant he's released\n if (dragging.current) {\n target.current.x = c.x;\n target.current.y = c.y;\n if (wrapRef.current) {\n wrapRef.current.style.transform = `translate(${c.x}px, calc(-50% + ${c.y}px))`;\n }\n raf = requestAnimationFrame(loop);\n return;\n }\n\n // re-measure a few times a second: reveal animations and layout\n // shifts move the anchors without firing a scroll event. Skipped while\n // he's been dropped somewhere, so he stays put until the next scroll.\n if (now - lastRetarget > retargetMs) {\n lastRetarget = now;\n if (!idle.current && !dropped.current) computeTarget();\n }\n\n // Ease toward the target, then cap the result — so he sets off at a\n // steady walk and slows down as he arrives, at the same pace on any\n // refresh rate. Velocities are px/s; multiplying by dt gives the step.\n const ease = 1 - Math.exp(-easeRate * dt);\n const dx = target.current.x - c.x;\n let vx = (dx * ease) / dt;\n if (vx > speed) vx = speed;\n if (vx < -speed) vx = -speed;\n if (Math.abs(dx) > 0.4) c.x += vx * dt;\n // vertical follow is a touch quicker: he lags behind fast scrolls and\n // catches up, like he's actually running after his anchor\n let vy = ((target.current.y - c.y) * ease) / dt;\n if (vy > speedY) vy = speedY;\n if (vy < -speedY) vy = -speedY;\n c.y += vy * dt;\n\n // lean into the stride, straighten out when idle\n const leanTarget = Math.max(-leanMax, Math.min(leanMax, (vx / speed) * leanMax * 1.42));\n c.lean += (leanTarget - c.lean) * (1 - Math.exp(-(easeRate * 1.21) * dt));\n if (Math.abs(vx) > walkThreshold * 1.33) c.facing = vx > 0 ? 1 : -1;\n\n const isWalking = Math.abs(vx) > walkThreshold;\n isWalkingRef.current = isWalking;\n if (isWalking !== wasWalking) {\n wasWalking = isWalking;\n setWalking(isWalking);\n if (isWalking) {\n // he sets off — put the bubble away until he arrives. A held pose\n // unfolds on the way out; cutting the class here is what used to\n // make him teleport back into a paperclip.\n hushNow();\n if (shownStop) p.current.onLeave?.(shownStop);\n shownStop = null;\n }\n }\n // speak whenever he's standing at a stop he hasn't announced yet —\n // this also covers two same-side stops, where there's no walk at all\n if (!isWalking && shownStop !== nearRef.current.id) {\n const s = nearRef.current;\n shownStop = s.id;\n p.current.onArrive?.(s.id);\n if (s.lines.length > 0) {\n // random line, but never the same one twice running — with this\n // many per stop an immediate repeat is the only ordering that\n // reads as a bug. Or in order, when asked.\n const prev = lineIdx.current[s.id];\n let i: number;\n if (p.current.random) {\n i = Math.floor(Math.random() * s.lines.length);\n if (s.lines.length > 1 && i === prev) i = (i + 1) % s.lines.length;\n } else {\n i = prev === undefined ? 0 : (prev + 1) % s.lines.length;\n }\n lineIdx.current[s.id] = i;\n const [text, acts] = s.lines[i];\n sayNow(text, acts);\n }\n }\n\n if (wrapRef.current) {\n wrapRef.current.style.transform = `translate(${c.x}px, calc(-50% + ${c.y}px))`;\n }\n if (palRef.current) {\n // rotate first (screen space) so the lean isn't mirrored by the flip\n palRef.current.style.transform = `rotate(${c.lean}deg) scaleX(${c.facing})`;\n }\n raf = requestAnimationFrame(loop);\n };\n\n computeTarget();\n cur.current.x = target.current.x;\n cur.current.y = target.current.y;\n raf = requestAnimationFrame(loop);\n\n const onScroll = () => {\n activity();\n dropped.current = false; // scrolling sends him back to his post\n computeTarget();\n };\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n window.addEventListener(\"resize\", computeTarget);\n window.addEventListener(\"mousemove\", activity, { passive: true });\n window.addEventListener(\"keydown\", activity);\n window.addEventListener(\"pointerdown\", activity, { passive: true });\n window.addEventListener(\"touchstart\", activity, { passive: true });\n window.addEventListener(\"wheel\", activity, { passive: true });\n idleTimer = setTimeout(enterIdle, idleAfterMs); // start the countdown\n\n return () => {\n cancelAnimationFrame(raf);\n const t = timers.current;\n clearTimeout(t.type);\n clearTimeout(idleTimer);\n clearTimeout(t.emote);\n clearTimeout(t.emoteEnd);\n window.removeEventListener(\"scroll\", onScroll);\n window.removeEventListener(\"resize\", computeTarget);\n window.removeEventListener(\"mousemove\", activity);\n window.removeEventListener(\"keydown\", activity);\n window.removeEventListener(\"pointerdown\", activity);\n window.removeEventListener(\"touchstart\", activity);\n window.removeEventListener(\"wheel\", activity);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n wide,\n contentWidth,\n contentVar,\n minWidth,\n maxWidth,\n gap,\n edge,\n side,\n offsetY,\n yLimitPad,\n speed,\n speedY,\n easeRate,\n leanMax,\n walkThreshold,\n retargetMs,\n idleOn,\n idleAfterMs,\n idleWanderMs,\n ]);\n\n // Drag handlers live outside the animation effect because they only touch\n // refs the loop already reads. Pointer capture means a fast drag can't\n // outrun the element and drop him mid-flight.\n const onPointerDown = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (!draggable) return;\n e.preventDefault();\n e.currentTarget.setPointerCapture(e.pointerId);\n grab.current = {\n x: e.clientX - cur.current.x,\n y: e.clientY - (window.innerHeight / 2 + cur.current.y),\n };\n dragging.current = true;\n dropped.current = true;\n setGrabbed(true);\n };\n\n const onPointerMove = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (!dragging.current) return;\n cur.current.x = e.clientX - grab.current.x;\n cur.current.y = e.clientY - grab.current.y - window.innerHeight / 2;\n };\n\n const endDrag = (e: ReactPointerEvent<HTMLDivElement>) => {\n if (!dragging.current) return;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n dragging.current = false;\n setGrabbed(false);\n };\n\n const bubbleSide = leftGutter ? \"left\" : \"right\";\n const vars = palVars(colors, motion) as CSSProperties;\n\n return (\n <div\n ref={wrapRef}\n className={`clip-pal-scroll${className ? ` ${className}` : \"\"}`}\n data-on={wide && roomy ? \"\" : undefined}\n style={{ ...vars, transform: \"translate(40px, -50%)\", zIndex, ...style }}\n aria-hidden\n >\n <div className=\"clip-pal-anchor\">\n {/* above his head, growing toward the screen edge — it stays in the\n gutter, so it can never cover text. types on arrival (key\n remount), then holds the line until he walks off */}\n {speak && (\n <div\n className=\"clip-pal-bubble-wrap\"\n style={leftGutter ? { right: 0 } : { left: 0 }}\n >\n <div key={arrivalId}>\n <Bubble\n side={bubbleSide}\n hidden={!bubbleOn}\n typing={typing}\n line={line}\n box={bubSize}\n contentRef={contentRef}\n contentStyle={{\n maxWidth: bubbleW,\n // never narrower than the pal — keeps the tail well inside\n // the bubble even with just the typing dots\n minWidth: palW + 8,\n }}\n tailInset={Math.max(10, palW / 2 - 7)}\n />\n </div>\n </div>\n )}\n {/* the only interactive part — the wrapper stays click-through so he\n never steals a click from the page behind him */}\n <div\n ref={palRef}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n className={`clip-pal-grab${grabbed ? \" is-grabbed\" : \"\"}`}\n style={draggable ? undefined : { cursor: \"default\" }}\n >\n <div className={walking ? \"pal-walk\" : \"pal-idle\"}>\n <PalSvg\n width={palW}\n walking={walking}\n emote={emote?.name ?? null}\n emoteOut={emote?.out ?? false}\n shape={shape}\n strokeWidth={strokeWidth}\n eyes={eyes}\n />\n </div>\n </div>\n </div>\n </div>\n );\n});\n\nexport default ScrollPal;\n"],"mappings":"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","names":["useEffect","useRef","jsx","jsxs","jsx","jsxs","PalCompanion","useRef","useEffect","forwardRef","useEffect","useImperativeHandle","useRef","useState","jsx","jsxs","forwardRef","ScrollPal","useRef","useState","useImperativeHandle","useEffect"]}