{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "performance-gauges",
  "title": "Performance Gauges",
  "description": "Animated SVG performance instruments with controlled and self-running modes.",
  "files": [
    {
      "path": "src/components/effects/performance-gauges.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\n/*\n * SPDX-License-Identifier: MIT\n * Inspired by ThreeUI's Performance Gauges by Meng To.\n * Upstream: https://github.com/MengTo/threeui\n * Sources: src/shaders/neuform-isolated/sources/performance-gauges.html and\n * src/shaders/neuform-isolated/NeuformIsolatedEffects.tsx\n * Audited revision: fbc9b3d61b0ef4b2e93b42e4fffa617ca277429b\n * This implementation is a from-scratch React/SVG adaptation.\n */\n\nconst THREE_UI_NOTICE =\n  \"ThreeUI © 2026 Meng To · MIT · github.com/MengTo/threeui · fbc9b3d61b0ef4b2e93b42e4fffa617ca277429b\"\n\nexport type PerformanceGaugeVariant =\n  \"tachometer\" | \"speedometer\" | \"boost\" | \"power\"\n\nexport type PerformanceGaugesProps = {\n  variant?: PerformanceGaugeVariant\n  value?: number\n  className?: string\n  style?: React.CSSProperties\n}\n\ntype GaugeDefinition = {\n  label: string\n  unit: string\n  min: number\n  max: number\n  target: number\n  accent: string\n  alert: string\n  majorLabels: readonly number[]\n  precision: number\n}\n\nconst GAUGES: Record<PerformanceGaugeVariant, GaugeDefinition> = {\n  tachometer: {\n    label: \"Engine speed\",\n    unit: \"×1000 r/min\",\n    min: 0,\n    max: 8,\n    target: 5.8,\n    accent: \"#f5a23a\",\n    alert: \"#ff5a45\",\n    majorLabels: [0, 2, 4, 6, 8],\n    precision: 1,\n  },\n  speedometer: {\n    label: \"Velocity\",\n    unit: \"km/h\",\n    min: 0,\n    max: 160,\n    target: 112,\n    accent: \"#70d6b0\",\n    alert: \"#e9b949\",\n    majorLabels: [0, 40, 80, 120, 160],\n    precision: 0,\n  },\n  boost: {\n    label: \"Turbo pressure\",\n    unit: \"bar\",\n    min: -1,\n    max: 2,\n    target: 1.3,\n    accent: \"#65b7ff\",\n    alert: \"#fd725c\",\n    majorLabels: [-1, -0.25, 0.5, 1.25, 2],\n    precision: 1,\n  },\n  power: {\n    label: \"Power delivery\",\n    unit: \"kW\",\n    min: -50,\n    max: 250,\n    target: 186,\n    accent: \"#d3f36b\",\n    alert: \"#f6a850\",\n    majorLabels: [-50, 25, 100, 175, 250],\n    precision: 0,\n  },\n}\n\nconst START_ANGLE = -125\nconst END_ANGLE = 125\nconst RANGE = END_ANGLE - START_ANGLE\n\nfunction valueToAngle(value: number, gauge: GaugeDefinition) {\n  const progress = (value - gauge.min) / (gauge.max - gauge.min)\n  return START_ANGLE + Math.min(1, Math.max(0, progress)) * RANGE\n}\n\nfunction polarPoint(angle: number, radius: number) {\n  const radians = ((angle - 90) * Math.PI) / 180\n  return {\n    x: 120 + radius * Math.cos(radians),\n    y: 120 + radius * Math.sin(radians),\n  }\n}\n\nfunction describeArc(startAngle: number, endAngle: number, radius: number) {\n  const start = polarPoint(endAngle, radius)\n  const end = polarPoint(startAngle, radius)\n  const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1\n  return `M ${start.x} ${start.y} A ${radius} ${radius} 0 ${largeArcFlag} 0 ${end.x} ${end.y}`\n}\n\nexport function PerformanceGauges({\n  variant = \"tachometer\",\n  value,\n  className,\n  style,\n}: PerformanceGaugesProps) {\n  const gauge = GAUGES[variant]\n  const instanceId = React.useId().replaceAll(\":\", \"\")\n  const resolvedValue = Math.min(\n    gauge.max,\n    Math.max(gauge.min, value ?? gauge.target)\n  )\n  const targetAngle = valueToAngle(resolvedValue, gauge)\n  const [paused, setPaused] = React.useState(false)\n\n  React.useEffect(() => {\n    const syncVisibility = () => setPaused(document.hidden)\n    syncVisibility()\n    document.addEventListener(\"visibilitychange\", syncVisibility)\n    return () =>\n      document.removeEventListener(\"visibilitychange\", syncVisibility)\n  }, [])\n\n  const rootStyle = {\n    \"--gauge-accent\": gauge.accent,\n    \"--gauge-alert\": gauge.alert,\n    \"--gauge-rest\": `${START_ANGLE}deg`,\n    \"--gauge-max\": `${END_ANGLE}deg`,\n    \"--gauge-target\": `${targetAngle}deg`,\n    ...style,\n  } as React.CSSProperties\n\n  return (\n    <figure\n      data-performance-gauge=\"\"\n      data-source-license={THREE_UI_NOTICE}\n      data-paused={paused ? \"true\" : \"false\"}\n      data-controlled={value === undefined ? \"false\" : \"true\"}\n      className={className}\n      style={rootStyle}\n    >\n      <style>{`\n        @keyframes performance-gauge-sweep {\n          0%, 5% { transform: rotate(var(--gauge-rest)); }\n          24% { transform: rotate(var(--gauge-max)); }\n          45% { transform: rotate(calc(var(--gauge-target) - 8deg)); }\n          55% { transform: rotate(calc(var(--gauge-target) + 3deg)); }\n          63%, 92% { transform: rotate(var(--gauge-target)); }\n          100% { transform: rotate(var(--gauge-rest)); }\n        }\n        @keyframes performance-gauge-glow {\n          0%, 100% { opacity: .42; transform: scale(.98); }\n          50% { opacity: .76; transform: scale(1); }\n        }\n        [data-performance-gauge] .performance-gauge__needle {\n          transform-box: fill-box;\n          transform-origin: center bottom;\n          animation: performance-gauge-sweep 6s cubic-bezier(.77, 0, .175, 1) infinite;\n        }\n        [data-performance-gauge][data-controlled=\"true\"] .performance-gauge__needle {\n          animation: none;\n          transform: rotate(var(--gauge-target));\n          transition: transform 500ms cubic-bezier(.77, 0, .175, 1);\n        }\n        [data-performance-gauge] .performance-gauge__glow {\n          transform-box: fill-box;\n          transform-origin: center;\n          animation: performance-gauge-glow 2.4s cubic-bezier(.77, 0, .175, 1) infinite;\n        }\n        [data-performance-gauge][data-paused=\"true\"] .performance-gauge__needle,\n        [data-performance-gauge][data-paused=\"true\"] .performance-gauge__glow {\n          animation-play-state: paused;\n        }\n        @media (prefers-reduced-motion: reduce) {\n          [data-performance-gauge] .performance-gauge__needle,\n          [data-performance-gauge] .performance-gauge__glow {\n            animation: none;\n          }\n          [data-performance-gauge] .performance-gauge__needle {\n            transform: rotate(var(--gauge-target));\n          }\n        }\n      `}</style>\n      <div className=\"relative aspect-square w-full overflow-hidden rounded-[1.25rem] border border-white/10 bg-[#070a09] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,.08),0_24px_80px_rgba(0,0,0,.28)] sm:p-5\">\n        <div\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-[8%] rounded-full opacity-70 blur-2xl\"\n          style={{\n            background: `radial-gradient(circle, ${gauge.accent}1f 0%, transparent 65%)`,\n          }}\n        />\n        <svg\n          viewBox=\"0 0 240 240\"\n          role=\"img\"\n          aria-label={`${gauge.label}: ${resolvedValue.toFixed(gauge.precision)} ${gauge.unit}`}\n          className=\"relative size-full\"\n        >\n          <defs>\n            <radialGradient\n              id={`dial-${instanceId}-${variant}`}\n              cx=\"50%\"\n              cy=\"42%\"\n              r=\"62%\"\n            >\n              <stop offset=\"0%\" stopColor=\"#1b211f\" />\n              <stop offset=\"68%\" stopColor=\"#0c100f\" />\n              <stop offset=\"100%\" stopColor=\"#020303\" />\n            </radialGradient>\n            <linearGradient\n              id={`needle-${instanceId}-${variant}`}\n              x1=\"0\"\n              y1=\"0\"\n              x2=\"0\"\n              y2=\"1\"\n            >\n              <stop offset=\"0%\" stopColor=\"#fff\" />\n              <stop offset=\"38%\" stopColor={gauge.alert} />\n              <stop offset=\"100%\" stopColor=\"#611b13\" />\n            </linearGradient>\n          </defs>\n\n          <circle cx=\"120\" cy=\"120\" r=\"111\" fill=\"#030404\" stroke=\"#2b312f\" />\n          <circle\n            cx=\"120\"\n            cy=\"120\"\n            r=\"104\"\n            fill={`url(#dial-${instanceId}-${variant})`}\n            stroke=\"#555e5a\"\n            strokeOpacity=\".55\"\n          />\n          <circle\n            cx=\"120\"\n            cy=\"120\"\n            r=\"97\"\n            fill=\"none\"\n            stroke=\"#fff\"\n            strokeOpacity=\".06\"\n          />\n\n          <path\n            d={describeArc(START_ANGLE, END_ANGLE, 88)}\n            fill=\"none\"\n            stroke=\"#8d9a95\"\n            strokeOpacity=\".2\"\n            strokeWidth=\"2\"\n          />\n          <path\n            d={describeArc(82, END_ANGLE, 88)}\n            fill=\"none\"\n            stroke={gauge.alert}\n            strokeLinecap=\"butt\"\n            strokeWidth=\"3\"\n          />\n\n          {Array.from({ length: 41 }, (_, index) => {\n            const angle = START_ANGLE + (RANGE * index) / 40\n            const major = index % 10 === 0\n            return (\n              <line\n                key={angle}\n                x1=\"120\"\n                y1={major ? 24 : 27}\n                x2=\"120\"\n                y2={major ? 35 : 32}\n                transform={`rotate(${angle} 120 120)`}\n                stroke={angle >= 82 ? gauge.alert : \"#e7eeeb\"}\n                strokeOpacity={major ? 0.92 : 0.42}\n                strokeWidth={major ? 1.6 : 0.8}\n              />\n            )\n          })}\n\n          {gauge.majorLabels.map((label, index) => {\n            const angle =\n              START_ANGLE + (RANGE * index) / (gauge.majorLabels.length - 1)\n            const point = polarPoint(angle, 70)\n            return (\n              <text\n                key={label}\n                x={point.x}\n                y={point.y}\n                fill=\"#d6dfdb\"\n                fillOpacity=\".84\"\n                fontSize=\"9\"\n                fontFamily=\"ui-monospace, SFMono-Regular, Menlo, monospace\"\n                textAnchor=\"middle\"\n                dominantBaseline=\"middle\"\n              >\n                {label}\n              </text>\n            )\n          })}\n\n          <text\n            x=\"120\"\n            y=\"82\"\n            fill=\"#7d8984\"\n            fontSize=\"6.5\"\n            letterSpacing=\"1.8\"\n            textAnchor=\"middle\"\n          >\n            {gauge.label.toUpperCase()}\n          </text>\n          <text\n            x=\"120\"\n            y=\"94\"\n            fill=\"#cbd6d1\"\n            fontSize=\"7\"\n            fontFamily=\"ui-monospace, SFMono-Regular, Menlo, monospace\"\n            textAnchor=\"middle\"\n          >\n            {gauge.unit}\n          </text>\n\n          <g className=\"performance-gauge__glow\">\n            <circle\n              cx=\"120\"\n              cy=\"120\"\n              r=\"17\"\n              fill={gauge.accent}\n              fillOpacity=\".08\"\n            />\n          </g>\n          <g className=\"performance-gauge__needle\">\n            <path\n              d=\"M116.8 120 L119.1 48 Q120 43 120.9 48 L123.2 120 Z\"\n              fill={`url(#needle-${instanceId}-${variant})`}\n              filter=\"drop-shadow(0 0 3px rgba(255,90,69,.35))\"\n            />\n            <path d=\"M118 120 L120 149 L122 120 Z\" fill=\"#6d1712\" />\n          </g>\n          <circle\n            cx=\"120\"\n            cy=\"120\"\n            r=\"9.5\"\n            fill=\"#111514\"\n            stroke=\"#7b8681\"\n            strokeWidth=\"1\"\n          />\n          <circle\n            cx=\"120\"\n            cy=\"120\"\n            r=\"4.5\"\n            fill=\"#262d2a\"\n            stroke=\"#dbe3df\"\n            strokeOpacity=\".4\"\n          />\n\n          <rect\n            x=\"82\"\n            y=\"165\"\n            width=\"76\"\n            height=\"24\"\n            rx=\"2\"\n            fill=\"#020303\"\n            stroke=\"#39423e\"\n          />\n          <text\n            x=\"120\"\n            y=\"180\"\n            fill={gauge.accent}\n            fontSize=\"13\"\n            fontFamily=\"ui-monospace, SFMono-Regular, Menlo, monospace\"\n            textAnchor=\"middle\"\n            letterSpacing=\"1\"\n          >\n            {resolvedValue.toFixed(gauge.precision).padStart(5, \"0\")}\n          </text>\n          <circle cx=\"68\" cy=\"177\" r=\"2.2\" fill={gauge.accent} />\n          <text\n            x=\"174\"\n            y=\"180\"\n            fill=\"#6e7a75\"\n            fontSize=\"5.5\"\n            textAnchor=\"middle\"\n          >\n            LIVE\n          </text>\n        </svg>\n      </div>\n      <figcaption className=\"sr-only\">\n        {gauge.label} gauge showing {resolvedValue.toFixed(gauge.precision)}{\" \"}\n        {gauge.unit}\n      </figcaption>\n    </figure>\n  )\n}\n",
      "type": "registry:ui",
      "target": "src/components/effects/performance-gauges.tsx"
    }
  ],
  "meta": {
    "collection": "motion-effects",
    "license": "MIT",
    "source": "https://github.com/MengTo/threeui",
    "sourceRevision": "fbc9b3d61b0ef4b2e93b42e4fffa617ca277429b"
  },
  "type": "registry:ui"
}