{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "contribution-heatmap",
  "title": "Contribution Heatmap",
  "description": "A pixel heatmap fills in under a counter rolling up to 12,480 renders.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/contribution-heatmap/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport { type ReactNode, useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"300\", \"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: mono } = loadMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\n\nexport interface ContributionHeatmapProps {\n  value?: number;\n  label?: string;\n  weeks?: number;\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 EASE_POP = Easing.bezier(0.3, 1.25, 0.6, 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    empty: \"rgba(255,255,255,0.055)\",\n    // gradients.sh \"beam\": the year warms from the accent into ice white.\n    ramp: [\"#7E9BFF\", \"#F4F7FF\"],\n    toward: [18, 22, 64],\n    levels: [0.68, 0.46, 0.22, 0],\n    glow: \"rgba(61,91,255,0.20)\",\n    ground: \"rgba(61,91,255,0.14)\",\n    dim: 0.42,\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    empty: \"rgba(18,18,20,0.06)\",\n    // gradients.sh \"pixel-glass\" (light): accent → violet → pink.\n    ramp: [\"#7A3CFF\", \"#FF4FB8\"],\n    toward: [245, 244, 240],\n    levels: [0.74, 0.52, 0.28, 0],\n    glow: \"rgba(122,60,255,0.10)\",\n    ground: \"rgba(255,79,184,0.06)\",\n    dim: 0.36,\n    grain: 0.045,\n  },\n} as const;\n\nconst MONTHS = [\n  \"Jan\",\n  \"Feb\",\n  \"Mar\",\n  \"Apr\",\n  \"May\",\n  \"Jun\",\n  \"Jul\",\n  \"Aug\",\n  \"Sep\",\n  \"Oct\",\n  \"Nov\",\n  \"Dec\",\n];\nconst DAYS = [\"\", \"Mon\", \"\", \"Wed\", \"\", \"Fri\", \"\"];\n\nconst SWEEP = [4, 50] as const;\nconst CELL_IN = 10;\nconst HIT = 62;\n\nfunction rgb(hex: string): number[] | null {\n  const m = /^#([0-9a-f]{6})$/i.exec(hex);\n  if (!m) return null;\n  const v = Number.parseInt(m[1], 16);\n  return [(v >> 16) & 255, (v >> 8) & 255, v & 255];\n}\n\nfunction mix(hex: string, toward: readonly number[], amt: number) {\n  const a = rgb(hex);\n  if (!a) return hex;\n  const c = a.map((v, i) => Math.round(v + (toward[i] - v) * amt));\n  return `rgb(${c[0]},${c[1]},${c[2]})`;\n}\n\nfunction hash(a: number, b: number, seed: number) {\n  let h = Math.imul(a + 1, 374761393) ^ Math.imul(b + 7, 668265263) ^ seed;\n  h = Math.imul(h ^ (h >>> 13), 1274126177);\n  h ^= h >>> 16;\n  return (h >>> 0) / 4294967295;\n}\n\nfunction levelOf(week: number, day: number, weeks: number) {\n  const r = hash(week, day, 0x51ed);\n  const trend = 0.42 + 0.58 * (week / Math.max(1, weeks - 1)) ** 0.8;\n  const weekend = day === 0 || day === 6 ? 0.42 : 1;\n  const quiet = hash(week, 99, 0x2b1) > 0.9 ? 0.3 : 1;\n  const burst = hash(week, day, 0x9e37) > 0.94 ? 0.45 : 0;\n  const a = r * trend * weekend * quiet + burst;\n  if (a < 0.12) return 0;\n  if (a < 0.28) return 1;\n  if (a < 0.47) return 2;\n  if (a < 0.67) return 3;\n  return 4;\n}\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={17}\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\nexport function ContributionHeatmap({\n  value = 12480,\n  label = \"renders this year\",\n  weeks = 52,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: ContributionHeatmapProps) {\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 cols = Math.max(4, Math.round(weeks));\n\n  const pitch = Math.min((width * 0.8) / cols, 34 * u);\n  const cell = pitch * 0.8;\n  const radius = cell * 0.26;\n  const gridW = cols * pitch - (pitch - cell);\n  const gridH = 7 * pitch - (pitch - cell);\n  const gx = (width - gridW) / 2;\n  const gy = height / 2 - gridH / 2;\n\n  const gradId = `r${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  const flashColor =\n    theme === \"dark\" ? mix(accent, [255, 255, 255], 0.6) : accent;\n  const diag = 2.4;\n  const span = cols + 6 * diag;\n  const startOf = (c: number, r: number) =>\n    SWEEP[0] + ((c + r * diag) / span) * (SWEEP[1] - SWEEP[0]);\n\n  const stage = interpolate(t, [0, 8], [0.45, 1], CLAMP);\n  const settle = interpolate(t, [HIT - 4, HIT + 22], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const drift = interpolate(t, [0, HIT + 60], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const zoom = interpolate(t, [0, HIT - 2], [1.12, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const gridScale = zoom * (1 - 0.1 * settle);\n  const gridY = settle * 0.16 * height;\n  const gridOpacity = 1 - p.dim * settle;\n  const gridBlur = 0.9 * u * settle;\n  const axisO =\n    interpolate(t, [2, 14], [0, 1], { ...CLAMP, easing: EASE_OUT }) *\n    interpolate(t, [HIT - 4, HIT + 6], [1, 0], CLAMP);\n\n  const land = interpolate(t, [HIT + 6, HIT + 26], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const count = interpolate(t, [HIT + 6, HIT + 34], [0.8, 1], {\n    ...CLAMP,\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  const labIn = interpolate(t, [HIT + 14, HIT + 32], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const shown = Math.round(value * count).toLocaleString(\"en-US\");\n\n  const cells: ReactNode[] = [];\n  for (let c = 0; c < cols; c++) {\n    for (let r = 0; r < 7; r++) {\n      const lv = levelOf(c, r, cols);\n      const s0 = startOf(c, r);\n      const k = interpolate(t, [s0, s0 + CELL_IN], [0, 1], CLAMP);\n      const pop = EASE_POP(k);\n      const flash = interpolate(t - s0, [0, 3, 13], [0, 1, 0], CLAMP);\n      const x = gx + c * pitch;\n      const y = gy + r * pitch;\n      const sz = cell * (0.66 + 0.34 * pop);\n      const off = (cell - sz) / 2;\n      cells.push(\n        <g key={`${c}-${r}`}>\n          <rect\n            x={x}\n            y={y}\n            width={cell}\n            height={cell}\n            rx={radius}\n            fill={p.empty}\n          />\n          {lv > 0 && k > 0 ? (\n            <rect\n              x={x + off}\n              y={y + off}\n              width={sz}\n              height={sz}\n              rx={radius * (sz / cell)}\n              fill={`url(#${gradId}${lv - 1})`}\n              opacity={EASE_OUT(k)}\n            />\n          ) : null}\n          {flash > 0.01 ? (\n            <rect\n              x={x + off}\n              y={y + off}\n              width={sz}\n              height={sz}\n              rx={radius * (sz / cell)}\n              fill={flashColor}\n              opacity={flash * (lv > 0 ? 0.75 : 0.18)}\n            />\n          ) : null}\n        </g>,\n      );\n    }\n  }\n\n  const valueSize = 200 * u;\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(70% 55% at ${58 + 10 * drift}% 108%, ${p.ground}, transparent 72%)`,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          transform: `translate(${(0.5 - drift) * 14 * u}px, ${gridY}px) scale(${gridScale})`,\n          transformOrigin: \"50% 50%\",\n          opacity: stage * gridOpacity,\n          filter: gridBlur > 0.05 ? `blur(${gridBlur}px)` : undefined,\n        }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          <defs>\n            {p.levels.map((amt, i) => (\n              <linearGradient\n                key={amt}\n                id={`${gradId}${i}`}\n                gradientUnits=\"userSpaceOnUse\"\n                x1={gx - gridW * 0.08 * (1 - drift)}\n                y1={gy + gridH}\n                x2={gx + gridW * (1.02 - 0.06 * drift)}\n                y2={gy}\n              >\n                <stop offset=\"0\" stopColor={mix(accent, p.toward, amt)} />\n                <stop offset=\"0.6\" stopColor={mix(p.ramp[0], p.toward, amt)} />\n                <stop\n                  offset=\"0.86\"\n                  stopColor={mix(p.ramp[1], p.toward, amt * 0.9 + 0.12)}\n                />\n                <stop offset=\"1\" stopColor={mix(p.ramp[1], p.toward, amt)} />\n              </linearGradient>\n            ))}\n          </defs>\n          {cells}\n        </svg>\n        {MONTHS.map((m, i) => {\n          const c = Math.round((i * cols) / 12);\n          const a = interpolate(t, [2 + i * 1.5, 14 + i * 1.5], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          });\n          return (\n            <div\n              key={m}\n              style={{\n                position: \"absolute\",\n                left: gx + c * pitch,\n                top: gy - 30 * u,\n                fontFamily: mono,\n                fontWeight: 400,\n                fontSize: 12 * u,\n                letterSpacing: \"0.06em\",\n                textTransform: \"uppercase\",\n                color: p.muted,\n                opacity: a * axisO,\n                transform: `translateY(${(1 - a) * 6 * u}px)`,\n              }}\n            >\n              {m}\n            </div>\n          );\n        })}\n        {DAYS.map((d, r) =>\n          d ? (\n            <div\n              key={d}\n              style={{\n                position: \"absolute\",\n                right: width - gx + 14 * u,\n                top: gy + r * pitch + cell / 2 - 8 * u,\n                fontFamily: mono,\n                fontWeight: 400,\n                fontSize: 12 * u,\n                letterSpacing: \"0.06em\",\n                textTransform: \"uppercase\",\n                lineHeight: `${16 * u}px`,\n                color: p.muted,\n                opacity: axisO,\n              }}\n            >\n              {d}\n            </div>\n          ) : null,\n        )}\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 46% 34% at 50% 36%, ${p.glow}, transparent 72%)`,\n          opacity: land,\n        }}\n      />\n      <AbsoluteFill\n        style={{\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          flexDirection: \"column\",\n          transform: `translateY(${-0.15 * height}px)`,\n        }}\n      >\n        <div\n          style={{\n            fontFamily: sans,\n            fontWeight: 400,\n            fontSize: valueSize,\n            lineHeight: 0.92,\n            letterSpacing: \"-0.04em\",\n            color: p.fg,\n            fontVariantNumeric: \"tabular-nums\",\n            whiteSpace: \"nowrap\",\n            opacity: land,\n            filter: land < 0.99 ? `blur(${(1 - land) * 10 * u}px)` : undefined,\n            transform: `translateY(${(1 - land) * 28 * u}px) scale(${1.04 - 0.04 * land})`,\n          }}\n        >\n          {shown}\n        </div>\n        <div\n          style={{\n            marginTop: 22 * u,\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 12 * u,\n            opacity: labIn,\n            transform: `translateY(${(1 - labIn) * 10 * u}px)`,\n          }}\n        >\n          <span\n            style={{\n              width: 44 * u,\n              height: 12 * u,\n              borderRadius: 4 * u,\n              background: `linear-gradient(90deg, ${accent}, ${p.ramp[0]} 62%, ${p.ramp[1]})`,\n            }}\n          />\n          <span\n            style={{\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: 24 * u,\n              letterSpacing: \"-0.015em\",\n              color: p.muted,\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {label}\n          </span>\n        </div>\n      </AbsoluteFill>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 80% 80% at 50% 50%, transparent 62%, ${p.bg} 100%)`,\n          opacity: 0.6,\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/contribution-heatmap.tsx"
    }
  ],
  "type": "registry:component"
}
