@nadicodeai/ui 6.0.0 → 6.0.2

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.
Files changed (37) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/components/forma-avatar.d.ts +23 -0
  3. package/dist/components/forma-avatar.d.ts.map +1 -0
  4. package/dist/components/forma-avatar.js +95 -0
  5. package/dist/hooks/use-confirm-action.d.ts +0 -6
  6. package/dist/hooks/use-confirm-action.d.ts.map +1 -1
  7. package/dist/hooks/use-confirm-action.js +5 -12
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/internal/forma/frame.d.ts +10 -0
  12. package/dist/internal/forma/frame.d.ts.map +1 -0
  13. package/dist/internal/forma/frame.js +211 -0
  14. package/dist/internal/forma/geometry.d.ts +41 -0
  15. package/dist/internal/forma/geometry.d.ts.map +1 -0
  16. package/dist/internal/forma/geometry.js +235 -0
  17. package/dist/internal/forma/hallmark.d.ts +5 -0
  18. package/dist/internal/forma/hallmark.d.ts.map +1 -0
  19. package/dist/internal/forma/hallmark.js +4 -0
  20. package/dist/internal/forma/model.d.ts +53 -0
  21. package/dist/internal/forma/model.d.ts.map +1 -0
  22. package/dist/internal/forma/model.js +70 -0
  23. package/dist/internal/forma/motion.d.ts +42 -0
  24. package/dist/internal/forma/motion.d.ts.map +1 -0
  25. package/dist/internal/forma/motion.js +194 -0
  26. package/dist/internal/forma/rig.d.ts +10 -0
  27. package/dist/internal/forma/rig.d.ts.map +1 -0
  28. package/dist/internal/forma/rig.js +211 -0
  29. package/dist/internal/forma/still.d.ts +4 -0
  30. package/dist/internal/forma/still.d.ts.map +1 -0
  31. package/dist/internal/forma/still.js +8 -0
  32. package/dist/lib/use-copy-to-clipboard.d.ts +0 -41
  33. package/dist/lib/use-copy-to-clipboard.d.ts.map +1 -1
  34. package/dist/lib/use-copy-to-clipboard.js +6 -15
  35. package/docs/contract.md +2 -2
  36. package/docs/forma-avatar.md +92 -0
  37. package/package.json +2 -2
package/AGENTS.md CHANGED
@@ -7,7 +7,7 @@ exports.
7
7
  - Component changes and admission: `docs/contract.md`.
8
8
  - Consumer setup: `docs/consuming-cross-repo.md`.
9
9
  - Tokens, CSS, motion, and assets: `../design-system/DESIGN.md`.
10
- - Agent visuals: `docs/agent-visual.md`.
10
+ - Agent visuals: `docs/agent-visual.md`; the Forma avatars: `docs/forma-avatar.md`.
11
11
  - System activity indicators: `docs/activity.md`.
12
12
  - First run, sign-in, and repair surfaces: `docs/threshold.md`; the
13
13
  primitives are `@nadicodeai/ui/threshold`, with
