jacquardSnapshot

← snapshot

1729 bytes
"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>
  );
}