{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hehku",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "motion",
    "ogl",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://usva.build/r/atmospheres-core.json"
  ],
  "files": [
    {
      "path": "filament-curve.ts",
      "target": "components/ui/filament-curve.ts",
      "type": "registry:ui",
      "content": "/**\n * One closed torus knot, sampled into a chain of capsules. Every term is\n * periodic in an integer multiple of the curve parameter, so the last knot\n * lands exactly on the first and the filament has no ends: one continuous\n * ribbon that passes behind and in front of itself.\n */\n\nconst TAU = Math.PI * 2;\n\nexport interface FilamentParams {\n  /** Capsules in the chain. Knots emitted is segments + 1. */\n  segments: number;\n  /** Major and minor radius of the torus the knot winds around. */\n  radius: [number, number];\n  /** Windings [about the axis, through the hole]. Keep them coprime, or the\n   * knot degenerates into one circle traced several times. */\n  winding: [number, number];\n  /** Anisotropic scale of the finished figure. */\n  scale: [number, number, number];\n  /** Slow wander of the curve away from the pure knot. */\n  drift: number;\n  /** Rate of the wander, and of the roll of the strand around its tube. */\n  driftRate: number;\n  /** Revolutions per second about the vertical. */\n  spin: number;\n  /** Fixed tilt of the whole figure, radians. */\n  tilt: number;\n  /** Radius of the emitting core, world units. */\n  thickness: number;\n  /** Falloff of exp(-glow * d^2). Larger is thinner and colder. */\n  glow: number;\n}\n\nexport const FILAMENT_DEFAULTS: FilamentParams = {\n  segments: 96,\n  radius: [2.1, 1.05],\n  winding: [2, 3],\n  scale: [1.85, 1.12, 1],\n  drift: 0.4,\n  driftRate: 0.06,\n  spin: 0.01,\n  tilt: 0.6,\n  thickness: 0.16,\n  glow: 26,\n};\n\nexport function resolveFilamentParams(\n  overrides: Partial<FilamentParams> = {},\n): FilamentParams {\n  return { ...FILAMENT_DEFAULTS, ...overrides };\n}\n\n/** Flat xyz triples, segments + 1 knots, closed. The drift phase rolls the\n * strand around its tube, so the coil keeps threading through itself without\n * the figure ever changing character. */\nexport function filamentKnots(time: number, p: FilamentParams): number[] {\n  const [major, minor] = p.radius;\n  const [pw, qw] = p.winding;\n  const [sx, sy, sz] = p.scale;\n  const spin = time * p.spin * TAU;\n  const cs = Math.cos(spin);\n  const ss = Math.sin(spin);\n  const ct = Math.cos(p.tilt);\n  const st = Math.sin(p.tilt);\n  const dt = time * p.driftRate;\n\n  const knots: number[] = [];\n  for (let i = 0; i <= p.segments; i++) {\n    const u = (i % p.segments) * (TAU / p.segments);\n    const tube = minor + p.drift * Math.sin(2 * u + dt * 1.3);\n    const roll = qw * u + dt * 0.7;\n    const w = major + tube * Math.cos(roll);\n\n    const x = w * Math.cos(pw * u) * sx;\n    const y = (w * Math.sin(pw * u) + p.drift * Math.sin(u + dt)) * sy;\n    const z = tube * Math.sin(roll) * sz;\n\n    const yt = y * ct - z * st;\n    const zt = y * st + z * ct;\n    knots.push(x * cs + zt * ss, yt, zt * cs - x * ss);\n  }\n  return knots;\n}\n"
    },
    {
      "path": "filament-shader.ts",
      "target": "components/ui/filament-shader.ts",
      "type": "registry:ui",
      "content": "import { glsl } from \"./atmospheres-glsl\";\n\n/**\n * Hehku is one object, not a field: a single filament coiling through void,\n * heated until the places where it bunches against itself glow green-white\n * while the long thin runs stay deep violet.\n *\n * There is no march. Around a thin capsule the squared distance along a ray is\n * a parabola, so the ray integral of the gaussian core has a closed form:\n * exp(-k * d0^2) / sin(theta), with d0 the closest approach and theta the angle\n * between ray and segment. That 1 / sin factor is the physics of the piece: a\n * ray running along the filament collects more light, which is why the coil\n * blooms exactly where it turns toward the eye or bunches against itself.\n */\n\nconst MAX_KNOTS = 97;\n\nexport const MAX_FILAMENT_SEGMENTS = MAX_KNOTS - 1;\n\nexport const filamentFragmentShader = /* glsl */ `#version 300 es\nprecision highp float;\n\nuniform vec2  uResolution;\nuniform vec3  uKnots[${MAX_KNOTS}];\nuniform int   uSegments;\nuniform float uDist;\nuniform float uFocal;\nuniform vec2  uOffset;\nuniform float uThickness;\nuniform float uGlow;\nuniform float uBloom;\nuniform vec3  uCool;\nuniform vec3  uHot;\nuniform float uExposure;\nuniform float uAlpha;\nuniform float uBlend;\n\nout vec4 fragColor;\n\n${glsl(\"dither\", \"composite\")}\n\nconst int MAX_SEGMENTS = ${MAX_KNOTS - 1};\n\nvoid main() {\n  vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution) / uResolution.y;\n\n  vec3 ro = vec3(uOffset, uDist);\n  vec3 rd = normalize(vec3(uv, -uFocal));\n\n  float spark = 0.0;\n  float core = 0.0;\n  float halo = 0.0;\n\n  for (int s = 0; s < MAX_SEGMENTS; s++) {\n    if (s >= uSegments) break;\n    vec3 a = uKnots[s];\n    vec3 ba = uKnots[s + 1] - a;\n    float ba2 = max(dot(ba, ba), 1e-5);\n    vec3 w = ro - a;\n    float bd = dot(rd, ba);\n    // The denominator is ba2 * sin^2(theta); the floor caps the alignment\n    // bloom so a ray staring straight down a segment stays finite.\n    float denom = max(ba2 - bd * bd, ba2 * 0.1);\n    float h = clamp((dot(w, ba) - bd * dot(w, rd)) / denom, 0.0, 1.0);\n    vec3 q = a + ba * h;\n    float t = max(dot(q - ro, rd), 0.0);\n    vec3 pc = ro + rd * t - q;\n    float dr = length(pc);\n    float d = max(dr - uThickness, 0.0);\n    // The coil's slow wander can sweep the strand through the eye point, and\n    // a pass through the camera would white out the whole frame; the near\n    // fade lets it slide past the lens instead.\n    float boost = inversesqrt(max(1.0 - bd * bd / ba2, 0.1))\n      * smoothstep(0.1, 0.9, t);\n    spark += exp(-uGlow * 4.0 * dr * dr) * boost;\n    core += exp(-uGlow * d * d) * boost;\n    halo += exp(-uGlow * 0.14 * d * d) * boost;\n\n    // The chain is closed, so every knot is the clamped end of two segments\n    // and would be counted twice. One point term per segment start pays the\n    // whole loop back exactly once.\n    float ta = max(dot(-w, rd), 0.0);\n    float dra = length(w + rd * ta);\n    float da = max(dra - uThickness, 0.0);\n    float nfa = smoothstep(0.1, 0.9, ta);\n    spark -= exp(-uGlow * 4.0 * dra * dra) * nfa;\n    core -= exp(-uGlow * da * da) * nfa;\n    halo -= exp(-uGlow * 0.14 * da * da) * nfa;\n  }\n\n  spark = max(spark, 0.0);\n  core = max(core, 0.0);\n  halo = max(halo, 0.0);\n\n  float bunch = max(halo - uBloom, 0.0);\n  float heat = 1.0 - exp(-bunch * bunch * 0.1);\n  // The heat ramp runs violet body, green-white strand centre, white where the\n  // coil bunches: incandescence, not a flat white clip.\n  vec3 glowRamp = mix(uHot, vec3(1.0), heat * 0.7);\n  vec3 col = (uCool * (core * 0.5 + halo * 0.06)\n    + glowRamp * (spark * (0.45 + 1.1 * heat) + 0.4 * core * heat))\n    * uExposure;\n\n  // Tonemapped on luminance, not per channel, so the bright passes saturate\n  // toward the heat ramp instead of clipping to flat white.\n  float lum = max(col.r, max(col.g, col.b));\n  float peak = 1.0 - exp(-lum);\n  vec3 display = col / max(lum, 1e-4);\n  display = mix(display, display * display, uBlend);\n  display = dither(display, gl_FragCoord.xy, 1.0 / 255.0);\n\n  fragColor = composite(display, peak * uAlpha);\n}\n`;\n"
    },
    {
      "path": "filament.ts",
      "target": "components/ui/filament.ts",
      "type": "registry:ui",
      "content": "import type { Rgb } from \"./atmospheres-color\";\nimport {\n  setUniform,\n  type Uniforms,\n} from \"./atmospheres-gl\";\nimport type { FilamentParams } from \"./filament-curve\";\nimport { MAX_FILAMENT_SEGMENTS } from \"./filament-shader\";\n\nexport interface FilamentColors {\n  /** The thin cold runs. */\n  cool: Rgb;\n  /** Where the coil bunches and light accumulates. */\n  hot: Rgb;\n}\n\nexport interface FilamentView {\n  /** Eye distance along +z. */\n  dist: number;\n  focal: number;\n  /** Lateral eye position. Off-axis, so the coil crops at the frame instead\n   * of sitting whole in the middle of it. */\n  offset: [number, number];\n  /** Halo level above which a ray counts as looking through bunched coil. */\n  bloom: number;\n  exposure: number;\n}\n\nexport const FILAMENT_VIEW: FilamentView = {\n  dist: 2.9,\n  focal: 1,\n  offset: [1.2, 1.9],\n  bloom: 6,\n  exposure: 1.9,\n};\n\nexport function resolveFilamentView(\n  overrides: Partial<FilamentView> = {},\n): FilamentView {\n  return { ...FILAMENT_VIEW, ...overrides };\n}\n\n/** The thin runs sink well under the token: deep cold violet, not lavender. */\nexport function coolFrom(accent: Rgb): Rgb {\n  return [accent[0] * 0.5, accent[1] * 0.3, accent[2] * 0.9];\n}\n\n/** Green heated toward white: the bunched passes are the only bright thing. */\nexport function hotFrom(accentAlt: Rgb): Rgb {\n  const lift = (c: number) => c + (1 - c) * 0.35;\n  return [lift(accentAlt[0]), lift(accentAlt[1]), lift(accentAlt[2])];\n}\n\n/** ogl reads array uniforms only when Array.isArray passes, so the knots reach\n * the GPU as a plain flat number[]. A Float32Array uploads nothing at all. */\nfunction padKnots(knots: readonly number[]): number[] {\n  const size = (MAX_FILAMENT_SEGMENTS + 1) * 3;\n  const out = knots.slice(0, size);\n  const tail = out.slice(-3);\n  while (out.length < size) out.push(...(tail.length === 3 ? tail : [0, 0, 0]));\n  return out;\n}\n\nexport interface FilamentFrame {\n  knots: readonly number[];\n  segments: number;\n  alpha: number;\n  blend: number;\n}\n\nexport function filamentUniforms(\n  colors: FilamentColors,\n  params: FilamentParams,\n  view: FilamentView,\n): Uniforms {\n  return {\n    uResolution: { value: [1, 1] },\n    uKnots: { value: padKnots([]) },\n    uSegments: { value: Math.min(params.segments, MAX_FILAMENT_SEGMENTS) },\n    uDist: { value: view.dist },\n    uFocal: { value: view.focal },\n    uOffset: { value: [view.offset[0], view.offset[1]] },\n    uThickness: { value: params.thickness },\n    uGlow: { value: params.glow },\n    uBloom: { value: view.bloom },\n    uCool: { value: colors.cool },\n    uHot: { value: colors.hot },\n    uExposure: { value: view.exposure },\n    uAlpha: { value: 1 },\n    uBlend: { value: 0 },\n  };\n}\n\nexport function setFilamentColors(u: Uniforms, colors: FilamentColors): void {\n  setUniform(u, \"uCool\", colors.cool);\n  setUniform(u, \"uHot\", colors.hot);\n}\n\nexport function setFilamentShape(\n  u: Uniforms,\n  params: FilamentParams,\n  view: FilamentView,\n): void {\n  setUniform(u, \"uThickness\", params.thickness);\n  setUniform(u, \"uGlow\", params.glow);\n  setUniform(u, \"uDist\", view.dist);\n  setUniform(u, \"uFocal\", view.focal);\n  setUniform(u, \"uOffset\", [view.offset[0], view.offset[1]]);\n  setUniform(u, \"uBloom\", view.bloom);\n  setUniform(u, \"uExposure\", view.exposure);\n}\n\nexport function setFilamentFrame(u: Uniforms, frame: FilamentFrame): void {\n  setUniform(u, \"uKnots\", padKnots(frame.knots));\n  setUniform(u, \"uSegments\", Math.min(frame.segments, MAX_FILAMENT_SEGMENTS));\n  setUniform(u, \"uAlpha\", frame.alpha);\n  setUniform(u, \"uBlend\", frame.blend);\n}\n"
    },
    {
      "path": "hehku.tsx",
      "target": "components/ui/hehku.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  type BlendMode,\n  blendStyleFor,\n  blendUniform,\n  resolveBlendMode,\n  resolveColor,\n} from \"./atmospheres-color\";\nimport { hiddenOnGround } from \"./atmospheres-ground\";\nimport { useGlCanvas } from \"./use-gl-canvas\";\nimport { useTokenColors } from \"./use-token-colors\";\nimport {\n  coolFrom,\n  type FilamentColors,\n  type FilamentView,\n  filamentUniforms,\n  hotFrom,\n  resolveFilamentView,\n  setFilamentColors,\n  setFilamentFrame,\n  setFilamentShape,\n} from \"./filament\";\nimport {\n  type FilamentParams,\n  filamentKnots,\n  resolveFilamentParams,\n} from \"./filament-curve\";\nimport { filamentFragmentShader } from \"./filament-shader\";\n\nconst ROLES = [\"accent\", \"accent-alt\"] as const;\n\nexport interface HehkuProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Rate of the coil's wander and spin. Defaults to 1. It should stay slow. */\n  speed?: number;\n  /** Overall strength, 0..1. Defaults to 1. */\n  opacity?: number;\n  /** Dark grounds emit, light grounds stain. Defaults to the resolved bg. */\n  mode?: BlendMode;\n  /** Override the two ends of the heat ramp. */\n  colors?: { cool?: string; hot?: string };\n  /** Escape hatch for the curve, for tuning demos. */\n  params?: Partial<FilamentParams>;\n  /** Escape hatch for the camera and the heat ramp. */\n  view?: Partial<FilamentView>;\n  children?: React.ReactNode;\n}\n\n/** Seconds into the coil the reduced-motion still frame is taken from. */\nconst STILL_TIME = 14;\n\nexport const Hehku = React.forwardRef<HTMLDivElement, HehkuProps>(\n  (\n    {\n      speed = 1,\n      opacity = 1,\n      mode,\n      colors,\n      params,\n      view,\n      className,\n      children,\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const cCool = colors?.cool;\n    const cHot = colors?.hot;\n\n    const scopeRef = React.useRef<HTMLDivElement | null>(null);\n    const tokens = useTokenColors(ROLES, { scopeRef });\n    const blend = resolveBlendMode(mode, tokens.bg);\n\n    const heat = React.useMemo<FilamentColors>(\n      () => ({\n        cool: cCool ? resolveColor(cCool) : coolFrom(tokens.colors.accent),\n        hot: cHot ? resolveColor(cHot) : hotFrom(tokens.colors[\"accent-alt\"]),\n      }),\n      [cCool, cHot, tokens],\n    );\n\n    const heatRef = React.useRef(heat);\n    heatRef.current = heat;\n    const speedRef = React.useRef(speed);\n    speedRef.current = speed;\n    const opacityRef = React.useRef(opacity);\n    opacityRef.current = opacity;\n    const blendRef = React.useRef(blend);\n    blendRef.current = blend;\n\n    const curve = resolveFilamentParams(params);\n    const curveRef = React.useRef(curve);\n    curveRef.current = curve;\n    const camera = resolveFilamentView(view);\n    const cameraRef = React.useRef(camera);\n    cameraRef.current = camera;\n\n    const canvas = useGlCanvas({\n      fragment: filamentFragmentShader,\n      uniforms: () =>\n        filamentUniforms(heatRef.current, curveRef.current, cameraRef.current),\n      enabled: !hiddenOnGround(\"hehku\", blend),\n      maxDpr: 1.5,\n      stillTime: STILL_TIME,\n      onFrame: (u, frame) => {\n        const p = curveRef.current;\n        const time = frame.time * speedRef.current;\n        setFilamentColors(u, heatRef.current);\n        setFilamentShape(u, p, cameraRef.current);\n        setFilamentFrame(u, {\n          knots: filamentKnots(time, p),\n          segments: p.segments,\n          alpha: opacityRef.current,\n          blend: blendUniform(blendRef.current),\n        });\n      },\n    });\n\n    const { redraw } = canvas;\n    // biome-ignore lint/correctness/useExhaustiveDependencies: the still frame must repaint when the theme repaints the heat ramp.\n    React.useEffect(() => {\n      redraw();\n    }, [redraw, heat, blend]);\n\n    const coilOn = canvas.active;\n\n    return (\n      <div\n        ref={(node) => {\n          canvas.containerRef.current = node;\n          scopeRef.current = node;\n          if (typeof forwardedRef === \"function\") forwardedRef(node);\n          else if (forwardedRef) forwardedRef.current = node;\n        }}\n        data-fluid={coilOn ? \"on\" : \"off\"}\n        className={cn(\"relative isolate overflow-hidden\", className)}\n        {...props}\n      >\n        {coilOn ? (\n          <div\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 -z-10\"\n          >\n            <canvas\n              ref={canvas.canvasRef}\n              className=\"block h-full w-full\"\n              style={blendStyleFor(blend)}\n            />\n          </div>\n        ) : null}\n        {children}\n      </div>\n    );\n  },\n);\nHehku.displayName = \"Hehku\";\n"
    }
  ]
}