@@ -0,0 +1,23 @@
1
+ import { type CSSProperties } from "react";
2
+ import { type FormaChoice, type FormaColor, type FormaForm, type FormaOptions, type FormaState, type FormaView } from "../internal/forma/model.js";
3
+ type AccessibleAvatar = {
4
+ decorative?: true;
5
+ label?: never;
6
+ } | {
7
+ decorative: false;
8
+ label: string;
9
+ };
10
+ type FormaAvatarProps = FormaOptions & AccessibleAvatar & {
11
+ className?: string;
12
+ style?: CSSProperties;
13
+ };
14
+ /**
15
+ * The Forma avatar: Nadia's approved shell and face, or one of the seven
16
+ * forms in the shell colour the customer chose, with the approved lifecycle
17
+ * gestures. One SVG per avatar; the server renders the state's resting frame
18
+ * and the client rig animates it in place.
19
+ */
20
+ declare function FormaAvatar({ form, color, state, size, paused, view, motionKey, decorative, label, className, style, }: FormaAvatarProps): import("react").JSX.Element;
21
+ export { FormaAvatar, type FormaAvatarProps, type FormaChoice, type FormaColor, type FormaForm, type FormaState, type FormaView, };
22
+ export { defaultForm, formDetails, formaChoices, formaColorVariable, formaColors, formaForms, formaStates, } from "../internal/forma/model.js";
23
+ //# sourceMappingURL=forma-avatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forma-avatar.d.ts","sourceRoot":"","sources":["../../src/components/forma-avatar.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAsC,MAAM,OAAO,CAAC;AAW/E,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EAMf,MAAM,wBAAwB,CAAC;AAIhC,KAAK,gBAAgB,GAAG;IAAE,UAAU,CAAC,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,KAAK,gBAAgB,GAAG,YAAY,GAClC,gBAAgB,GAAG;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAiBJ;;;;;GAKG;AACH,iBAAS,WAAW,CAAC,EACnB,IAAI,EACJ,KAAK,EACL,KAAc,EACd,IAAS,EACT,MAAc,EACd,IAAc,EACd,SAAS,EACT,UAAiB,EACjB,KAAK,EACL,SAAS,EACT,KAAK,GACN,EAAE,gBAAgB,+BA4blB;AAED,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,GACf,CAAC;AACF,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,WAAW,GACZ,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { ORBIT_DOTS, ORBIT_TILTS, RIBBON_BEADS, RIBBON_TILTS, SHELL_LAYERS, } from "../internal/forma/frame.js";
5
+ import { bodies, eyeProfiles } from "../internal/forma/geometry.js";
6
+ import { ITALY_PATH } from "../internal/forma/hallmark.js";
7
+ import { celebrationColors, formDetails, formaColorVariable, resolveFormaOptions, shellColor, } from "../internal/forma/model.js";
8
+ import { stillFrame } from "../internal/forma/still.js";
9
+ const WHITE = "var(--nc-identity-white)";
10
+ const INK = "var(--nc-identity-ink)";
11
+ function part(frame, name) {
12
+ const { display, ...attributes } = frame[name] ?? {};
13
+ return {
14
+ "data-part": name,
15
+ ...attributes,
16
+ ...(display === undefined ? {} : { style: { display } }),
17
+ };
18
+ }
19
+ const orbitDotRadius = (i) => (i % 10 === 0 ? 0.036 : 0.022 * (1 - (i % 10) / 13)).toFixed(4);
20
+ /**
21
+ * The Forma avatar: Nadia's approved shell and face, or one of the seven
22
+ * forms in the shell colour the customer chose, with the approved lifecycle
23
+ * gestures. One SVG per avatar; the server renders the state's resting frame
24
+ * and the client rig animates it in place.
25
+ */
26
+ function FormaAvatar({ form, color, state = "idle", size = 32, paused = false, view = "front", motionKey, decorative = true, label, className, style, }) {
27
+ const options = resolveFormaOptions({ form, color, state, size, paused, view, motionKey });
28
+ const id = useId().replace(/:/g, "");
29
+ const host = useRef(null);
30
+ const handle = useRef(null);
31
+ const latest = useRef(options);
32
+ const [mode, setMode] = useState("still");
33
+ useEffect(() => {
34
+ latest.current = options;
35
+ handle.current?.update(options);
36
+ }, [options]);
37
+ useEffect(() => {
38
+ const element = host.current;
39
+ if (!element)
40
+ return;
41
+ let generation = 0;
42
+ const preference = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : null;
43
+ const reconcile = () => {
44
+ const token = ++generation;
45
+ handle.current?.dispose();
46
+ handle.current = null;
47
+ setMode("still");
48
+ if (paused || preference?.matches)
49
+ return;
50
+ import("../internal/forma/rig.js").then(({ mountForma }) => {
51
+ if (generation !== token)
52
+ return;
53
+ handle.current = mountForma(element, latest.current, { onModeChange: setMode });
54
+ });
55
+ };
56
+ reconcile();
57
+ preference?.addEventListener("change", reconcile);
58
+ return () => {
59
+ generation++;
60
+ preference?.removeEventListener("change", reconcile);
61
+ handle.current?.dispose();
62
+ handle.current = null;
63
+ };
64
+ }, [paused]);
65
+ const detail = formDetails[options.form];
66
+ const nadia = options.form === "nadia";
67
+ const shade = shellColor(options);
68
+ const pigment = `var(${formaColorVariable[shade]})`;
69
+ const visorFill = detail.visor === "ivory" ? "var(--nc-forma-avorio)" : "var(--nc-forma-visor)";
70
+ const bezelStroke = detail.visor === "ivory" ? "var(--nc-seam)" : "var(--nc-forma-bezel)";
71
+ const eye = nadia ? "var(--nc-forma-eye)" : detail.eyes === "ink" ? INK : WHITE;
72
+ const frame = stillFrame(options);
73
+ const signature = eyeProfiles[options.form].signature === true;
74
+ const { width: w, height: h } = bodies[options.form];
75
+ const shadow = nadia ? INK : pigment;
76
+ const shading = { mixBlendMode: "multiply" };
77
+ const body = `scale(${w} ${h})`;
78
+ const url = (name) => `url(#${name}-${id})`;
79
+ const renderEye = (name) => (_jsxs("g", { ...part(frame, `eye${name}`), children: [_jsx("ellipse", { cx: "0", cy: "0", fill: url("halo"), ...part(frame, `halo${name}`) }), _jsx("path", { fill: eye, ...part(frame, `light${name}`) }), _jsx("path", { fill: url("glint"), ...part(frame, `glint${name}`) }), signature ? (_jsx("path", { d: "M0.045 0.103 Q0.092 0.102 0.112 0.147", fill: "none", stroke: eye, strokeLinecap: "round", strokeWidth: "0.021", ...part(frame, `flick${name}`) })) : null, _jsx("path", { d: "M-0.084 -0.012 Q0 0.082 0.084 -0.012", fill: "none", stroke: eye, strokeLinecap: "round", strokeWidth: "0.036", ...part(frame, `happy${name}`) }), _jsx("ellipse", { cx: "0", cy: "0", fill: url("pauseHalo"), rx: "0.17", ry: "0.32", ...part(frame, `pauseHalo${name}`) }), _jsx("rect", { fill: "var(--nc-identity-giallo)", height: "0.388", rx: "0.044", width: "0.088", x: "-0.044", y: "-0.194", ...part(frame, `pause${name}`) })] }));
80
+ const renderOrbitDots = (layer) => ORBIT_TILTS.map((_, index) => Array.from({ length: ORBIT_DOTS }, (__, i) => (_jsx("circle", { fill: pigment, r: orbitDotRadius(i), ...part(frame, `orbit${index}${layer}${i}`) }, `${index}-${i}`))));
81
+ return (_jsx("span", { "aria-hidden": decorative || undefined, "aria-label": decorative ? undefined : label, className: className, "data-color": options.color ?? "default", "data-forma-avatar": options.form, "data-state": options.state, role: decorative ? undefined : "img", style: {
82
+ display: "inline-block",
83
+ position: "relative",
84
+ flexShrink: 0,
85
+ verticalAlign: "middle",
86
+ ...style,
87
+ width: options.size,
88
+ height: options.size,
89
+ }, children: _jsxs("svg", { "aria-hidden": "true", "data-render-mode": mode, height: options.size, overflow: "visible", ref: host, style: { display: "block", overflow: "visible" }, viewBox: "-1.55 -1.55 3.1 3.1", width: options.size, children: [_jsxs("defs", { children: [_jsxs("radialGradient", { cx: "-0.18", cy: "0.3", gradientTransform: body, gradientUnits: "userSpaceOnUse", id: `tone-${id}`, r: "1.34", children: [_jsx("stop", { offset: "0", stopColor: shadow, stopOpacity: nadia ? "0.04" : "0.4" }), _jsx("stop", { offset: "1", stopColor: shadow, stopOpacity: nadia ? "0.14" : "0.95" })] }), _jsxs("radialGradient", { cx: "-0.18", cy: "0.3", gradientTransform: body, gradientUnits: "userSpaceOnUse", id: `form-${id}`, r: "1.34", children: [_jsx("stop", { offset: "0", stopColor: INK, stopOpacity: "0.02" }), _jsx("stop", { offset: "0.5", stopColor: INK, stopOpacity: "0.1" }), _jsx("stop", { offset: "0.8", stopColor: INK, stopOpacity: "0.26" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.52" })] }), _jsxs("linearGradient", { gradientTransform: body, gradientUnits: "userSpaceOnUse", id: `floor-${id}`, x1: "0", x2: "0", y1: "1", y2: "-1", children: [_jsx("stop", { offset: "0.45", stopColor: INK, stopOpacity: "0" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.42" })] }), _jsxs("radialGradient", { cx: "0.05", cy: "3.6", gradientTransform: `scale(${w} ${h * 0.3})`, gradientUnits: "userSpaceOnUse", id: `key-${id}`, r: "1.05", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.62" }), _jsx("stop", { offset: "0.4", stopColor: WHITE, stopOpacity: "0.22" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "0.5", cy: "0.5", id: `spec-${id}`, r: "0.5", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.85" }), _jsx("stop", { offset: "0.5", stopColor: WHITE, stopOpacity: "0.25" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "-0.16", cy: "-0.12", gradientTransform: body, gradientUnits: "userSpaceOnUse", id: `rim-${id}`, r: "1.2", children: [_jsx("stop", { offset: "0.7", stopColor: WHITE, stopOpacity: "0" }), _jsx("stop", { offset: "0.9", stopColor: WHITE, stopOpacity: "0.26" }), _jsx("stop", { offset: "0.975", stopColor: WHITE, stopOpacity: "0.5" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0.1" })] }), _jsxs("radialGradient", { cx: "0", cy: "0", gradientUnits: "userSpaceOnUse", id: `tube-${id}`, r: "1", children: [_jsx("stop", { offset: "0.64", stopColor: INK, stopOpacity: "0.45" }), _jsx("stop", { offset: "0.7", stopColor: INK, stopOpacity: "0.1" }), _jsx("stop", { offset: "0.76", stopColor: WHITE, stopOpacity: "0" }), _jsx("stop", { offset: "0.82", stopColor: WHITE, stopOpacity: "0.36" }), _jsx("stop", { offset: "0.89", stopColor: WHITE, stopOpacity: "0" }), _jsx("stop", { offset: "0.95", stopColor: INK, stopOpacity: "0" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.45" })] }), _jsxs("radialGradient", { cx: "0.3", cy: "1", gradientTransform: "translate(0.3 1) scale(1 0.6) translate(-0.3 -1)", id: `glass-${id}`, r: "0.7", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.36" }), _jsx("stop", { offset: "0.5", stopColor: WHITE, stopOpacity: "0.14" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "0.2", cy: "0.84", gradientTransform: "translate(0.2 0.84) scale(1 0.5) translate(-0.2 -0.84)", id: `streak-${id}`, r: "0.24", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.9" }), _jsx("stop", { offset: "0.62", stopColor: WHITE, stopOpacity: "0.72" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "0.98", cy: "0.58", gradientTransform: "translate(0.98 0.58) scale(0.3 1) translate(-0.98 -0.58)", id: `sole-${id}`, r: "0.42", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.28" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("linearGradient", { id: `visorShade-${id}`, x1: "0", x2: "0", y1: "1", y2: "0", children: [_jsx("stop", { offset: "0.45", stopColor: INK, stopOpacity: "0" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.22" })] }), _jsxs("linearGradient", { id: `bezelLight-${id}`, x1: "0", x2: "1", y1: "1", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.5" }), _jsx("stop", { offset: "0.35", stopColor: WHITE, stopOpacity: "0" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.25" })] }), _jsxs("radialGradient", { cx: "0.5", cy: "0.5", id: `halo-${id}`, r: "0.5", children: [_jsx("stop", { offset: "0", stopColor: eye, stopOpacity: "0.55" }), _jsx("stop", { offset: "0.45", stopColor: eye, stopOpacity: "0.18" }), _jsx("stop", { offset: "1", stopColor: eye, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "0.5", cy: "0.62", id: `glint-${id}`, r: "0.55", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.6" }), _jsx("stop", { offset: "1", stopColor: WHITE, stopOpacity: "0" })] }), _jsxs("radialGradient", { cx: "0.5", cy: "0.5", id: `pauseHalo-${id}`, r: "0.5", children: [_jsx("stop", { offset: "0", stopColor: "var(--nc-identity-giallo)", stopOpacity: "0.5" }), _jsx("stop", { offset: "0.5", stopColor: "var(--nc-identity-giallo)", stopOpacity: "0.14" }), _jsx("stop", { offset: "1", stopColor: "var(--nc-identity-giallo)", stopOpacity: "0" })] }), _jsxs("linearGradient", { id: `bar-${id}`, x1: "0", x2: "1", y1: "0", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.55" }), _jsx("stop", { offset: "0.4", stopColor: WHITE, stopOpacity: "0.08" }), _jsx("stop", { offset: "0.7", stopColor: INK, stopOpacity: "0.06" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.4" })] }), _jsxs("radialGradient", { cx: "0.36", cy: "0.68", id: `bead-${id}`, r: "0.72", children: [_jsx("stop", { offset: "0", stopColor: WHITE, stopOpacity: "0.65" }), _jsx("stop", { offset: "0.45", stopColor: WHITE, stopOpacity: "0" }), _jsx("stop", { offset: "0.78", stopColor: INK, stopOpacity: "0" }), _jsx("stop", { offset: "1", stopColor: INK, stopOpacity: "0.45" })] }), _jsx("clipPath", { id: `shell-${id}`, children: _jsx("path", { clipRule: "evenodd", ...part(frame, "shellClip") }) }), _jsx("clipPath", { id: `visor-${id}`, children: _jsx("path", { ...part(frame, "visorClip") }) })] }), _jsxs("g", { "data-part": "root", transform: "scale(1 -1)", children: [_jsx("g", { ...part(frame, "effectsBack"), children: renderOrbitDots("Back") }), _jsxs("g", { ...part(frame, "object"), children: [_jsxs("g", { clipPath: url("shell"), children: [_jsx("path", { fill: pigment, fillRule: "evenodd", ...part(frame, "shell") }), nadia ? (_jsxs(_Fragment, { children: [_jsx("path", { fill: "var(--nc-forma-avorio)", ...part(frame, "bandW") }), _jsx("path", { fill: "var(--nc-identity-rosso)", ...part(frame, "bandR") }), _jsx("path", { fill: "var(--nc-forma-sabbia)", ...part(frame, "bandSand") }), _jsx("g", { fill: "none", opacity: "0.82", stroke: "var(--nc-forma-avorio)", strokeLinecap: "round", strokeLinejoin: "round", strokeOpacity: "0.72", strokeWidth: "12", ...part(frame, "hallmark"), children: _jsx("path", { d: ITALY_PATH }) })] })) : null, SHELL_LAYERS.map((layer) => (_jsx("path", { fill: url(layer), fillRule: "evenodd", style: layer === "tone" ? shading : undefined, ...part(frame, layer) }, layer))), _jsx("ellipse", { cx: -0.4 * w, cy: 0.6 * h, fill: url("spec"), rx: 0.27 * w, ry: 0.11 * h, transform: `rotate(-24 ${-0.4 * w} ${0.6 * h})`, ...part(frame, "hot") }), _jsx("path", { fill: url("tube"), fillRule: "evenodd", ...part(frame, "tube") }), _jsx("path", { fill: "var(--nc-forma-sabbia)", fillRule: "evenodd", ...part(frame, "fade") }), _jsxs("g", { ...part(frame, "face"), children: [_jsx("path", { fill: "none", stroke: bezelStroke, strokeWidth: "0.014", ...part(frame, "bezel") }), _jsx("path", { fill: "none", stroke: url("bezelLight"), strokeWidth: "0.014", ...part(frame, "bezelLight") }), _jsx("path", { fill: visorFill, ...part(frame, "visor") }), _jsxs("g", { clipPath: url("visor"), children: [_jsx("path", { fill: url("visorShade"), ...part(frame, "visorShade") }), _jsx("path", { fill: url("glass"), ...part(frame, "glass") }), _jsx("path", { fill: url("streak"), ...part(frame, "streak") }), _jsx("path", { fill: url("sole"), ...part(frame, "sole") }), _jsx("path", { fill: "none", stroke: WHITE, strokeOpacity: "0.05", strokeWidth: "0.04", ...part(frame, "visorEdge") })] }), renderEye("L"), renderEye("R"), [0, 1, 2].map((i) => (_jsx("rect", { fill: eye, height: "0.024", rx: "0.012", width: "0.047", ...part(frame, `tick${i}`) }, i)))] })] }), _jsxs("g", { ...part(frame, "blockedBar"), children: [_jsx("rect", { fill: nadia ? "var(--nc-forma-avorio)" : pigment, height: "1.664", rx: "0.112", transform: "rotate(45)", width: "0.224", x: "-0.112", y: "-0.832" }), nadia ? null : (_jsx("rect", { fill: "var(--nc-forma-sabbia)", height: "1.664", opacity: "0.28", rx: "0.112", transform: "rotate(45)", width: "0.224", x: "-0.112", y: "-0.832" })), _jsx("rect", { fill: url("bar"), height: "1.664", rx: "0.112", transform: "rotate(45)", width: "0.224", x: "-0.112", y: "-0.832" })] })] }), _jsxs("g", { ...part(frame, "effectsFront"), children: [renderOrbitDots("Front"), [0, 1, 2].map((i) => (_jsxs("g", { ...part(frame, `thought${i}`), children: [_jsx("circle", { fill: pigment, r: (0.042 + i * 0.015).toFixed(3) }), _jsx("circle", { fill: url("bead"), r: (0.042 + i * 0.015).toFixed(3) })] }, i))), RIBBON_TILTS.map((tilt, index) => {
90
+ const ribbon = `var(${formaColorVariable[celebrationColors[shade][index]]})`;
91
+ return (_jsx("g", { transform: `rotate(${tilt})`, ...part(frame, `ribbon${index}Plane`), children: _jsxs("g", { ...part(frame, `ribbon${index}Sweep`), children: [_jsx("path", { fill: "none", stroke: ribbon, strokeLinecap: "round", strokeWidth: "0.024", ...part(frame, `ribbon${index}Arc`) }), _jsx("circle", { fill: ribbon, r: "0.03", ...part(frame, `ribbon${index}Head`) }), Array.from({ length: RIBBON_BEADS }, (_, i) => (_jsx("circle", { fill: ribbon, ...part(frame, `ribbon${index}Bead${i}`) }, i)))] }) }, index));
92
+ })] })] })] }) }));
93
+ }
94
+ export { FormaAvatar, };
95
+ export { defaultForm, formDetails, formaChoices, formaColorVariable, formaColors, formaForms, formaStates, } from "../internal/forma/model.js";
@@ -1,9 +1,3 @@
1
- export interface ConfirmActionGate {
2
- busy: boolean;
3
- }
4
- export declare function requestConfirmAction<T>(gate: ConfirmActionGate, value: T, setPending: (value: T | null) => void): void;
5
- export declare function cancelConfirmAction<T>(gate: ConfirmActionGate, setPending: (value: T | null) => void): void;
6
- export declare function runConfirmAction<T>(gate: ConfirmActionGate, pending: T | null, onConfirm: (value: T) => void | Promise<void>, setPending: (value: T | null) => void, setBusy: (busy: boolean) => void): Promise<void>;
7
1
  export declare function useConfirmAction<T>({ onConfirm, }: {
8
2
  onConfirm: (value: T) => void | Promise<void>;
9
3
  }): {
@@ -1 +1 @@
1
- {"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAID,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,CAAC,EACR,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,GACpC,IAAI,CAKN;AAID,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,IAAI,EAAE,iBAAiB,EACvB,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,GACpC,IAAI,CAKN;AAKD,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,CAAC,GAAG,IAAI,EACjB,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC7C,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,EACrC,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;8BAK2C,CAAC;;;;;;EAqB5C"}
1
+ {"version":3,"file":"use-confirm-action.d.ts","sourceRoot":"","sources":["../../src/hooks/use-confirm-action.ts"],"names":[],"mappings":"AAwDA,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAClC,SAAS,GACV,EAAE;IACD,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;8BAK2C,CAAC;;;;;;EAqB5C"}
@@ -1,25 +1,18 @@
1
1
  "use client";
2
2
  import * as React from "react";
3
- // Request stages a value for confirmation; ignored while a confirm is in
4
- // flight so the pending value cannot change under a running action.
5
- export function requestConfirmAction(gate, value, setPending) {
3
+ function requestConfirmAction(gate, value, setPending) {
6
4
  if (gate.busy) {
7
5
  return;
8
6
  }
9
7
  setPending(value);
10
8
  }
11
- // Cancel clears the pending value, but is a no-op while a confirm is in flight
12
- // so the dialog cannot be dismissed mid-action.
13
- export function cancelConfirmAction(gate, setPending) {
9
+ function cancelConfirmAction(gate, setPending) {
14
10
  if (gate.busy) {
15
11
  return;
16
12
  }
17
13
  setPending(null);
18
14
  }
19
- // Confirm marks busy, awaits `onConfirm`, and clears the pending value on
20
- // success. A thrown error is swallowed so the dialog stays open (the caller
21
- // surfaces the error inside `onConfirm`); busy always resets in `finally`.
22
- export async function runConfirmAction(gate, pending, onConfirm, setPending, setBusy) {
15
+ async function confirmOrKeepOpen(gate, pending, onConfirm, setPending, setBusy) {
23
16
  if (gate.busy || pending === null) {
24
17
  return;
25
18
  }
@@ -30,7 +23,7 @@ export async function runConfirmAction(gate, pending, onConfirm, setPending, set
30
23
  setPending(null);
31
24
  }
32
25
  catch {
33
- // Keep the pending value so the dialog stays open.
26
+ return;
34
27
  }
35
28
  finally {
36
29
  gate.busy = false;
@@ -47,7 +40,7 @@ export function useConfirmAction({ onConfirm, }) {
47
40
  const cancel = React.useCallback(() => {
48
41
  cancelConfirmAction(gateRef.current, setPending);
49
42
  }, []);
50
- const confirm = React.useCallback(() => runConfirmAction(gateRef.current, pending, onConfirm, setPending, setIsBusy), [pending, onConfirm]);
43
+ const confirm = React.useCallback(() => confirmOrKeepOpen(gateRef.current, pending, onConfirm, setPending, setIsBusy), [pending, onConfirm]);
51
44
  return {
52
45
  request,
53
46
  cancel,
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export * from "./components/field.js";
60
60
  export * from "./components/file-type-icon.js";
61
61
  export * from "./components/footer.js";
62
62
  export * from "./components/form.js";
63
+ export * from "./components/forma-avatar.js";
63
64
  export * from "./components/glyph-spinner.js";
64
65
  export * from "./components/glass-control.js";
65
66
  export * from "./components/handoff-banner.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -60,6 +60,7 @@ export * from "./components/field.js";
60
60
  export * from "./components/file-type-icon.js";
61
61
  export * from "./components/footer.js";
62
62
  export * from "./components/form.js";
63
+ export * from "./components/forma-avatar.js";
63
64
  export * from "./components/glyph-spinner.js";
64
65
  export * from "./components/glass-control.js";
65
66
  export * from "./components/handoff-banner.js";
@@ -0,0 +1,10 @@
1
+ import { type FormaMotion, type Pose } from "./motion.js";
2
+ export type PartAttributes = Record<string, string>;
3
+ export type Frame = Record<string, PartAttributes>;
4
+ export declare const SHELL_LAYERS: readonly ["tone", "form", "floor", "key", "rim"];
5
+ export declare const ORBIT_DOTS = 20;
6
+ export declare const RIBBON_BEADS = 8;
7
+ export declare function computeFrame(motion: FormaMotion, pose: Pose, now: number): Frame;
8
+ export declare const RIBBON_TILTS: (48 | -22 | -64)[];
9
+ export declare const ORBIT_TILTS: readonly [-0.36, 0.49];
10
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/frame.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAsB,KAAK,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,UAAU,CAAC;AAE3E,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAanD,eAAO,MAAM,YAAY,kDAAmD,CAAC;AAY7E,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,CAyGhF;AAOD,eAAO,MAAM,YAAY,oBAAuC,CAAC;AACjE,eAAO,MAAM,WAAW,wBAAyB,CAAC"}
@@ -0,0 +1,211 @@
1
+ import { TAU, bodies, closedPath, eyeProfiles, f3, project, shellPath, smooth, stripePath, superellipse, visorPoints, } from "./geometry.js";
2
+ import { HALLMARK_LONGITUDE, HALLMARK_SIZE, ITALY_BOX } from "./hallmark.js";
3
+ import { formDetails } from "./model.js";
4
+ import { easeInOut, easeOut } from "./motion.js";
5
+ const eyeShapes = new Map();
6
+ function eyeShape(form) {
7
+ let shape = eyeShapes.get(form);
8
+ if (!shape) {
9
+ const profile = eyeProfiles[form];
10
+ shape = closedPath(superellipse(profile.width, profile.height, profile.power, 32));
11
+ eyeShapes.set(form, shape);
12
+ }
13
+ return shape;
14
+ }
15
+ export const SHELL_LAYERS = ["tone", "form", "floor", "key", "rim"];
16
+ const VISOR_LAYERS = [
17
+ "visor",
18
+ "visorClip",
19
+ "bezel",
20
+ "bezelLight",
21
+ "visorShade",
22
+ "glass",
23
+ "streak",
24
+ "sole",
25
+ "visorEdge",
26
+ ];
27
+ export const ORBIT_DOTS = 20;
28
+ export const RIBBON_BEADS = 8;
29
+ export function computeFrame(motion, pose, now) {
30
+ const body = bodies[motion.form];
31
+ const nadia = motion.form === "nadia";
32
+ const t = (now - motion.entered) / 1000;
33
+ const morph = pose.morph < 0.00005 ? 0 : pose.morph > 0.99995 ? 1 : pose.morph;
34
+ const { yaw, pitch } = pose;
35
+ const frame = {};
36
+ const shell = shellPath(body, morph);
37
+ frame.shell = { d: shell };
38
+ frame.shellClip = { d: shell };
39
+ for (const layer of SHELL_LAYERS)
40
+ frame[layer] = { d: shell };
41
+ frame.tube = { d: shell, opacity: f3(morph) };
42
+ frame.hot = { opacity: f3(1 - morph * 0.6) };
43
+ frame.fade = { d: shell, opacity: f3(nadia ? 0 : morph * 0.28) };
44
+ frame.object = {
45
+ transform: `translate(0 ${f3(pose.lift)}) rotate(${f3((pose.roll * 180) / Math.PI)}) scale(${f3(1 / Math.sqrt(pose.stretch))} ${f3(pose.stretch)})`,
46
+ };
47
+ frame.blockedBar = { opacity: f3(morph > 0.42 ? smooth((morph - 0.42) / 0.4) : 0) };
48
+ if (nadia) {
49
+ frame.bandW = { d: stripePath(body, yaw, -0.36, 0.36) };
50
+ frame.bandR = { d: stripePath(body, yaw, 0.36, Math.PI) };
51
+ frame.bandSand = { d: frame.bandW.d, opacity: f3(morph * 0.28) };
52
+ }
53
+ const faceOpacity = 1 - smooth(morph / 0.52);
54
+ frame.face = { opacity: f3(faceOpacity), display: faceOpacity > 0.001 ? "" : "none" };
55
+ const visor = closedPath(visorPoints.map(([x, y]) => {
56
+ const p = project(body, x, y, yaw, pitch);
57
+ return [p.x, p.y];
58
+ }));
59
+ for (const layer of VISOR_LAYERS)
60
+ frame[layer] = { d: visor };
61
+ if (nadia) {
62
+ const hm = project(body, Math.sin(HALLMARK_LONGITUDE), -0.12, yaw, pitch);
63
+ const scale = HALLMARK_SIZE / ITALY_BOX;
64
+ frame.hallmark = {
65
+ display: hm.front && faceOpacity > 0.001 ? "" : "none",
66
+ transform: `translate(${f3(hm.x)} ${f3(hm.y)}) scale(${f3(hm.sx * scale)} ${f3(-hm.sy * scale)}) translate(${-ITALY_BOX / 2} ${-ITALY_BOX / 2})`,
67
+ };
68
+ }
69
+ const happiness = smooth(pose.done);
70
+ const pauseAmount = smooth((pose.waiting - 0.15) / 0.85);
71
+ const blinkPhase = ((t % 5.2) - 3.4) / 0.16;
72
+ const blink = !motion.still &&
73
+ motion.state !== "blocked" &&
74
+ motion.state !== "waiting" &&
75
+ blinkPhase > 0 &&
76
+ blinkPhase < 1
77
+ ? 1 - Math.sin(blinkPhase * Math.PI)
78
+ : 1;
79
+ const eyeOpen = Math.max(0.055, pose.open * blink);
80
+ const profile = eyeProfiles[motion.form];
81
+ const glow = formDetails[motion.form].eyes === "ink" ? 0 : profile.signature ? 0.95 : 0.65;
82
+ const shape = eyeShape(motion.form);
83
+ for (const [name, side] of [
84
+ ["L", -1],
85
+ ["R", 1],
86
+ ]) {
87
+ const x = side * ((1 - pauseAmount) * profile.spacing + pauseAmount * 0.125) + pose.gazeX;
88
+ const y = profile.y + pose.gazeY;
89
+ const p = project(body, x, y, yaw, pitch);
90
+ frame[`eye${name}`] = {
91
+ display: p.front ? "" : "none",
92
+ transform: `translate(${f3(p.x)} ${f3(p.y)}) scale(${f3(p.sx)} ${f3(p.sy)})`,
93
+ };
94
+ frame[`light${name}`] = {
95
+ d: shape,
96
+ transform: `scale(1 ${f3(eyeOpen)})`,
97
+ opacity: f3((1 - happiness) * (1 - pauseAmount)),
98
+ };
99
+ frame[`glint${name}`] = frame[`light${name}`];
100
+ frame[`halo${name}`] = {
101
+ rx: f3(profile.width * 2.8),
102
+ ry: f3(profile.height * (1.1 + 0.9 * eyeOpen)),
103
+ opacity: f3(glow * (1 - pauseAmount) * (1 - happiness)),
104
+ };
105
+ if (profile.signature)
106
+ frame[`flick${name}`] = {
107
+ transform: `scale(${side} ${f3(eyeOpen)})`,
108
+ opacity: f3((1 - happiness) * (1 - pauseAmount)),
109
+ };
110
+ frame[`happy${name}`] = {
111
+ opacity: f3(happiness * (1 - pauseAmount)),
112
+ transform: `rotate(${f3(side * pose.done * 1.7)})`,
113
+ };
114
+ frame[`pause${name}`] = { opacity: f3(pauseAmount) };
115
+ frame[`pauseHalo${name}`] = { opacity: f3(pauseAmount * 0.7) };
116
+ }
117
+ for (let i = 0; i < 3; i++) {
118
+ const pulse = motion.still ? 0.6 : 0.18 + (0.82 * (Math.sin(t * 5.6 - i * 1.25) + 1)) / 2;
119
+ const p = project(body, (i - 1) * 0.086, -0.266, yaw, pitch);
120
+ frame[`tick${i}`] = {
121
+ transform: `translate(${f3(p.x - 0.0235 * p.sx)} ${f3(p.y - 0.012)}) scale(${f3(p.sx)} 1)`,
122
+ opacity: f3(pose.working * pulse * 0.75 + pose.thinking * pulse * 0.35),
123
+ };
124
+ }
125
+ writeEffects(frame, motion, pose, now);
126
+ return frame;
127
+ }
128
+ const RIBBONS = [
129
+ { tilt: -22, aspect: 0.62, radius: 1.2, span: 2.85, phase: 0.2, direction: 1 },
130
+ { tilt: 48, aspect: 0.5, radius: 1.19, span: 2.5, phase: 2.1, direction: -1 },
131
+ { tilt: -64, aspect: 0.72, radius: 1.24, span: 2.3, phase: 4.0, direction: 1 },
132
+ ];
133
+ export const RIBBON_TILTS = RIBBONS.map((ribbon) => ribbon.tilt);
134
+ export const ORBIT_TILTS = [-0.36, 0.49];
135
+ function writeEffects(frame, motion, pose, now) {
136
+ const morph = pose.morph < 0.00005 ? 0 : pose.morph > 0.99995 ? 1 : pose.morph;
137
+ const clock = motion.still ? 0.6 : (now - motion.started) / 1000;
138
+ const celebrationAge = (now - motion.celebratedAt) / 1000;
139
+ const celebrating = !motion.still && celebrationAge > 0.14 && celebrationAge < 2.4;
140
+ const workAmount = pose.working * (1 - morph);
141
+ const thoughtAmount = pose.thinking * (1 - morph);
142
+ const ribbonOpacity = celebrating ? pose.done * (1 - morph) : 0;
143
+ const active = workAmount > 0.003 || thoughtAmount > 0.003 || ribbonOpacity > 0.003;
144
+ frame.effectsBack = { display: active ? "" : "none" };
145
+ frame.effectsFront = {
146
+ display: active ? "" : "none",
147
+ transform: `translate(0 ${f3(pose.lift * 0.45)})`,
148
+ };
149
+ ORBIT_TILTS.forEach((tilt, index) => {
150
+ for (let i = 0; i < ORBIT_DOTS; i++) {
151
+ const tail = i % 10;
152
+ const offset = i < 10 ? 0 : Math.PI;
153
+ const a = clock * (index === 0 ? 2.8 : -2.2) + index * 2.2 + offset - tail * 0.065;
154
+ const x = 1.2 * Math.cos(a);
155
+ const y = 0.49 * Math.sin(a);
156
+ const front = Math.sin(a) > 0;
157
+ const dot = {
158
+ cx: f3(x * Math.cos(tilt) - y * Math.sin(tilt)),
159
+ cy: f3(x * Math.sin(tilt) + y * Math.cos(tilt) - 0.04),
160
+ opacity: f3(workAmount * (tail === 0 ? 0.94 : 0.47 * (1 - tail / 10))),
161
+ };
162
+ frame[`orbit${index}Back${i}`] = { ...dot, display: front ? "none" : "" };
163
+ frame[`orbit${index}Front${i}`] = { ...dot, display: front ? "" : "none" };
164
+ }
165
+ });
166
+ for (let i = 0; i < 3; i++) {
167
+ const pulse = motion.still ? 0.72 : 0.23 + (0.77 * (Math.sin(clock * 2.4 - i * 0.9) + 1)) / 2;
168
+ frame[`thought${i}`] = {
169
+ transform: `translate(${f3(0.68 + i * 0.23)} ${f3(0.79 + i * 0.13 + (motion.still ? 0 : 0.025 * Math.sin(clock * 2.4 - i * 0.9)))})`,
170
+ opacity: f3(thoughtAmount * pulse),
171
+ };
172
+ }
173
+ RIBBONS.forEach((ribbon, index) => {
174
+ const name = `ribbon${index}`;
175
+ if (ribbonOpacity <= 0.003) {
176
+ frame[`${name}Plane`] = { opacity: "0", display: "none" };
177
+ return;
178
+ }
179
+ const age = celebrationAge - 0.14 - index * 0.06;
180
+ const reveal = easeOut(age / 0.46);
181
+ const dissolve = easeInOut((age - 1.1) / 0.95);
182
+ const fade = easeOut(age / 0.2) * (1 - easeOut((age - 1.76) / 0.44)) * ribbonOpacity;
183
+ const a0 = dissolve * ribbon.span;
184
+ const a1 = Math.max(a0, reveal * ribbon.span);
185
+ const points = [];
186
+ for (let i = 0; i <= 24; i++) {
187
+ const a = a0 + ((a1 - a0) * i) / 24;
188
+ points.push(`${i ? "L" : "M"}${f3(Math.cos(a) * ribbon.radius)} ${f3(Math.sin(a) * ribbon.radius * ribbon.aspect)}`);
189
+ }
190
+ frame[`${name}Plane`] = { opacity: f3(fade * 0.88), display: "" };
191
+ frame[`${name}Sweep`] = {
192
+ transform: `rotate(${f3(((ribbon.phase + ribbon.direction * TAU * easeInOut(Math.min(1, Math.max(0, age / 2.12)))) * 180) / Math.PI)})`,
193
+ };
194
+ frame[`${name}Arc`] = { d: a1 > a0 + 0.01 ? points.join("") : "M0 0" };
195
+ frame[`${name}Head`] = {
196
+ cx: f3(Math.cos(a1) * ribbon.radius),
197
+ cy: f3(Math.sin(a1) * ribbon.radius * ribbon.aspect),
198
+ };
199
+ for (let i = 0; i < RIBBON_BEADS; i++) {
200
+ const u = i / 7;
201
+ const angle = a0 - u * (0.95 + 0.48 * dissolve);
202
+ const radial = ribbon.radius + ((i % 3) - 1) * (0.014 + 0.034 * u) + dissolve * u * 0.06;
203
+ frame[`${name}Bead${i}`] = {
204
+ cx: f3(Math.cos(angle) * radial),
205
+ cy: f3(Math.sin(angle) * radial * ribbon.aspect),
206
+ r: f3(0.006 + 0.012 * (1 - u)),
207
+ opacity: f3(dissolve > 0.01 ? 0.78 : 0),
208
+ };
209
+ }
210
+ });
211
+ }
@@ -0,0 +1,41 @@
1
+ import type { FormaForm } from "./model.js";
2
+ export declare const TAU: number;
3
+ export declare const clamp: (value: number, low: number, high: number) => number;
4
+ export declare const mix: (a: number, b: number, t: number) => number;
5
+ export declare const smooth: (t: number) => number;
6
+ export declare const f3: (value: number) => string;
7
+ export type Point = readonly [number, number];
8
+ export interface Body {
9
+ height: number;
10
+ width: number;
11
+ hw: (y: number) => number;
12
+ outline: readonly Point[];
13
+ core: {
14
+ cy: number;
15
+ r: number;
16
+ };
17
+ }
18
+ export declare const bodies: Record<FormaForm, Body>;
19
+ export interface EyeProfile {
20
+ power: number;
21
+ width: number;
22
+ height: number;
23
+ spacing: number;
24
+ y: number;
25
+ signature?: boolean;
26
+ }
27
+ export declare const eyeProfiles: Record<FormaForm, EyeProfile>;
28
+ export declare function superellipse(hw: number, hh: number, power: number, n: number): Point[];
29
+ export declare const visorPoints: readonly Point[];
30
+ export declare function closedPath(pts: readonly Point[]): string;
31
+ export interface Projected {
32
+ x: number;
33
+ y: number;
34
+ sx: number;
35
+ sy: number;
36
+ front: boolean;
37
+ }
38
+ export declare function project(body: Body, xs: number, ys: number, yaw: number, pitch: number): Projected;
39
+ export declare function shellPath(body: Body, morph: number): string;
40
+ export declare function stripePath(body: Body, yaw: number, a: number, b: number): string;
41
+ //# sourceMappingURL=geometry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geometry.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/geometry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,eAAO,MAAM,GAAG,QAAc,CAAC;AAE/B,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,KAAG,MAC3B,CAAC;AACvC,eAAO,MAAM,GAAG,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAAyB,CAAC;AAChF,eAAO,MAAM,MAAM,GAAI,GAAG,MAAM,KAAG,MAGlC,CAAC;AAGF,eAAO,MAAM,EAAE,GAAI,OAAO,MAAM,KAAG,MAAsD,CAAC;AAE1F,MAAM,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9C,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1B,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IAC1B,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjC;AAuGD,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,IAAI,CAgD1C,CAAC;AAIF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,UAAU,CASrD,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,KAAK,EAAE,CAOtF;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,KAAK,EAKD,CAAC;AAExC,wBAAgB,UAAU,CAAC,GAAG,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAYxD;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAkBjG;AAgCD,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU3D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAWhF"}