// src/pointer/index.js
var pathOf = (el) => {
const path = [];
for (let cur = el; cur; cur = cur.parentElement)
path.push(cur);
return path.reverse();
};
var prefixLen = (a, b) => {
const n = Math.min(a.length, b.length);
let i = 0;
while (i < n && a[i] === b[i])
i++;
return i;
};
var eventDefaults = (type) => ({
pointerover: { bubbles: true, cancelable: true, composed: true },
pointerenter: { bubbles: false, cancelable: false, composed: false },
pointerdown: { bubbles: true, cancelable: true, composed: true },
pointermove: { bubbles: true, cancelable: true, composed: true },
pointerup: { bubbles: true, cancelable: true, composed: true },
pointerout: { bubbles: true, cancelable: true, composed: true },
pointerleave: { bubbles: false, cancelable: false, composed: false },
pointercancel: { bubbles: true, cancelable: true, composed: true },
gotpointercapture: { bubbles: true, cancelable: false, composed: true },
lostpointercapture: { bubbles: true, cancelable: false, composed: true }
})[type] ?? { bubbles: true, cancelable: true, composed: true };
var mouseCompat = {
pointerover: "mouseover",
pointerenter: "mouseenter",
pointerdown: "mousedown",
pointermove: "mousemove",
pointerup: "mouseup",
pointerout: "mouseout",
pointerleave: "mouseleave"
};
var clamp = (min, max, n) => Math.min(max, Math.max(min, n));
var rand01 = (seed) => {
let t = seed + 1831565813 >>> 0;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
var phase = (seed) => rand01(seed) * Math.PI * 2;
var drift = (min, max, t, phi) => min + (max - min) * ((Math.sin(Math.PI * 2 * t + phi) + 1) / 2);
var Pointer = class _Pointer {
static id = () => {
const { crypto } = globalThis;
if (!crypto?.getRandomValues)
throw new Error("crypto.getRandomValues is required");
return crypto.getRandomValues(new Uint32Array(1))[0];
};
#id;
#primary;
#pressed = false;
#path = [];
#target = null;
#captureTarget = null;
#lastPoint = null;
#lastMovePoint = null;
constructor({ id = _Pointer.id(), primary } = {}) {
if (typeof primary !== "boolean")
throw new TypeError("Pointer.primary must be boolean");
this.#id = id;
this.#primary = primary;
}
get id() {
return this.#id;
}
get target() {
return this.#target;
}
get type() {
throw new Error("Pointer.type must be implemented");
}
get emitsTouch() {
return false;
}
get implicitCapture() {
return false;
}
props(i, total) {
return {};
}
enter(target, point) {
const next = pathOf(target);
this.#target = target;
this.#path = next;
this.#lastMovePoint = point;
this.#dispatch("pointerover", target, point, { bubbles: true });
for (const el of next)
this.#dispatch("pointerenter", el, point, { bubbles: false });
return this;
}
down(target, point, i, total) {
this.#pressed = true;
this.#target = target;
this.#lastPoint = point;
this.#lastMovePoint = point;
if (this.implicitCapture)
this.#captureTarget = target;
this.#dispatch("pointerdown", target, point, {
bubbles: true,
button: 0,
buttons: 1
}, { i, total });
return this;
}
move(target, point, i, total) {
const nextTarget = this.#captureTarget ?? target;
if (!this.#captureTarget)
this.#transition(nextTarget, point, i, total);
this.#dispatch("pointermove", this.#captureTarget ?? this.#target, point, {
bubbles: true,
buttons: this.#pressed ? 1 : 0
}, { i, total });
return this;
}
up(target, point, i, total) {
this.#pressed = false;
this.#target = this.#captureTarget ?? target;
this.#dispatch("pointerup", this.#target, point, {
bubbles: true,
button: 0,
buttons: 0
}, { i, total });
return this;
}
leave(target, point, i, total) {
if (!this.#target)
return this;
this.release(this.#captureTarget, i, total);
this.#dispatch(
"pointerout",
this.#target,
point,
{ bubbles: true },
{ i, total }
);
for (const el of [...this.#path].reverse())
this.#dispatch(
"pointerleave",
el,
point,
{ bubbles: false },
{ i, total }
);
this.#pressed = false;
this.#path = [];
this.#target = null;
this.#captureTarget = null;
this.#lastPoint = null;
this.#lastMovePoint = null;
return this;
}
cancel(target, point, i, total) {
if (!this.#target)
return this;
this.#dispatch("pointercancel", this.#target, point, {
bubbles: true,
buttons: 0
}, { i, total });
return this.leave(target, point, i, total);
}
capture(target, i, total) {
this.#captureTarget = target;
if (target !== this.#target) {
this.#target = target;
this.#path = pathOf(target);
}
this.#dispatch("gotpointercapture", target, this.#lastPoint ?? null, {
bubbles: true
}, { i, total });
return this;
}
release(target, i, total) {
if (!this.#captureTarget)
return this;
this.#captureTarget = null;
this.#dispatch("lostpointercapture", target, this.#lastPoint ?? null, {
bubbles: true
}, { i, total });
return this;
}
touch(target, point) {
if (!this.emitsTouch)
return null;
const Touch = globalThis.Touch;
if (typeof Touch !== "function")
throw new Error("Touch is not available in this environment");
const { width, height } = this.props(0, 1);
const win = target.ownerDocument?.defaultView ?? globalThis.window;
const scrollX = typeof win?.scrollX === "number" ? win.scrollX : 0;
const scrollY = typeof win?.scrollY === "number" ? win.scrollY : 0;
const clientX = point.x;
const clientY = point.y;
return new Touch({
identifier: this.id,
target,
clientX,
clientY,
pageX: clientX + scrollX,
pageY: clientY + scrollY,
screenX: clientX,
screenY: clientY,
radiusX: width / 2,
radiusY: height / 2,
rotationAngle: 0,
force: 0
});
}
#transition(nextTarget, point, i, total) {
if (nextTarget === this.#target)
return;
const prev = this.#path;
const next = pathOf(nextTarget);
const common = prefixLen(prev, next);
this.#dispatch(
"pointerout",
this.#target,
point,
{ bubbles: true },
{ i, total }
);
for (const el of prev.slice(common).reverse())
this.#dispatch(
"pointerleave",
el,
point,
{ bubbles: false },
{ i, total }
);
this.#dispatch(
"pointerover",
nextTarget,
point,
{ bubbles: true },
{ i, total }
);
for (const el of next.slice(common))
this.#dispatch(
"pointerenter",
el,
point,
{ bubbles: false },
{ i, total }
);
this.#path = next;
this.#target = nextTarget;
}
#dispatch(type, target, point, extra, { i = 0, total = 1 } = {}) {
const Event2 = globalThis.PointerEvent;
if (typeof Event2 !== "function")
throw new Error("PointerEvent is not available in this environment");
const defaults = eventDefaults(type);
const base = {
pointerId: this.#id,
pointerType: this.type,
isPrimary: this.#primary,
hasCapture: Boolean(this.#captureTarget)
};
const coords = point ? {
clientX: point.x,
clientY: point.y,
pageX: point.x + (target.ownerDocument?.defaultView?.scrollX ?? 0),
pageY: point.y + (target.ownerDocument?.defaultView?.scrollY ?? 0),
screenX: point.x,
screenY: point.y
} : {};
const props = this.props(i, total);
const movement = type === "pointermove" ? this.#movement(point) : {};
const init = { ...defaults, ...base, ...coords, ...props, ...movement, ...extra };
target.dispatchEvent(new Event2(type, init));
const mtype = mouseCompat[type];
if (this.type === "mouse" && mtype)
target.dispatchEvent(new MouseEvent(mtype, init));
if (point)
this.#lastPoint = point;
}
#movement(point) {
if (!point || !this.#lastMovePoint)
return { movementX: 0, movementY: 0 };
const movementX = point.x - this.#lastMovePoint.x;
const movementY = point.y - this.#lastMovePoint.y;
this.#lastMovePoint = point;
return { movementX, movementY };
}
};
var PenPointer = class extends Pointer {
get type() {
return "pen";
}
get emitsTouch() {
return true;
}
props(i, total) {
return {
width: 0.5,
height: 0.5,
pressure: 0.5,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
altitudeAngle: 1,
azimuthAngle: 0.6
};
}
};
var IosPenPointer = class extends PenPointer {
props(i, total) {
const t = total <= 1 ? 0 : i / (total - 1);
const seed = this.id;
const pressureBase = 0.08 + 0.52 * Math.sin(Math.PI * t) ** 8;
const noise = (rand01(seed + 1e4 + i * 101) - 0.5) * 0.04;
const pressure = clamp(0.08, 0.6, pressureBase + noise);
return {
width: 0.5,
height: 0.5,
pressure,
tiltX: drift(22, 35, t, phase(seed + 1)),
tiltY: drift(20, 30, t, phase(seed + 2)),
tangentialPressure: 0,
twist: 0,
altitudeAngle: drift(0.86, 1.04, t, phase(seed + 3)),
azimuthAngle: drift(0.47, 0.83, t, phase(seed + 4))
};
}
};
var MousePointer = class extends Pointer {
get type() {
return "mouse";
}
props(i, total) {
return {
width: 1,
height: 1,
pressure: 0,
tiltX: 0,
tiltY: 0
};
}
};
var IosMousePointer = class extends MousePointer {
};
var TouchPointer = class extends Pointer {
get type() {
return "touch";
}
get emitsTouch() {
return true;
}
get implicitCapture() {
return true;
}
props(i, total) {
return {
width: 42,
height: 42,
pressure: 0,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
altitudeAngle: Math.PI / 2,
azimuthAngle: 0
};
}
};
var IosTouchPointer = class extends TouchPointer {
props(i, total) {
return {
...super.props(i, total),
width: 41.72413777559996,
height: 41.72413777559996
};
}
};
var webkit = {
pen: IosPenPointer,
mouse: IosMousePointer,
touch: IosTouchPointer
};
// src/motion/index.js
var Motion = class {
#el;
#platform;
#touches = /* @__PURE__ */ new Map();
#lastTarget = null;
#hitWarned = false;
constructor(el, opts = {}) {
if (!(el instanceof Element))
throw new TypeError("Motion.el must be an Element");
if (opts === null || typeof opts !== "object")
throw new TypeError("Motion.opts must be an object");
const { platform = webkit } = opts;
this.#el = el;
this.#platform = platform;
}
get el() {
return this.#el;
}
get platform() {
return this.#platform;
}
get device() {
throw new Error("Motion.device must be implemented");
}
static normalizePoints(name, raw) {
const pointsKey = `${name}.points`;
if (!Array.isArray(raw))
throw new TypeError(`${pointsKey} must be [x, y, ms][]`);
const points = raw.map((p, i) => {
if (!Array.isArray(p) || p.length !== 3)
throw new TypeError(`${pointsKey}[${i}] must be [x, y, ms]`);
const [x, y, ms] = p;
if (![x, y, ms].every((n) => typeof n === "number" && Number.isFinite(n)))
throw new TypeError(`${pointsKey}[${i}] must contain finite numbers`);
if (ms < 0)
throw new RangeError(`${pointsKey}[${i}][2] must be >= 0`);
return { x, y, ms };
});
for (let i = 1; i < points.length; i++) {
if (points[i].ms < points[i - 1].ms)
throw new RangeError(
`${pointsKey}[${i}][2] must be >= ${pointsKey}[${i - 1}][2]`
);
}
return points;
}
pointer(opts) {
const Ctor = this.#platform?.[this.device];
if (!Ctor)
throw new Error(`Platform missing device: ${this.device}`);
return new Ctor(opts);
}
hit(point) {
const target = document.elementFromPoint(point.x, point.y);
if (target && this.#el.contains(target))
return this.#lastTarget = target;
if (!this.#hitWarned) {
console.warn(`hit-test missed: (${point.x},${point.y}) outside element`);
this.#hitWarned = true;
}
return this.#lastTarget ?? this.#el;
}
delay(ms) {
if (!ms)
return Promise.resolve();
return new Promise(
(resolve) => setTimeout(resolve, ms)
);
}
touchstart(pointer, point, gesture = { scale: 1, rotation: 0 }) {
const target = pointer.target;
if (!pointer.emitsTouch || !target)
return;
this.#touches.set(pointer.id, { pointer, target, point });
const changed = pointer.touch(target, point);
this.#dispatchTouch("touchstart", target, changed, gesture);
}
touchsync(pointer, point) {
if (!pointer.emitsTouch)
return;
const entry = this.#touches.get(pointer.id);
if (!entry)
return;
entry.point = point;
}
touchmove(pointer, point, gesture = { scale: 1, rotation: 0 }) {
if (!pointer.emitsTouch)
return;
const entry = this.#touches.get(pointer.id);
if (!entry)
return;
entry.point = point;
const changed = pointer.touch(entry.target, point);
this.#dispatchTouch("touchmove", entry.target, changed, gesture);
}
touchend(pointer, point, gesture = { scale: 1, rotation: 0 }) {
if (!pointer.emitsTouch)
return;
const entry = this.#touches.get(pointer.id);
if (!entry)
return;
const changed = pointer.touch(entry.target, point);
this.#touches.delete(pointer.id);
this.#dispatchTouch("touchend", entry.target, changed, gesture);
}
touchcancel(pointer, point, gesture = { scale: 1, rotation: 0 }) {
if (!pointer.emitsTouch)
return;
const entry = this.#touches.get(pointer.id);
if (!entry)
return;
const changed = pointer.touch(entry.target, point);
this.#touches.delete(pointer.id);
this.#dispatchTouch("touchcancel", entry.target, changed, gesture);
}
gesturestart(target, gesture = { scale: 1, rotation: 0 }) {
this.#dispatchGesture("gesturestart", target, gesture);
}
gesturechange(target, gesture) {
this.#dispatchGesture("gesturechange", target, gesture);
}
gestureend(target, gesture) {
this.#dispatchGesture("gestureend", target, gesture);
}
#dispatchTouch(type, target, changed, gesture) {
const Event2 = globalThis.TouchEvent;
if (typeof Event2 !== "function")
throw new Error("TouchEvent is not available in this environment");
const touches = [...this.#touches.values()].map(
(entry) => entry.target === target && entry.pointer.id === changed.identifier ? changed : entry.pointer.touch(entry.target, entry.point)
);
const init = {
bubbles: true,
cancelable: true,
composed: true,
touches,
targetTouches: touches.filter((t) => t.target === target),
changedTouches: [changed],
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
scale: gesture.scale,
rotation: gesture.rotation
};
target.dispatchEvent(new Event2(type, init));
}
#dispatchGesture(type, target, { scale = 1, rotation = 0 } = {}) {
const Event2 = globalThis.GestureEvent;
if (typeof Event2 !== "function")
return;
const points = [...this.#touches.values()].map((entry) => entry.point).filter(Boolean);
const coords = points.length ? (() => {
const clientX = points.reduce((sum, p) => sum + p.x, 0) / points.length;
const clientY = points.reduce((sum, p) => sum + p.y, 0) / points.length;
const win = target.ownerDocument?.defaultView ?? globalThis.window;
const scrollX = typeof win?.scrollX === "number" ? win.scrollX : 0;
const scrollY = typeof win?.scrollY === "number" ? win.scrollY : 0;
return {
clientX,
clientY,
pageX: clientX + scrollX,
pageY: clientY + scrollY
};
})() : {};
target.dispatchEvent(new Event2(type, {
bubbles: true,
cancelable: true,
composed: true,
...coords,
scale,
rotation
}));
}
};
// src/font/index.js
var attr = (tag, name) => {
const match = tag.match(new RegExp(`${name}="([^"]*)"`, "i"));
return match?.[1] ?? null;
};
var parseD = (d) => {
if (!d)
return [];
const tokens = d.trim().split(/\s+/);
const strokes = [];
let current = null;
for (let i = 0; i < tokens.length; i++) {
const t = tokens[i];
if (t === "M") {
current = [[+tokens[i + 1], +tokens[i + 2]]];
strokes.push(current);
i += 2;
} else if (t === "L") {
current?.push([+tokens[i + 1], +tokens[i + 2]]);
i += 2;
}
}
return strokes.filter((s) => s.length > 1);
};
var parseSvg = (svg) => {
const fontTag = svg.match(/]*>/i)?.[0];
if (!fontTag)
throw new TypeError("missing element");
const faceTag = svg.match(/