{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "spring-bars",
  "title": "Spring Bars",
  "description": "Bars spring up with overshoot, one gradient bar highlighted under a 3.4x stat.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/spring-bars/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/SchibstedGrotesk\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: display } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface SpringBarsProps {\n  values?: number[];\n  accentIndex?: number;\n  value?: string;\n  caption?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    muted: \"#8B8A90\",\n    coil: \"#9A99A0\",\n    weight: \"#4A4950\",\n    axis: \"rgba(255,255,255,0.2)\",\n    // gradients.sh \"fluted\": #1d2a8f → #2f7bff → #ff7a3d → #ffe2c4, bottom to top\n    hero: [\"#1D2A8F\", \"#2F7BFF\", \"#B565C2\", \"#FF7A3D\", \"#FFE2C4\"],\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    coil: \"#6E6C67\",\n    weight: \"#BAB8B1\",\n    axis: \"rgba(18,18,20,0.22)\",\n    hero: [\"#1D2A8F\", \"#2F7BFF\", \"#B04FB8\", \"#F2662A\", \"#FF9A5C\"],\n    grain: 0.045,\n  },\n} as const;\n\nconst SPAWN0 = 14;\nconst STAGGER = 3;\nconst FALL = 15;\nconst ZETA = 0.28;\nconst COILS = 11;\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={5}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nfunction coilPath(\n  cx: number,\n  base: number,\n  len: number,\n  cw: number,\n  e: number,\n) {\n  const top = base - len;\n  const pts: string[] = [`M ${cx} ${base}`, `L ${cx} ${base - e}`];\n  const body = Math.max(0, len - 2 * e);\n  for (let k = 0; k < COILS; k++) {\n    const y = base - e - ((k + 0.5) / COILS) * body;\n    const x = cx + (k % 2 === 0 ? -cw / 2 : cw / 2);\n    pts.push(`L ${x} ${y}`);\n  }\n  pts.push(`L ${cx} ${top + e}`, `L ${cx} ${top}`);\n  return pts.join(\" \");\n}\n\nfunction barPath(x: number, base: number, w: number, h: number, r: number) {\n  const rr = Math.min(r, h / 2, w / 2);\n  const top = base - h;\n  return `M ${x} ${base} L ${x} ${top + rr} Q ${x} ${top} ${x + rr} ${top} L ${x + w - rr} ${top} Q ${x + w} ${top} ${x + w} ${top + rr} L ${x + w} ${base} Z`;\n}\n\nexport function SpringBars({\n  values = [0.58, 0.72, 0.52, 0.66, 0.97, 0.62, 0.7],\n  accentIndex = 4,\n  value = \"3.4×\",\n  caption = \"Least sag under the same load\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: SpringBarsProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n  const vals = values.slice(0, 10).map((v) => Math.max(0.3, Math.min(1, v)));\n  const n = Math.max(1, vals.length);\n  const ai = Math.max(0, Math.min(n - 1, Math.round(accentIndex)));\n\n  const chartW = Math.min(width * 0.72, n * 132 * u);\n  const pitch = chartW / n;\n  const left = (width - chartW) / 2;\n  const groundY = height * 0.84;\n  const L0 = 340 * u;\n  const plateW = Math.min(pitch * 0.62, 84 * u);\n  const plateH = 14 * u;\n  const coilW = plateW * 0.7;\n  const endSeg = 9 * u;\n  const sw = Math.max(1, 1.6 * u);\n\n  const springs = vals.map((v, i) => {\n    const rest = v * L0 * 0.94;\n    const comp = L0 - rest;\n    const omega = Math.max(\n      0.26,\n      Math.min(0.46, 0.3 * Math.sqrt((150 * u) / Math.max(comp, 1))),\n    );\n    const spawn = SPAWN0 + i * STAGGER;\n    return { i, rest, comp, omega, spawn, contact: spawn + FALL };\n  });\n  const lastContact = Math.max(...springs.map((s) => s.contact));\n  const MORPH = lastContact + 44;\n\n  const enter = interpolate(t, [0, 14], [0, 1], { ...CLAMP, easing: EASE_IO });\n  const morph = interpolate(t, [MORPH, MORPH + 20], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const labelIn = interpolate(t, [MORPH + 10, MORPH + 30], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const capIn = interpolate(t, [MORPH + 18, MORPH + 36], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const lengthAt = (s: (typeof springs)[number]) => {\n    const lt = t - s.contact;\n    if (lt <= 0) return L0;\n    const wd = s.omega * Math.sqrt(1 - ZETA * ZETA);\n    const x0 = s.comp;\n    const v0 = -0.3 * x0 * s.omega;\n    const decay = Math.exp(-ZETA * s.omega * lt);\n    const x =\n      decay *\n      (x0 * Math.cos(wd * lt) +\n        ((v0 + ZETA * s.omega * x0) / wd) * Math.sin(wd * lt));\n    const len = s.rest + x;\n    const floor = L0 * 0.16;\n    return len < floor * 1.6 ? floor + (len - floor) * 0.4 : len;\n  };\n\n  const accentS = springs[ai];\n  const accentX = left + pitch * (ai + 0.5);\n  const accentTop = groundY - accentS.rest - plateH;\n  const valueSize = 112 * u;\n  const capSize = 22 * u;\n  const labelBottom = accentTop - 26 * u;\n  const labelX = Math.max(width * 0.1, Math.min(width * 0.9, accentX));\n  const flutedId = `fluted${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const drift = interpolate(t, [0, 160], [0, 1], CLAMP);\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", inset: 0, overflow: \"visible\" }}\n        aria-hidden\n      >\n        <defs>\n          <linearGradient\n            id={flutedId}\n            gradientUnits=\"userSpaceOnUse\"\n            x1={0}\n            y1={groundY}\n            x2={0}\n            y2={accentTop - 6 * u}\n          >\n            <stop offset={0} stopColor={p.hero[0]} />\n            <stop offset={0.3 + 0.03 * drift} stopColor={p.hero[1]} />\n            <stop offset={0.56 + 0.03 * drift} stopColor={p.hero[2]} />\n            <stop offset={0.78 + 0.02 * drift} stopColor={p.hero[3]} />\n            <stop offset={1} stopColor={p.hero[4]} />\n          </linearGradient>\n        </defs>\n        <line\n          x1={width / 2 - (chartW / 2 + 40 * u) * enter}\n          x2={width / 2 + (chartW / 2 + 40 * u) * enter}\n          y1={groundY + Math.max(1, u) / 2}\n          y2={groundY + Math.max(1, u) / 2}\n          stroke={p.axis}\n          strokeWidth={Math.max(1, u)}\n        />\n        {springs.map((s) => {\n          const cx = left + pitch * (s.i + 0.5);\n          const isA = s.i === ai;\n          const len = lengthAt(s);\n          const appear = interpolate(\n            t,\n            [2 + s.i * 1.5, 14 + s.i * 1.5],\n            [0, 1],\n            { ...CLAMP, easing: EASE_OUT },\n          );\n          const lt = t - s.spawn;\n          const startY = -plateH - 24 * u;\n          const contactY = groundY - L0 - plateH;\n          const k = Math.min(1, Math.max(0, lt / FALL));\n          const plateY =\n            lt < FALL\n              ? startY + (contactY - startY) * k * k\n              : groundY - len - plateH;\n          const barH = (groundY - plateY) * morph;\n          const r = Math.min(3 * u, plateH / 3);\n          const color = isA ? `url(#${flutedId})` : p.weight;\n          return (\n            <g key={`s${s.i}`}>\n              <path\n                d={coilPath(cx, groundY, len, coilW, endSeg)}\n                fill=\"none\"\n                stroke={isA ? `url(#${flutedId})` : p.coil}\n                strokeWidth={sw}\n                strokeLinejoin=\"round\"\n                strokeLinecap=\"round\"\n                opacity={appear * (1 - morph)}\n                transform={`translate(0 ${(1 - appear) * 10 * u})`}\n              />\n              {lt >= 0 ? (\n                <rect\n                  x={cx - plateW / 2}\n                  y={plateY}\n                  width={plateW}\n                  height={plateH}\n                  rx={r}\n                  fill={color}\n                />\n              ) : null}\n              {morph > 0 ? (\n                <path\n                  d={barPath(cx - plateW / 2, groundY, plateW, barH, r)}\n                  fill={color}\n                />\n              ) : null}\n            </g>\n          );\n        })}\n      </svg>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: labelX,\n          bottom: height - labelBottom,\n          transform: \"translateX(-50%)\",\n          display: \"flex\",\n          flexDirection: \"column\",\n          alignItems: \"center\",\n          gap: 12 * u,\n          whiteSpace: \"nowrap\",\n        }}\n      >\n        <div\n          style={{\n            fontFamily: display,\n            fontWeight: 500,\n            fontSize: valueSize,\n            lineHeight: 0.8,\n            letterSpacing: \"-0.04em\",\n            color: p.fg,\n            opacity: labelIn,\n            filter:\n              labelIn < 0.99 ? `blur(${(1 - labelIn) * 8 * u}px)` : undefined,\n            transform: `translateY(${(1 - labelIn) * 18 * u}px)`,\n          }}\n        >\n          {value}\n        </div>\n        {caption ? (\n          <div\n            style={{\n              fontFamily: display,\n              fontWeight: 400,\n              fontSize: capSize,\n              lineHeight: 1.2,\n              letterSpacing: \"-0.01em\",\n              color: p.muted,\n              opacity: capIn,\n              transform: `translateY(${(1 - capIn) * 8 * u}px)`,\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 10 * u,\n            }}\n          >\n            <span\n              style={{\n                width: 8 * u,\n                height: 8 * u,\n                borderRadius: \"50%\",\n                background: accent,\n              }}\n            />\n            {caption}\n          </div>\n        ) : null}\n      </div>\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/spring-bars.tsx"
    }
  ],
  "type": "registry:component"
}
