@divinci-ai/robot-avatar 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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/blend.d.ts +8 -0
  4. package/dist/blend.d.ts.map +1 -0
  5. package/dist/blend.js +17 -0
  6. package/dist/blend.js.map +1 -0
  7. package/dist/index.d.ts +14 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +13 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/render-gate.d.ts +32 -0
  12. package/dist/render-gate.d.ts.map +1 -0
  13. package/dist/render-gate.js +72 -0
  14. package/dist/render-gate.js.map +1 -0
  15. package/dist/scene/AnimatedHeart.d.ts +18 -0
  16. package/dist/scene/AnimatedHeart.d.ts.map +1 -0
  17. package/dist/scene/AnimatedHeart.js +63 -0
  18. package/dist/scene/AnimatedHeart.js.map +1 -0
  19. package/dist/scene/LogoRobot.d.ts +24 -0
  20. package/dist/scene/LogoRobot.d.ts.map +1 -0
  21. package/dist/scene/LogoRobot.js +285 -0
  22. package/dist/scene/LogoRobot.js.map +1 -0
  23. package/dist/scene/RobotScene.d.ts +43 -0
  24. package/dist/scene/RobotScene.d.ts.map +1 -0
  25. package/dist/scene/RobotScene.js +43 -0
  26. package/dist/scene/RobotScene.js.map +1 -0
  27. package/dist/scene/index.d.ts +16 -0
  28. package/dist/scene/index.d.ts.map +1 -0
  29. package/dist/scene/index.js +15 -0
  30. package/dist/scene/index.js.map +1 -0
  31. package/dist/scene/useGroupMotion.d.ts +12 -0
  32. package/dist/scene/useGroupMotion.d.ts.map +1 -0
  33. package/dist/scene/useGroupMotion.js +47 -0
  34. package/dist/scene/useGroupMotion.js.map +1 -0
  35. package/dist/scene/useRobotSignals.d.ts +28 -0
  36. package/dist/scene/useRobotSignals.d.ts.map +1 -0
  37. package/dist/scene/useRobotSignals.js +101 -0
  38. package/dist/scene/useRobotSignals.js.map +1 -0
  39. package/dist/types.d.ts +15 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +8 -0
  42. package/dist/types.js.map +1 -0
  43. package/package.json +46 -0
  44. package/src/blend.ts +16 -0
  45. package/src/index.ts +14 -0
  46. package/src/render-gate.ts +102 -0
  47. package/src/scene/AnimatedHeart.tsx +95 -0
  48. package/src/scene/LogoRobot.tsx +361 -0
  49. package/src/scene/RobotScene.tsx +102 -0
  50. package/src/scene/index.ts +16 -0
  51. package/src/scene/useGroupMotion.ts +61 -0
  52. package/src/scene/useRobotSignals.ts +126 -0
  53. package/src/types.ts +16 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Divinci AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @divinci-ai/robot-avatar
