"use client";
import { useCallback, useState } from "react";
import { JackieOrbPoints } from "./jackie-orb-points";
import { JackieOrbSvg, type OrbState, type OrbSubject } from "./jackie-orb-svg";
export type { OrbState, OrbSubject };
/**
* Jackie's face.
*
* Prefers the Three.js point cloud; falls back to the flat SVG one if WebGL is
* unavailable, Three fails to load, or the GPU drops the context mid-session.
* Both draw the same taxonomy — the subject names the figure, the state names
* the colour — so a fallback loses depth, not meaning.
*
* The raymarched figure in `jackie-orb-gl.tsx` is the other candidate and is
* kept working; `/lab` compares all of them side by side.
*/
export function JackieOrb({
state,
subject = "lobby",
level = 0,
size = 132,
}: {
state: OrbState;
subject?: OrbSubject;
level?: number;
size?: number;
}) {
const [flat, setFlat] = useState(false);
const fail = useCallback(() => setFlat(true), []);
if (flat) {
return <JackieOrbSvg state={state} subject={subject} level={level} size={size} />;
}
return (
<div
className="jac-orb jac-orb--gl"
data-state={state}
data-subject={subject}
style={
{
width: size,
height: size,
"--orb-thread":
state === "listening" ? "var(--jac-human)" : "var(--jac-agent)",
"--orb-swell": String(
state === "speaking" || state === "listening" ? 1 + level * 0.04 : 1,
),
} as React.CSSProperties
}
aria-hidden="true"
>
<JackieOrbPoints
state={state}
subject={subject}
level={level}
size={size}
onFail={fail}
/>
</div>
);
}