{
  "name": "data-flow-pipes",
  "type": "registry:block",
  "description": "Pipeline running end to end: stages come up in order, pipes draw between them, and a stream of payloads hops through, lighting each stage and ticking its tally",
  "dependencies": [
    "remotion",
    "@remotion/google-fonts"
  ],
  "registryDependencies": [
    "code-syntax",
    "layout",
    "motion-tokens",
    "path-utils"
  ],
  "files": [
    {
      "path": "registry/bases/default/scenes/data-flow-pipes/index.tsx",
      "type": "registry:block",
      "content": "import { loadFont } from \"@remotion/google-fonts/Inter\";\nimport { interpolate, spring, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { CODE_THEMES } from \"@/remotion/lib/code-syntax\";\nimport { getSafeAreaPadding } from \"@/remotion/lib/layout\";\nimport { EASING } from \"@/remotion/lib/motion-tokens\";\nimport { samplePath } from \"@/remotion/lib/path-utils\";\n\nconst { fontFamily } = loadFont(\"normal\", {\n  weights: [\"400\", \"500\", \"600\", \"700\"],\n  subsets: [\"latin\"],\n});\n\nexport type PipeStage = {\n  /** Stage name, e.g. \"Transcode\". */\n  label: string;\n  /** Optional second line — the queue, the worker, the region. */\n  detail?: string;\n};\n\nexport type DataFlowPipesProps = {\n  /** Stages the payload passes through, in order. Two to five read best. */\n  stages?: PipeStage[];\n  /** Unit counted at each stage, shown next to the tally. */\n  unit?: string;\n  /** How many payloads are pushed through the pipeline. */\n  packets?: number;\n  backgroundColor?: string;\n  accentColor?: string;\n  theme?: \"dark\" | \"light\";\n  /** Animation speed multiplier. */\n  speed?: number;\n};\n\nconst DEFAULT_STAGES: PipeStage[] = [\n  { label: \"Ingest\", detail: \"s3://raw-takes\" },\n  { label: \"Transcode\", detail: \"h264 · 4 workers\" },\n  { label: \"Caption\", detail: \"whisper-large\" },\n  { label: \"Deliver\", detail: \"edge cache\" },\n];\n\n/** Beat plan in seconds. */\nconst T = {\n  nodes: 0,\n  nodeStagger: 0.12,\n  pipes: 0.5,\n  pipeStagger: 0.16,\n  firstPacket: 1.15,\n  /** Gap between payloads leaving the source. */\n  emit: 0.26,\n  /** Time a payload spends in one pipe. */\n  hop: 0.52,\n  /** How long a node stays lit after a payload lands on it. */\n  pulse: 0.5,\n} as const;\n\nconst clamp = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst CheckGlyph: React.FC<{\n  size: number;\n  color: string;\n  progress: number;\n}> = ({ size, color, progress }) => (\n  <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\">\n    <path\n      d=\"M4.8 12.6l4.9 4.9 9.5-10.4\"\n      stroke={color}\n      strokeWidth={2.6}\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeDasharray={22}\n      strokeDashoffset={22 * (1 - progress)}\n    />\n  </svg>\n);\n\n/**\n * A pipeline actually running rather than dots looping on a static graph:\n * stages come up in dependency order, the pipes draw between them, and then a\n * stream of payloads travels hop by hop — lighting each stage as it lands and\n * ticking that stage's tally — until the last one drains and completes.\n */\nexport const DataFlowPipes: React.FC<DataFlowPipesProps> = ({\n  stages = DEFAULT_STAGES,\n  unit = \"clips\",\n  packets = 9,\n  backgroundColor,\n  accentColor = \"#2DD4BF\",\n  theme = \"dark\",\n  speed = 1,\n}) => {\n  const frame = useCurrentFrame();\n  const { fps, width, height } = useVideoConfig();\n  const palette = CODE_THEMES[theme];\n  const safe = getSafeAreaPadding({ width, height });\n\n  /** Seconds elapsed, stretched by `speed`, so every beat below is in seconds. */\n  const now = ((frame / fps) * speed);\n  const at = (seconds: number) => (seconds * fps) / speed;\n  const ease = (from: number, to: number, easing = EASING.enter) =>\n    interpolate(frame, [at(from), at(to)], [0, 1], { easing, ...clamp });\n\n  const stage = {\n    x: safe.paddingLeft,\n    y: safe.paddingTop,\n    w: width - safe.paddingLeft - safe.paddingRight,\n    h: height - safe.paddingTop - safe.paddingBottom,\n  };\n  const portrait = height > width;\n  const u = Math.min(stage.w / 1120, stage.h / 620);\n  const list = stages.slice(0, 5);\n  const count = list.length;\n  const hops = Math.max(count - 1, 1);\n\n  const nodeW = portrait\n    ? Math.min(420 * u, stage.w * 0.72)\n    : Math.min(232 * u, stage.w / count - 26 * u);\n  const nodeH = 134 * u;\n\n  // Alternating cross-axis offsets give the run its shape, so the pipes read\n  // as plumbing rather than as one straight rule through the middle.\n  const swing = portrait ? stage.w * 0.08 : stage.h * 0.085;\n  const center = (index: number) => {\n    const along = ((index + 0.5) / count) * (portrait ? stage.h : stage.w);\n    const across =\n      (portrait ? stage.w : stage.h) / 2 +\n      (index % 2 === 0 ? -swing : swing) * (count > 2 ? 1 : 0);\n    return portrait ? { x: across, y: along } : { x: along, y: across };\n  };\n\n  /** Pipe between two stages, leaving and entering along the flow axis so the\n      run reads as plumbing rather than as a diagonal rule. */\n  const pipePath = (index: number) => {\n    const from = center(index);\n    const to = center(index + 1);\n    const exit = portrait\n      ? { x: from.x, y: from.y + nodeH / 2 }\n      : { x: from.x + nodeW / 2, y: from.y };\n    const entry = portrait\n      ? { x: to.x, y: to.y - nodeH / 2 }\n      : { x: to.x - nodeW / 2, y: to.y };\n    const bend = portrait\n      ? Math.abs(entry.y - exit.y) * 0.55\n      : Math.abs(entry.x - exit.x) * 0.55;\n    const c1 = portrait\n      ? { x: exit.x, y: exit.y + bend }\n      : { x: exit.x + bend, y: exit.y };\n    const c2 = portrait\n      ? { x: entry.x, y: entry.y - bend }\n      : { x: entry.x - bend, y: entry.y };\n    return `M ${exit.x} ${exit.y} C ${c1.x} ${c1.y} ${c2.x} ${c2.y} ${entry.x} ${entry.y}`;\n  };\n\n  const pipes = Array.from({ length: hops }, (_, index) => pipePath(index));\n\n  /** Seconds at which payload `k` enters hop `h` (h = 0 leaves the source). */\n  const departure = (packet: number, hop: number) =>\n    T.firstPacket + packet * T.emit + hop * T.hop;\n  const lastArrival = departure(packets - 1, hops);\n\n  const inFlight = Array.from({ length: packets }, (_, k) => {\n    const elapsed = now - departure(k, 0);\n    if (elapsed < 0) {\n      return null;\n    }\n    const hop = Math.floor(elapsed / T.hop);\n    if (hop >= hops) {\n      return null;\n    }\n    const progress = elapsed / T.hop - hop;\n    const point = samplePath(pipes[hop]!, progress);\n    return { key: k, hop, progress, point };\n  }).filter(Boolean) as {\n    key: number;\n    hop: number;\n    progress: number;\n    point: { x: number; y: number };\n  }[];\n\n  /** Payloads that have reached stage `index`, and how hot that landing is. */\n  const arrivals = (index: number) => {\n    let tally = 0;\n    let heat = 0;\n    for (let k = 0; k < packets; k += 1) {\n      const landed = departure(k, index);\n      if (now >= landed) {\n        tally += 1;\n        heat = Math.max(\n          heat,\n          interpolate(now, [landed, landed + T.pulse], [1, 0], clamp),\n        );\n      }\n    }\n    return { tally, heat };\n  };\n\n  const drained = ease(lastArrival, lastArrival + 0.45);\n  const strokeW = 3.2 * u;\n\n  return (\n    <div\n      style={{\n        width,\n        height,\n        background: backgroundColor ?? palette.page,\n        fontFamily,\n        position: \"relative\",\n        overflow: \"hidden\",\n      }}\n    >\n      <div\n        style={{\n          position: \"absolute\",\n          inset: 0,\n          background: `radial-gradient(ellipse 70% 60% at 50% 50%, ${accentColor}14, transparent 72%)`,\n        }}\n      />\n\n      <svg\n        width={width}\n        height={height}\n        style={{ position: \"absolute\", left: 0, top: 0 }}\n      >\n        <g transform={`translate(${stage.x} ${stage.y})`}>\n          {pipes.map((d, index) => {\n            const draw = ease(\n              T.pipes + index * T.pipeStagger,\n              T.pipes + index * T.pipeStagger + 0.55,\n            );\n            const carrying = inFlight.some((packet) => packet.hop === index);\n            return (\n              <g key={`pipe-${index}`}>\n                <path\n                  d={d}\n                  fill=\"none\"\n                  stroke={palette.border}\n                  strokeWidth={strokeW}\n                  strokeLinecap=\"round\"\n                  pathLength={1}\n                  strokeDasharray={1}\n                  strokeDashoffset={1 - draw}\n                />\n                <path\n                  d={d}\n                  fill=\"none\"\n                  stroke={accentColor}\n                  strokeWidth={strokeW}\n                  strokeLinecap=\"round\"\n                  opacity={draw * (carrying ? 0.5 : 0.16)}\n                  pathLength={1}\n                  strokeDasharray={1}\n                  strokeDashoffset={1 - draw}\n                />\n              </g>\n            );\n          })}\n\n          {inFlight.map((packet) => (\n            <g key={`packet-${packet.key}`}>\n              {[0.16, 0.08, 0].map((lag, index) => {\n                const trail = samplePath(\n                  pipes[packet.hop]!,\n                  Math.max(packet.progress - lag, 0),\n                );\n                const radius = (index === 2 ? 8.5 : 6.5 - index) * u;\n                return (\n                  <circle\n                    key={lag}\n                    cx={trail.x}\n                    cy={trail.y}\n                    r={radius}\n                    fill={accentColor}\n                    opacity={index === 2 ? 1 : 0.22 + index * 0.14}\n                  />\n                );\n              })}\n            </g>\n          ))}\n        </g>\n      </svg>\n\n      <div\n        style={{\n          position: \"absolute\",\n          left: stage.x,\n          top: stage.y,\n          width: stage.w,\n          height: stage.h,\n        }}\n      >\n        {list.map((node, index) => {\n          const point = center(index);\n          const enter = spring({\n            frame: frame - at(T.nodes + index * T.nodeStagger),\n            fps,\n            config: { damping: 16, stiffness: 140, mass: 0.7 },\n          });\n          const { tally, heat } = arrivals(index);\n          const isSink = index === count - 1;\n          const complete = isSink ? drained : 0;\n\n          return (\n            <div\n              key={node.label}\n              style={{\n                position: \"absolute\",\n                left: point.x - nodeW / 2,\n                top: point.y - nodeH / 2,\n                width: nodeW,\n                height: nodeH,\n                borderRadius: 18 * u,\n                background: palette.window,\n                border: `1px solid ${\n                  heat > 0.02 || complete > 0 ? `${accentColor}99` : palette.border\n                }`,\n                boxShadow: `inset 0 1px 0 ${palette.highlight}, 0 ${\n                  16 * u\n                }px ${46 * u}px ${palette.shadow}, 0 0 ${\n                  38 * u * heat\n                }px ${accentColor}44`,\n                opacity: enter,\n                transform: `scale(${\n                  interpolate(enter, [0, 1], [0.9, 1]) + heat * 0.02\n                })`,\n                padding: `${16 * u}px ${18 * u}px`,\n                display: \"flex\",\n                flexDirection: \"column\",\n                justifyContent: \"center\",\n                gap: 6 * u,\n                overflow: \"hidden\",\n              }}\n            >\n              <div\n                style={{\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  gap: 9 * u,\n                  color: palette.fg,\n                  fontSize: 25 * u,\n                  fontWeight: 600,\n                  letterSpacing: \"-0.01em\",\n                }}\n              >\n                <span\n                  style={{\n                    width: 9 * u,\n                    height: 9 * u,\n                    borderRadius: \"50%\",\n                    background: accentColor,\n                    opacity: 0.3 + heat * 0.7,\n                    boxShadow: `0 0 ${12 * u * heat}px ${accentColor}`,\n                    flexShrink: 0,\n                  }}\n                />\n                <span style={{ whiteSpace: \"nowrap\" }}>{node.label}</span>\n                {complete > 0 ? (\n                  <span style={{ marginLeft: \"auto\", display: \"flex\" }}>\n                    <CheckGlyph\n                      size={22 * u}\n                      color={accentColor}\n                      progress={complete}\n                    />\n                  </span>\n                ) : null}\n              </div>\n              {node.detail ? (\n                <div\n                  style={{\n                    color: palette.faint,\n                    fontSize: 17 * u,\n                    whiteSpace: \"nowrap\",\n                    overflow: \"hidden\",\n                    textOverflow: \"ellipsis\",\n                  }}\n                >\n                  {node.detail}\n                </div>\n              ) : null}\n              <div\n                style={{\n                  marginTop: 2 * u,\n                  color: tally > 0 ? palette.dim : palette.faint,\n                  fontSize: 18 * u,\n                  fontVariantNumeric: \"tabular-nums\",\n                }}\n              >\n                {tally} {unit}\n              </div>\n              {/* Fill line — how much of the stream this stage has seen */}\n              <div\n                style={{\n                  position: \"absolute\",\n                  left: 0,\n                  bottom: 0,\n                  height: 3 * u,\n                  width: `${(tally / packets) * 100}%`,\n                  background: accentColor,\n                  opacity: 0.8,\n                }}\n              />\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "blocks",
    "drive": "data",
    "tier": "advanced",
    "tags": [
      "data"
    ]
  }
}