2
+
3
+ The Divinci robot mascot — the procedural, rigged react-three-fiber avatar
4
+ shared by every surface that shows him:
5
+
6
+ | Surface | Wrapper |
7
+ |---|---|
8
+ | SDK docs hero (sdk.divinci.ai) | `workspace/sdk/docs/src/components/RobotHero/RobotHero.tsx` |
9
+ | Web app + Divinci Agent panel | `workspace/clients/web/src/components/RobotAvatar/RobotAvatar.tsx` |
10
+ | Chrome extension (iframe) | via the web app / a dedicated robot page (planned) |
11
+
12
+ ## Entry points
13
+
14
+ - **`@divinci-ai/robot-avatar`** (light, no three.js): `AvatarState`,
15
+ `RobotAvatarColors`, `blendHex`, `useRenderGate`. Safe for main bundles.
16
+ - **`@divinci-ai/robot-avatar/scene`** (heavy): `RobotScene`, `LogoRobot`,
17
+ `AnimatedHeart`, `useRobotSignals`, `useGroupMotion`. **Always lazy-import**
18
+ so three.js stays in its own chunk:
19
+
20
+ ```tsx
21
+ import { lazy } from "react";
22
+ const RobotScene = lazy(() =>
23
+ import("@divinci-ai/robot-avatar/scene").then((m) => ({ default: m.RobotScene }))
24
+ );
25
+ ```
26
+
27
+ ## Built-in hardening (every consumer gets these)
28
+
29
+ - `powerPreference: "low-power"` — never spins up the discrete GPU.
30
+ - `failIfMajorPerformanceCaveat: true` — refuses software WebGL (SwiftShader)
31
+ instead of burning CPU; your error boundary shows the static fallback.
32
+ - `paused` prop → R3F `frameloop: "never"`. Pair with `useRenderGate` (pauses
33
+ when scrolled off-screen, tab hidden, or user idle).
34
+ - `onContextLost` callback — `webglcontextlost` does not throw through React;
35
+ listen here and swap to your static fallback.
36
+ - `prefers-reduced-motion` honored live: autonomous animation freezes to a
37
+ rest pose; only the gentle cursor-driven gaze lean remains.
38
+ - `dpr` capped at `[1, 1.5]` by default (the mascot renders small).
39
+
40
+ ## Consumer responsibilities
41
+
42
+ Each wrapper owns: color resolution (theme vars vs brand constants), the
43
+ static fallback image (Suspense placeholder + WebGL-failure fallback +
44
+ context-loss swap), and any surface-specific gaze target (`speakerTarget`).
45
+
46
+ ## Version skew
47
+
48
+ Compiled against the lowest consumer versions (react 18 / three 0.169 /
49
+ r3f 8 / drei 9); peer ranges span through the docs site's react 19 / three
50
+ 0.185 / r3f 9 / drei 10. Only skew-stable APIs are used (`Canvas`, `useFrame`,
51
+ `Environment`, `Lightformer`, core three geometry/materials) — check both
52
+ consumers when touching imports.
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tiny hex-color blend used by consumers to derive the robot's palette from a
3
+ * theme (web client) or brand constants (docs hero). Lives in the light entry
4
+ * so wrappers can compute colors without touching three.js.
5
+ */
6
+ /** Blend two hex colors; t=0 → a, t=1 → b. */
7
+ export declare function blendHex(a: string, b: string, t: number): string;
8
+ //# sourceMappingURL=blend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blend.d.ts","sourceRoot":"","sources":["../src/blend.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhE"}
package/dist/blend.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tiny hex-color blend used by consumers to derive the robot's palette from a
3
+ * theme (web client) or brand constants (docs hero). Lives in the light entry
4
+ * so wrappers can compute colors without touching three.js.
5
+ */
6
+ /** Blend two hex colors; t=0 → a, t=1 → b. */
7
+ export function blendHex(a, b, t) {
8
+ const m = a.replace("#", "").trim();
9
+ const n = b.replace("#", "").trim();
10
+ if (m.length < 6 || n.length < 6)
11
+ return a;
12
+ const ra = [parseInt(m.slice(0, 2), 16), parseInt(m.slice(2, 4), 16), parseInt(m.slice(4, 6), 16)];
13
+ const rb = [parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16)];
14
+ const mix = ra.map((v, i) => Math.round(v + (rb[i] - v) * t));
15
+ return "#" + mix.map((v) => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
16
+ }
17
+ //# sourceMappingURL=blend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blend.js","sourceRoot":"","sources":["../src/blend.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS;IACtD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACnG,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACnG,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9D,OAAO,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpG,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @divinci-ai/robot-avatar — light entry (NO three.js).
3
+ *
4
+ * Import this from main bundles; lazy-import "@divinci-ai/robot-avatar/scene"
5
+ * for the WebGL chunk:
6
+ *
7
+ * const RobotScene = lazy(() =>
8
+ * import("@divinci-ai/robot-avatar/scene").then((m) => ({ default: m.RobotScene }))
9
+ * );
10
+ */
11
+ export type { AvatarState, RobotAvatarColors } from "./types.js";
12
+ export { blendHex } from "./blend.js";
13
+ export { useRenderGate, type RenderGate, type RenderGateOptions } from "./render-gate.js";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @divinci-ai/robot-avatar — light entry (NO three.js).
3
+ *
4
+ * Import this from main bundles; lazy-import "@divinci-ai/robot-avatar/scene"
5
+ * for the WebGL chunk:
6
+ *
7
+ * const RobotScene = lazy(() =>
8
+ * import("@divinci-ai/robot-avatar/scene").then((m) => ({ default: m.RobotScene }))
9
+ * );
10
+ */
11
+ export { blendHex } from "./blend.js";
12
+ export { useRenderGate } from "./render-gate.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAA2C,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * useRenderGate — shared "should the robot render right now?" gate.
3
+ *
4
+ * Returns `active: false` when any of these hold:
5
+ * - the container is scrolled out of the viewport (IntersectionObserver)
6
+ * - the tab is hidden (visibilitychange)
7
+ * - the user has been idle for `idleMs` (optional; pass 0/undefined to skip)
8
+ *
9
+ * Consumers feed `active` into RobotScene's `paused` prop, which sets the R3F
10
+ * frameloop to "never" — the WebGL context stays alive but stops burning
11
+ * GPU/battery. This generalizes the SDK docs hero's gate controller so the web
12
+ * app / agent panel / extension iframe get the same behavior for free.
13
+ *
14
+ * Lives in the light entry: React-only, no three.js.
15
+ */
16
+ export interface RenderGateOptions {
17
+ /** Pause after this many ms without pointer/key/scroll activity. 0 = never. */
18
+ idleMs?: number;
19
+ /** IntersectionObserver threshold for "visible" (default 0.05). */
20
+ threshold?: number;
21
+ }
22
+ export interface RenderGate<T extends HTMLElement> {
23
+ /**
24
+ * Attach to the element wrapping the canvas. A callback ref (not a ref
25
+ * object) so the type is identical under react 18 and 19 typings.
26
+ */
27
+ ref: (node: T | null) => void;
28
+ /** True when the robot should animate. */
29
+ active: boolean;
30
+ }
31
+ export declare function useRenderGate<T extends HTMLElement = HTMLDivElement>(options?: RenderGateOptions): RenderGate<T>;
32
+ //# sourceMappingURL=render-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-gate.d.ts","sourceRoot":"","sources":["../src/render-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,WAAW,iBAAiB;IAChC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,WAAW;IAC/C;;;OAGG;IACH,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9B,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EAClE,OAAO,GAAE,iBAAsB,GAC9B,UAAU,CAAC,CAAC,CAAC,CAgEf"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * useRenderGate — shared "should the robot render right now?" gate.
3
+ *
4
+ * Returns `active: false` when any of these hold:
5
+ * - the container is scrolled out of the viewport (IntersectionObserver)
6
+ * - the tab is hidden (visibilitychange)
7
+ * - the user has been idle for `idleMs` (optional; pass 0/undefined to skip)
8
+ *
9
+ * Consumers feed `active` into RobotScene's `paused` prop, which sets the R3F
10
+ * frameloop to "never" — the WebGL context stays alive but stops burning
11
+ * GPU/battery. This generalizes the SDK docs hero's gate controller so the web
12
+ * app / agent panel / extension iframe get the same behavior for free.
13
+ *
14
+ * Lives in the light entry: React-only, no three.js.
15
+ */
16
+ import { useEffect, useState } from "react";
17
+ export function useRenderGate(options = {}) {
18
+ const { idleMs = 0, threshold = 0.05 } = options;
19
+ const [node, setNode] = useState(null);
20
+ const [active, setActive] = useState(true);
21
+ useEffect(() => {
22
+ let visible = true;
23
+ let shown = !document.hidden;
24
+ let idle = false;
25
+ let idleTimer;
26
+ const recompute = () => {
27
+ setActive(visible && shown && !idle);
28
+ };
29
+ const bumpIdle = () => {
30
+ if (!idleMs)
31
+ return;
32
+ if (idle) {
33
+ idle = false;
34
+ recompute();
35
+ }
36
+ window.clearTimeout(idleTimer);
37
+ idleTimer = window.setTimeout(() => {
38
+ idle = true;
39
+ recompute();
40
+ }, idleMs);
41
+ };
42
+ let io;
43
+ if (node && typeof IntersectionObserver !== "undefined") {
44
+ io = new IntersectionObserver((entries) => {
45
+ visible = entries.some((e) => e.isIntersecting);
46
+ recompute();
47
+ }, { threshold });
48
+ io.observe(node);
49
+ }
50
+ const onVisibility = () => {
51
+ shown = !document.hidden;
52
+ recompute();
53
+ };
54
+ document.addEventListener("visibilitychange", onVisibility);
55
+ const activityEvents = ["pointermove", "pointerdown", "keydown", "wheel", "touchstart", "scroll"];
56
+ if (idleMs) {
57
+ activityEvents.forEach((ev) => window.addEventListener(ev, bumpIdle, { passive: true }));
58
+ bumpIdle();
59
+ }
60
+ recompute();
61
+ return () => {
62
+ io?.disconnect();
63
+ document.removeEventListener("visibilitychange", onVisibility);
64
+ if (idleMs) {
65
+ activityEvents.forEach((ev) => window.removeEventListener(ev, bumpIdle));
66
+ window.clearTimeout(idleTimer);
67
+ }
68
+ };
69
+ }, [node, idleMs, threshold]);
70
+ return { ref: setNode, active };
71
+ }
72
+ //# sourceMappingURL=render-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-gate.js","sourceRoot":"","sources":["../src/render-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * AnimatedHeart — the Divinci heart rendered in code (not baked into the GLB)
3
+ * so it can be alive: it slowly phases through brand colors, is slightly
4
+ * transparent, and gently pulses.
5
+ */
6
+ import React from "react";
7
+ import type { MutableRefObject } from "react";
8
+ import type { RobotSignals } from "./useRobotSignals.js";
9
+ export interface AnimatedHeartProps {
10
+ position?: [number, number, number];
11
+ scale?: number;
12
+ cycleSeconds?: number;
13
+ opacity?: number;
14
+ stretchY?: number;
15
+ sig?: MutableRefObject<RobotSignals>;
16
+ }
17
+ export declare function AnimatedHeart({ position, scale, cycleSeconds, opacity, stretchY, sig, }: AnimatedHeartProps): React.ReactElement;
18
+ //# sourceMappingURL=AnimatedHeart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedHeart.d.ts","sourceRoot":"","sources":["../../src/scene/AnimatedHeart.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAA0B,MAAM,OAAO,CAAC;AAG/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CACtC;AAwBD,wBAAgB,aAAa,CAAC,EAC5B,QAAyB,EACzB,KAAW,EACX,YAAgB,EAChB,OAAa,EACb,QAAY,EACZ,GAAG,GACJ,EAAE,kBAAkB,GAAG,KAAK,CAAC,YAAY,CA4CzC"}
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * AnimatedHeart — the Divinci heart rendered in code (not baked into the GLB)
4
+ * so it can be alive: it slowly phases through brand colors, is slightly
5
+ * transparent, and gently pulses.
6
+ */
7
+ import { useMemo, useRef } from "react";
8
+ import { useFrame } from "@react-three/fiber";
9
+ import * as THREE from "three";
10
+ function makeHeartGeometry() {
11
+ const s = new THREE.Shape();
12
+ s.moveTo(0, 0.3);
13
+ s.bezierCurveTo(0, 0.3, -0.2, 0, -0.5, 0);
14
+ s.bezierCurveTo(-0.95, 0, -0.95, 0.5, -0.95, 0.5);
15
+ s.bezierCurveTo(-0.95, 0.78, -0.6, 1.05, 0, 1.35);
16
+ s.bezierCurveTo(0.6, 1.05, 0.95, 0.78, 0.95, 0.5);
17
+ s.bezierCurveTo(0.95, 0.5, 0.95, 0, 0.5, 0);
18
+ s.bezierCurveTo(0.2, 0, 0, 0.3, 0, 0.3);
19
+ const geo = new THREE.ExtrudeGeometry(s, {
20
+ depth: 0.55,
21
+ bevelEnabled: true,
22
+ bevelThickness: 0.14,
23
+ bevelSize: 0.14,
24
+ bevelSegments: 5,
25
+ curveSegments: 28,
26
+ });
27
+ geo.center();
28
+ geo.rotateZ(Math.PI);
29
+ return geo;
30
+ }
31
+ export function AnimatedHeart({ position = [0, 0.2, 0.45], scale = 0.5, cycleSeconds = 6, opacity = 0.8, stretchY = 1, sig, }) {
32
+ const geometry = useMemo(makeHeartGeometry, []);
33
+ const mesh = useRef(null);
34
+ const material = useRef(null);
35
+ const color = useMemo(() => new THREE.Color(), []);
36
+ useFrame(() => {
37
+ const t = performance.now() / 1000;
38
+ const s = sig?.current;
39
+ const reduced = s?.reduced ?? false;
40
+ // Reduced motion: hold a static mid-cycle color, no hue drift / pulse / bob.
41
+ const phase = reduced ? 0 : Math.sin((t * 2 * Math.PI) / cycleSeconds);
42
+ const hue = (0.89 + 0.11 * phase) % 1;
43
+ color.setHSL(hue, 0.7, 0.56);
44
+ if (material.current) {
45
+ material.current.color.copy(color);
46
+ material.current.emissive.copy(color);
47
+ }
48
+ if (mesh.current) {
49
+ if (reduced) {
50
+ mesh.current.scale.set(scale, scale * stretchY, scale);
51
+ mesh.current.position.y = position[1];
52
+ }
53
+ else {
54
+ const reacting = s ? t < s.reactUntil : false;
55
+ const pulse = 1 + (reacting ? 0.2 : 0.05) * Math.sin(t * (reacting ? 9 : 2.4));
56
+ mesh.current.scale.set(scale * pulse, scale * stretchY * pulse, scale * pulse);
57
+ mesh.current.position.y = position[1] + (reacting ? Math.abs(Math.sin(t * 9)) * 0.06 : 0);
58
+ }
59
+ }
60
+ });
61
+ return (_jsx("mesh", { ref: mesh, geometry: geometry, position: position, scale: scale, children: _jsx("meshStandardMaterial", { ref: material, transparent: true, opacity: opacity, roughness: 0.2, metalness: 0.0, emissiveIntensity: 0.35, depthWrite: false }) }));
62
+ }
63
+ //# sourceMappingURL=AnimatedHeart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedHeart.js","sourceRoot":"","sources":["../../src/scene/AnimatedHeart.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * LogoRobot — the Divinci mascot, hand-built in code and fully rigged.
3
+ *
4
+ * Structure mirrors the flat logo (open frame, NO torso):
5
+ * - rounded-corner head with two dark eyes + two ball-tipped antennae
6
+ * - two "4"-shaped arm/leg forms (mirrored): diagonal + crossbar + vertical
7
+ * stem, one sphere hand; a foot floats below each
8
+ * - the AnimatedHeart floating in the chest center
9
+ *
10
+ * Every part is rigged and animates independently off shared interaction
11
+ * signals (page-wide gaze, keystroke reactions, idle fidgets).
12
+ */
13
+ import React from "react";
14
+ import { type SpeakerTarget } from "./useRobotSignals.js";
15
+ import type { AvatarState, RobotAvatarColors } from "../types.js";
16
+ export interface LogoRobotProps {
17
+ state: AvatarState;
18
+ colors: RobotAvatarColors;
19
+ autoRotate?: boolean;
20
+ /** Optional extra gaze target (see useRobotSignals). */
21
+ speakerTarget?: SpeakerTarget;
22
+ }
23
+ export declare function LogoRobot({ state, colors, autoRotate, speakerTarget }: LogoRobotProps): React.ReactElement;
24
+ //# sourceMappingURL=LogoRobot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogoRobot.d.ts","sourceRoot":"","sources":["../../src/scene/LogoRobot.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAA0B,MAAM,OAAO,CAAC;AAI/C,OAAO,EAAqD,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE7G,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA6PlE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wDAAwD;IACxD,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAkB,EAAE,aAAa,EAAE,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CAgFlH"}