{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "streamgraph",
  "title": "Streamgraph",
  "description": "Flowing stacked layers draw across the frame and settle into a 48% share.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/streamgraph/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadBricolage } from \"@remotion/google-fonts/BricolageGrotesque\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadBricolage(\"normal\", {\n  weights: [\"400\", \"500\"],\n  subsets: [\"latin\"],\n});\nconst { fontFamily: manrope } = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\n});\n\nexport interface StreamSeries {\n  label: string;\n  values: number[];\n}\n\nexport interface StreamgraphProps {\n  series?: StreamSeries[];\n  highlight?: number;\n  label?: string;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst DEFAULT_SERIES: StreamSeries[] = [\n  {\n    label: \"Kitebase\",\n    values: [12, 14, 17, 19, 18, 16, 17, 19, 18, 16, 15, 15, 14, 13, 13, 12],\n  },\n  {\n    label: \"Lumen\",\n    values: [9, 10, 12, 14, 15, 13, 12, 13, 14, 13, 11, 10, 10, 9, 9, 9],\n  },\n  {\n    label: \"remocn\",\n    values: [2, 2, 3, 4, 5, 7, 9, 11, 13, 16, 19, 22, 25, 28, 31, 33],\n  },\n  {\n    label: \"Fernway\",\n    values: [11, 12, 12, 10, 11, 13, 13, 11, 10, 11, 10, 9, 9, 8, 8, 8],\n  },\n  {\n    label: \"Quarry\",\n    values: [7, 8, 10, 11, 9, 8, 9, 10, 9, 8, 8, 7, 7, 7, 7, 7],\n  },\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_GROW = Easing.bezier(0.42, 0, 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    grays: [\"#5E5D63\", \"#3B3A40\", \"#4C4B51\", \"#2F2E34\"],\n    hair: \"rgba(255,255,255,0.28)\",\n    grain: 0.06,\n    // gradients.sh \"beam\": bg #02030a, [#f4f7ff, #8fb4ff, #3d5bff, #1a1660]\n    beam: [\"#1a1660\", \"#3d5bff\", \"#7f9dff\", \"#b9cdff\"],\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    muted: \"#6E6C67\",\n    grays: [\"#A9A7A1\", \"#D2D0CA\", \"#BEBCB6\", \"#DEDCD6\"],\n    hair: \"rgba(18,18,20,0.3)\",\n    grain: 0.045,\n    beam: [\"#1a1660\", \"#2c3fd6\", \"#3d5bff\", \"#8fb4ff\"],\n  },\n} as const;\n\nconst GROW = [-16, 82] as const;\nconst TIP = 0.12;\nconst HIT = 82;\nconst SAMPLES = 220;\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={31}\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\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n\n/** The remocn.dev logo: R mark (0.76em, on the baseline) + \"emocn\" in Manrope 600. */\nfunction RemocnLogo({ fontSize, color }: { fontSize: number; color: string }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"baseline\",\n        color,\n        fontFamily: manrope,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n        whiteSpace: \"nowrap\",\n      }}\n    >\n      <svg\n        viewBox=\"0 0 124.06 134.26\"\n        style={{ height: \"0.76em\", width: \"auto\" }}\n        aria-hidden\n      >\n        <path fill=\"currentColor\" d={REMOCN_R_PATH} />\n      </svg>\n      <span style={{ marginLeft: \"0.04em\" }}>emocn</span>\n    </div>\n  );\n}\n\nfunction sampleValues(values: number[], u: number) {\n  const m = values.length;\n  if (m === 0) return 0;\n  if (m === 1) return Math.max(0, values[0]);\n  const s = Math.min(1, Math.max(0, u)) * (m - 1);\n  const i = Math.min(m - 2, Math.floor(s));\n  const f = s - i;\n  const p0 = values[Math.max(0, i - 1)];\n  const p1 = values[i];\n  const p2 = values[i + 1];\n  const p3 = values[Math.min(m - 1, i + 2)];\n  const v =\n    0.5 *\n    (2 * p1 +\n      (-p0 + p2) * f +\n      (2 * p0 - 5 * p1 + 4 * p2 - p3) * f * f +\n      (-p0 + 3 * p1 - 3 * p2 + p3) * f * f * f);\n  return Math.max(0, v);\n}\n\nconst smooth = (a: number, b: number, x: number) => {\n  const k = Math.min(1, Math.max(0, (x - a) / (b - a)));\n  return k * k * (3 - 2 * k);\n};\n\nexport function Streamgraph({\n  series = DEFAULT_SERIES,\n  highlight = 2,\n  label = \"of new projects\",\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: StreamgraphProps) {\n  const t = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const u = Math.min(width / 1280, height / 720);\n  const p = THEMES[theme];\n  const list = series.slice(0, 6);\n  const hl = Math.min(Math.max(0, Math.round(highlight)), list.length - 1);\n\n  const x0 = width * 0.105;\n  const x1 = width * 0.69;\n  const cy = height / 2;\n  const maxH = height * 0.54;\n\n  let peak = 0;\n  for (let i = 0; i <= 48; i++) {\n    const sum = list.reduce((s, x) => s + sampleValues(x.values, i / 48), 0);\n    peak = Math.max(peak, sum);\n  }\n  const scale = peak > 0 ? maxH / peak : 0;\n\n  const wave = (uu: number) =>\n    height * 0.022 * Math.sin(Math.PI * 2 * 0.9 * uu + 0.4);\n  const front = interpolate(t, GROW, [0, 1 + TIP], {\n    ...CLAMP,\n    easing: EASE_GROW,\n  });\n  const reach = Math.min(1, front);\n  const steps = Math.max(2, Math.ceil(SAMPLES * reach));\n  const span = x1 - x0;\n  const fx = x0 + span * front;\n  const feather = span * TIP * 0.6;\n  const fEnd = Math.max(fx, x0 + 1);\n  const stop = (x: number) => Math.min(1, Math.max(0, (x - x0) / (fEnd - x0)));\n  const inStop = stop(x0 + span * 0.14);\n  const outStop = Math.max(inStop, stop(fx - feather));\n  const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n\n  const columns = Array.from({ length: steps + 1 }, (_, j) => {\n    const uu = (reach * j) / steps;\n    const env =\n      (0.3 + 0.7 * Math.sin((Math.PI / 2) * smooth(0, 0.2, uu))) *\n      (1 - 0.6 * smooth(front - TIP, front, uu));\n    const vals = list.map((s) => sampleValues(s.values, uu) * env * scale);\n    const total = vals.reduce((a, b) => a + b, 0);\n    const edges = [cy + wave(uu) - total / 2];\n    for (const v of vals) edges.push(edges[edges.length - 1] + v);\n    return { x: x0 + (x1 - x0) * uu, edges };\n  });\n\n  const layerPath = (k: number) => {\n    const top = columns.map(\n      (c, j) => `${j ? \"L\" : \"M\"}${c.x.toFixed(2)} ${c.edges[k].toFixed(2)}`,\n    );\n    const bottom = [...columns]\n      .reverse()\n      .map((c) => `L${c.x.toFixed(2)} ${c.edges[k + 1].toFixed(2)}`);\n    return `${top.join(\" \")} ${bottom.join(\" \")} Z`;\n  };\n\n  const endVals = list.map((s) => sampleValues(s.values, 1));\n  const endTotal = endVals.reduce((a, b) => a + b, 0) || 1;\n  const share = endVals[hl] / endTotal;\n  const endTop = cy + wave(1) - (endTotal * scale) / 2;\n  let hlTop = endTop;\n  for (let i = 0; i < hl; i++) hlTop += endVals[i] * scale;\n  const hlMid = hlTop + (endVals[hl] * scale) / 2;\n\n  const dim = interpolate(t, [HIT - 6, HIT + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const land = spring({\n    frame: t - HIT,\n    fps,\n    config: { damping: 18, stiffness: 150, mass: 0.8 },\n  });\n  const landO = interpolate(t, [HIT, HIT + 8], [0, 1], CLAMP);\n  const blur = interpolate(t, [HIT, HIT + 14], [1, 0], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const count = interpolate(t, [HIT, HIT + 28], [0.6, 1], {\n    ...CLAMP,\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  const lead = interpolate(t, [HIT - 4, HIT + 14], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const cap = interpolate(t, [HIT + 10, HIT + 28], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n\n  const push = interpolate(t, [0, 150], [0.965, 1.005], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const stage = interpolate(t, [0, 8], [0.5, 1], CLAMP);\n\n  const sheen = interpolate(t, [0, 150], [0, 1], { ...CLAMP, easing: EASE_IO });\n\n  let grayIdx = 0;\n  const colors = list.map((_, i) =>\n    i === hl ? accent : p.grays[grayIdx++ % p.grays.length],\n  );\n\n  const numSize = 144 * u;\n  const numLeft = x1 + 44 * u;\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          transform: `scale(${push})`,\n          transformOrigin: \"45% 50%\",\n          opacity: stage,\n        }}\n      >\n        <svg\n          width={width}\n          height={height}\n          style={{ position: \"absolute\", inset: 0 }}\n          aria-hidden\n        >\n          <defs>\n            <linearGradient\n              id={`${id}g`}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={x0}\n              y1={0}\n              x2={fEnd}\n              y2={0}\n            >\n              <stop offset={0} stopColor=\"#fff\" stopOpacity={0} />\n              <stop offset={inStop} stopColor=\"#fff\" stopOpacity={1} />\n              <stop offset={outStop} stopColor=\"#fff\" stopOpacity={1} />\n              <stop offset={1} stopColor=\"#fff\" stopOpacity={0} />\n            </linearGradient>\n            <linearGradient\n              id={`${id}b`}\n              gradientUnits=\"userSpaceOnUse\"\n              x1={x0 + span * 0.08}\n              y1={hlMid + height * 0.12}\n              x2={x1}\n              y2={hlMid - height * 0.06}\n            >\n              <stop offset={0} stopColor={p.beam[0]} />\n              <stop offset={0.42 - 0.06 * sheen} stopColor={p.beam[1]} />\n              <stop offset={0.8 - 0.04 * sheen} stopColor={p.beam[2]} />\n              <stop offset={1} stopColor={p.beam[3]} />\n            </linearGradient>\n            <mask id={`${id}m`}>\n              <rect width={width} height={height} fill={`url(#${id}g)`} />\n            </mask>\n          </defs>\n          <g mask={`url(#${id}m)`}>\n            {list.map((s, k) => (\n              <path\n                key={s.label}\n                d={layerPath(k)}\n                fill={k === hl ? `url(#${id}b)` : colors[k]}\n                stroke={p.bg}\n                strokeWidth={2 * u}\n                strokeLinejoin=\"round\"\n                opacity={k === hl ? 1 : 1 - 0.38 * dim}\n              />\n            ))}\n          </g>\n          <line\n            x1={x1 + 12 * u}\n            y1={hlMid}\n            x2={x1 + 12 * u + 20 * u * lead}\n            y2={hlMid}\n            stroke={accent}\n            strokeWidth={Math.max(1, 1.5 * u)}\n            opacity={lead}\n          />\n        </svg>\n\n        <div\n          style={{\n            position: \"absolute\",\n            left: numLeft,\n            top: hlMid,\n            transform: `translateY(${-numSize * 0.47}px)`,\n          }}\n        >\n          <div\n            style={{\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: numSize,\n              lineHeight: 0.9,\n              letterSpacing: \"-0.05em\",\n              color: p.fg,\n              fontVariantNumeric: \"tabular-nums\",\n              whiteSpace: \"nowrap\",\n              opacity: landO,\n              filter: blur > 0.01 ? `blur(${blur * 8 * u}px)` : undefined,\n              transform: `translateX(${(1 - land) * -24 * u}px)`,\n            }}\n          >\n            {Math.round(share * 100 * count)}\n            <span\n              style={{\n                fontSize: numSize * 0.5,\n                letterSpacing: \"-0.02em\",\n                marginLeft: numSize * 0.03,\n                color: p.muted,\n              }}\n            >\n              %\n            </span>\n          </div>\n          <div\n            style={{\n              marginTop: 20 * u,\n              marginLeft: numSize * 0.04,\n              opacity: cap,\n              transform: `translateY(${(1 - cap) * 8 * u}px)`,\n              fontFamily: sans,\n              fontWeight: 400,\n              fontSize: 21 * u,\n              lineHeight: 1.35,\n              letterSpacing: \"-0.01em\",\n              whiteSpace: \"nowrap\",\n            }}\n          >\n            {list[hl]?.label.toLowerCase() === \"remocn\" ? (\n              <RemocnLogo fontSize={30 * u} color={p.fg} />\n            ) : (\n              <div style={{ color: p.fg, fontSize: 24 * u }}>\n                {list[hl]?.label}\n              </div>\n            )}\n            <div style={{ color: p.muted, marginTop: 6 * u }}>{label}</div>\n          </div>\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/streamgraph.tsx"
    }
  ],
  "type": "registry:component"
}
