Skip to content

Thinking orbs

Three WebGL orbs for AI interfaces: Diffusion, DNA and Gemini. They think and listen, and the Gemini orb moves with your voice.

Uses
WebGL

Code

"use client";

import * as React from "react";

import { cn } from "@manikrana/ui/lib/utils";
import { orbFragment } from "./orb-renderer";
import { PALETTES, type OrbPalette } from "./palettes";
import { useOrb, type OrbVariant } from "./use-orb";

export type DiffusionOrbState = "thinking" | "listening";

export type DiffusionOrbProps = Omit<React.ComponentProps<"span">, "children"> & {
  state?: DiffusionOrbState;
  variant?: OrbVariant;
  palette?: OrbPalette;
  size?: number;
  speed?: number;
  brilliance?: number;
  paused?: boolean;
};

// Adapted from thinking-orbs (MIT; see THINKING-ORBS-LICENSE).
const FRAGMENT = orbFragment(`
uniform float compact;
uniform vec3 midColor;
uniform vec3 lightColor;
uniform float projectionTilt;
uniform float yaw;
uniform float rotation;
uniform float waveAmplitude;
uniform float waveCount;
uniform float secondaryWave;
uniform float phaseSkew;
uniform float bandOffset;
uniform float innerDiffusion;
uniform float outerDiffusion;
uniform float diffusionPower;
uniform float rearOpacity;
uniform float exposure;
uniform float highlight;

vec4 lightField(vec3 p, float back) {
  p.xz = rotate(yaw * 0.017453293) * p.xz;
  p.xy = rotate(rotation * 0.017453293) * p.xy;
  float tilt = projectionTilt * 0.017453293 + search * 0.22 * sin(time * 0.4) * activity;
  p.yz = rotate(tilt) * p.yz;
  float a = atan(p.y, p.x);
  float offset = p.z / max(0.08, length(p.xy));
  float t = time * 1.45;
  float wave = (0.16 * sin(a * waveCount - t * 1.7 + offset * phaseSkew)
    + 0.16 * secondaryWave * sin(a * (waveCount + 2.0) + t * 1.1)) * activity;
  wave *= waveAmplitude * (1.0 - focus * 0.35);
  float c = (offset - wave - bandOffset) / (1.0 + compact * 0.16);
  float width = c < 0.0 ? outerDiffusion : innerDiffusion;
  float density = exp(-pow(abs(c) / width, diffusionPower) * 1.4);
  vec3 color = mix(midColor, lightColor, pow(density, 2.0) * highlight);
  float travelingLight = 0.94 + 0.06 * sin(a - t * 0.48) * activity;
  float alpha = density * travelingLight * mix(1.0, rearOpacity, back);
  color *= exposure * mix(0.85, 1.12, brilliance);
  return vec4(color, alpha);
}

vec4 paint(vec2 uv, float rr, float edge) {
  float depth = sqrt(1.0 - rr);
  vec4 front = lightField(vec3(uv, depth), 0.0);
  vec4 back = lightField(vec3(uv, -depth), 1.0);
  float alpha = front.a + back.a * (1.0 - front.a);
  vec3 color = (front.rgb * front.a + back.rgb * back.a * (1.0 - front.a)) / max(alpha, 0.001);
  return vec4(color, alpha * edge);
}
`);

const DIFFUSION_TUNING = {
  projectionTilt: 49,
  yaw: 0,
  rotation: -23,
  waveAmplitude: 1,
  waveCount: 3,
  secondaryWave: 0.44,
  phaseSkew: 2.93,
  bandOffset: 0,
  innerDiffusion: 0.64,
  outerDiffusion: 0.64,
  diffusionPower: 2,
  rearOpacity: 0.1,
  exposure: 1.2,
  highlight: 0.22,
};

const STATE_TRANSITION = 0.5;
const COMPACT_SIZE = 24;

function DiffusionOrb({
  state = "thinking",
  variant = "weave",
  palette = "opal",
  size = 64,
  speed = 1,
  brilliance = 0.65,
  paused = false,
  className,
  style,
  ...props
}: DiffusionOrbProps) {
  const listening = React.useRef(state === "listening" ? 1 : 0);

  const { canvasRef, wake } = useOrb({
    fragment: FRAGMENT,
    size,
    speed,
    variant,
    brilliance,
    paused,
    step: ({ dt, motionReduced }) => {
      const target = state === "listening" ? 1 : 0;
      const blend = motionReduced ? 1 : 1 - Math.exp((-dt * 7) / STATE_TRANSITION);
      listening.current += (target - listening.current) * blend;
      if (Math.abs(target - listening.current) < 0.00001) listening.current = target;
    },
    uniforms: ({ search, focus }) => {
      const morph = listening.current;
      const settle = (value: number) => (morph === 1 ? 0 : value * (1 - morph));
      const [, midColor, lightColor] = PALETTES[palette];
      return {
        ...DIFFUSION_TUNING,
        projectionTilt: settle(DIFFUSION_TUNING.projectionTilt),
        yaw: settle(DIFFUSION_TUNING.yaw),
        rotation: settle(DIFFUSION_TUNING.rotation),
        secondaryWave: settle(DIFFUSION_TUNING.secondaryWave),
        search: search * (1 - morph),
        focus: focus * (1 - morph),
        compact: size <= COMPACT_SIZE ? 1 : 0,
        midColor,
        lightColor,
      };
    },
  });

  React.useEffect(() => {
    wake();
  }, [wake, state, palette]);

  return (
    <span
      {...props}
      data-slot="diffusion-orb"
      data-state={state}
      data-variant={variant}
      data-palette={palette}
      aria-hidden="true"
      className={cn("relative inline-flex size-(--orb-size) shrink-0 align-middle", className)}
      style={{ "--orb-size": `${size}px`, ...style } as React.CSSProperties}
    >
      <canvas ref={canvasRef} className="relative block size-full" aria-hidden="true" />
    </span>
  );
}

export { DiffusionOrb };