{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flow-builder",
  "title": "Flow Builder",
  "description": "A workflow canvas connects nodes, branches by plan and publishes with a toast.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/flow-builder/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadMono } from \"@remotion/google-fonts/GeistMono\";\nimport { loadFont as loadHanken } from \"@remotion/google-fonts/HankenGrotesk\";\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\nimport type { GradientParams } from \"@/lib/remocn/gsh/gradient-engine\";\nimport { GshGround } from \"@/lib/remocn/gsh/gsh-ground\";\nimport type { GshPresetId } from \"@/lib/remocn/gsh/presets\";\n\nconst hanken = loadHanken(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\nconst mono = loadMono(\"normal\", { weights: [\"400\"], subsets: [\"latin\"] });\n\nexport type FlowIcon = \"bolt\" | \"kite\" | \"split\" | \"mail\" | \"hash\" | \"sheet\";\n\nexport interface FlowNode {\n  id: string;\n  title: string;\n  subtitle: string;\n  icon: FlowIcon;\n  /** Icon tint. */\n  color: string;\n  /** Run time shown once the packet passes, e.g. \"0.4s\". */\n  duration: string;\n  width?: number;\n  /** Ids of the nodes this one connects to. */\n  next?: string[];\n}\n\nexport interface FlowBuilderProps {\n  nodes?: FlowNode[];\n  title?: string;\n  workspace?: string;\n  toast?: string;\n  theme?: \"light\" | \"dark\";\n  accent?: string;\n  speed?: number;\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst DEFAULT_NODES: FlowNode[] = [\n  {\n    id: \"signup\",\n    title: \"New signup\",\n    subtitle: \"Webhook · /signup\",\n    icon: \"bolt\",\n    color: \"#E8590C\",\n    duration: \"0.1s\",\n    next: [\"enrich\"],\n  },\n  {\n    id: \"enrich\",\n    title: \"Enrich lead\",\n    subtitle: \"Company and role\",\n    icon: \"kite\",\n    color: \"#0E9F9A\",\n    duration: \"1.2s\",\n    next: [\"paths\"],\n  },\n  {\n    id: \"paths\",\n    title: \"Split by plan\",\n    subtitle: \"2 paths\",\n    icon: \"split\",\n    color: \"#7A7F8C\",\n    duration: \"12ms\",\n    width: 176,\n    next: [\"email\", \"notify\"],\n  },\n  {\n    id: \"email\",\n    title: \"Welcome email\",\n    subtitle: \"Free plan · Day 0\",\n    icon: \"mail\",\n    color: \"#D6455D\",\n    duration: \"0.4s\",\n  },\n  {\n    id: \"notify\",\n    title: \"Notify #growth\",\n    subtitle: \"Pro plan · post\",\n    icon: \"hash\",\n    color: \"#3B82C4\",\n    duration: \"0.3s\",\n  },\n];\n\nconst HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst CARD = { mass: 0.8, damping: 18, stiffness: 160 };\nconst CHIP = { mass: 0.5, damping: 12, stiffness: 200 };\nconst PRESS = { mass: 0.4, damping: 14, stiffness: 420 };\nconst ENTER = Easing.bezier(0.32, 0.72, 0, 1);\nconst MOVE = Easing.bezier(0.65, 0, 0.35, 1);\nconst CAMERA = Easing.bezier(0.87, 0, 0.13, 1);\nconst CL = { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" } as const;\nconst clamp = (v: number, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, v));\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\nconst NODE_H = 68;\nconst BAR = 56;\nconst B = {\n  node: 10,\n  nodeStep: 6,\n  cursor: 46,\n  run: 72,\n  level: 18,\n  travel: 10,\n};\n\nfunction withAlpha(color: string, alpha: number) {\n  const hex = color.replace(\"#\", \"\");\n  const full = hex.length === 3 ? hex.replace(/(.)/g, \"$1$1\") : hex;\n  if (!/^[0-9a-fA-F]{6}$/.test(full)) return color;\n  const n = Number.parseInt(full, 16);\n  return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`;\n}\n\nconst ICON_PATHS: Record<FlowIcon, string> = {\n  bolt: \"M9 1.8 3.6 9h4l-.9 5.2L12.4 7h-4z\",\n  kite: \"M8 1.8 12.6 7 8 12.2 3.4 7zM8 1.8v10.4M3.4 7h9.2\",\n  split:\n    \"M2.5 8h3.2M5.7 8c2.2 0 2.6-4.2 5.4-4.2h2.4M5.7 8c2.2 0 2.6 4.2 5.4 4.2h2.4\",\n  mail: \"M2.6 4.2h10.8v7.6H2.6zM2.8 4.6 8 8.6l5.2-4\",\n  hash: \"M6 2.5 4.8 13.5M11.2 2.5 10 13.5M2.8 6h11M2.2 10h11\",\n  sheet: \"M3 2.5h10v11H3zM3 6.2h10M3 9.8h10M7.4 2.5v11\",\n};\n\nfunction palette(theme: \"light\" | \"dark\") {\n  const dark = theme === \"dark\";\n  return {\n    dark,\n    window: dark ? \"#0F1116\" : \"#FCFCFB\",\n    canvas: dark ? \"#0B0D11\" : \"#F5F4F1\",\n    dot: dark ? \"rgba(255,255,255,0.07)\" : \"rgba(30,28,24,0.11)\",\n    card: dark ? \"#161920\" : \"#FFFFFF\",\n    edge: dark ? \"rgba(255,255,255,0.22)\" : \"rgba(30,28,24,0.26)\",\n    text: dark ? \"#ECEDEF\" : \"#1B1A18\",\n    muted: dark ? \"#989BA4\" : \"#6B6862\",\n    faint: dark ? \"#62666F\" : \"#A3A09A\",\n    rule: dark ? \"rgba(255,255,255,0.07)\" : \"rgba(30,28,24,0.08)\",\n    success: dark ? \"#43D18C\" : \"#17914F\",\n    depth: dark\n      ? \"0 0 0 1px rgba(255,255,255,0.09), 0 12px 24px -12px rgba(0,0,0,0.6), 0 40px 90px -24px rgba(0,0,0,0.75)\"\n      : \"0 0 0 1px rgba(20,22,30,0.06), 0 2px 4px rgba(20,22,30,0.04), 0 40px 90px -24px rgba(20,30,80,0.3)\",\n    cardDepth: dark\n      ? \"0 0 0 1px rgba(255,255,255,0.08)\"\n      : \"0 0 0 1px rgba(30,28,24,0.07), 0 1px 2px rgba(30,28,24,0.05), 0 6px 14px -6px rgba(30,28,24,0.1)\",\n    pill: dark ? \"rgba(255,255,255,0.06)\" : \"rgba(30,28,24,0.05)\",\n  };\n}\ntype Pal = ReturnType<typeof palette>;\n\ninterface Pt {\n  x: number;\n  y: number;\n}\ninterface Key extends Pt {\n  t: number;\n}\n\nfunction Cursor({ press }: { press: number }) {\n  return (\n    <svg\n      width={22}\n      height={26}\n      viewBox=\"0 0 20 24\"\n      style={{\n        transformOrigin: \"2px 2px\",\n        transform: `scale(${1 - 0.1 * press})`,\n        filter: \"drop-shadow(0 2px 4px rgba(0,0,0,0.3))\",\n        overflow: \"visible\",\n      }}\n    >\n      <path\n        d=\"M2 2 L2 19.5 L6.6 15.4 L9.8 22.4 L12.9 21 L9.8 14.2 L16 14.2 Z\"\n        fill=\"#FFFFFF\"\n        stroke=\"#16171A\"\n        strokeWidth={1.1}\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction Spinner({\n  color,\n  track,\n  frame,\n  size = 14,\n}: {\n  color: string;\n  track: string;\n  frame: number;\n  size?: number;\n}) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 16 16\"\n      style={{ transform: `rotate(${frame * 18}deg)`, flexShrink: 0 }}\n    >\n      <circle\n        cx={8}\n        cy={8}\n        r={6}\n        fill=\"none\"\n        stroke={track}\n        strokeWidth={1.5}\n      />\n      <path\n        d=\"M8 2 A6 6 0 0 1 14 8\"\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={1.5}\n        strokeLinecap=\"round\"\n      />\n    </svg>\n  );\n}\n\nfunction Check({\n  color,\n  size,\n  draw,\n}: {\n  color: string;\n  size: number;\n  draw: number;\n}) {\n  return (\n    <svg\n      width={size}\n      height={size}\n      viewBox=\"0 0 16 16\"\n      style={{ flexShrink: 0 }}\n    >\n      <path\n        d=\"M3.5 8.4 6.6 11.4 12.5 5\"\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={1.5}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        pathLength={1}\n        strokeDasharray=\"1 1\"\n        strokeDashoffset={1 - draw}\n      />\n    </svg>\n  );\n}\n\nfunction NodeCard({\n  node,\n  pal,\n  accent,\n  active,\n}: {\n  node: FlowNode;\n  pal: Pal;\n  accent: string;\n  active: number;\n}) {\n  return (\n    <div\n      style={{\n        width: \"100%\",\n        height: \"100%\",\n        borderRadius: 12,\n        background: pal.card,\n        boxShadow: `0 0 0 ${1.5 * active}px ${withAlpha(accent, active)}, 0 0 0 ${5 * active}px ${withAlpha(accent, 0.14 * active)}, ${pal.cardDepth}`,\n        padding: \"0 14px\",\n        boxSizing: \"border-box\",\n        display: \"flex\",\n        alignItems: \"center\",\n        gap: 12,\n      }}\n    >\n      <div\n        style={{\n          width: 34,\n          height: 34,\n          borderRadius: 9,\n          background: withAlpha(node.color, pal.dark ? 0.18 : 0.11),\n          display: \"flex\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          flexShrink: 0,\n        }}\n      >\n        <svg width={18} height={18} viewBox=\"0 0 16 16\">\n          <path\n            d={ICON_PATHS[node.icon]}\n            fill=\"none\"\n            stroke={node.color}\n            strokeWidth={1.4}\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        </svg>\n      </div>\n      <div style={{ minWidth: 0 }}>\n        <div\n          style={{\n            fontSize: 16,\n            lineHeight: \"20px\",\n            color: pal.text,\n            whiteSpace: \"nowrap\",\n            letterSpacing: \"-0.01em\",\n          }}\n        >\n          {node.title}\n        </div>\n        <div\n          style={{\n            fontSize: 14,\n            lineHeight: \"18px\",\n            marginTop: 2,\n            color: pal.muted,\n            whiteSpace: \"nowrap\",\n          }}\n        >\n          {node.subtitle}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport function FlowBuilder({\n  nodes = DEFAULT_NODES,\n  title = \"Welcome new signups\",\n  workspace = \"remocn\",\n  toast = \"Live · runs on every signup\",\n  theme = \"light\",\n  accent = \"#3B5BFD\",\n  speed = 1,\n  ground,\n  groundParams,\n}: FlowBuilderProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width, height, fps } = useVideoConfig();\n  const tall = height > width;\n  const SW = tall ? 720 : 1280;\n  const SH = tall ? 1280 : 720;\n  const u = Math.min(width / SW, height / SH);\n  const pal = palette(theme);\n  const sp = (at: number, config: typeof CARD, dur?: number) =>\n    spring({ frame: frame - at, fps, config, durationInFrames: dur });\n\n  const WIN = tall\n    ? { x: 80, y: 290, w: 560, h: 860 }\n    : { x: 150, y: 110, w: 980, h: 500 };\n  const CW = WIN.w;\n  const CH = WIN.h - BAR;\n\n  const byId = new Map(nodes.map((n) => [n.id, n]));\n  const parents = new Map<string, string>();\n  for (const n of nodes)\n    for (const id of n.next ?? []) if (!parents.has(id)) parents.set(id, n.id);\n  const depth = new Map<string, number>();\n  const queue = nodes.filter((n) => !parents.has(n.id)).map((n) => n.id);\n  for (const id of queue) depth.set(id, 0);\n  while (queue.length) {\n    const id = queue.shift() as string;\n    for (const nx of byId.get(id)?.next ?? []) {\n      if (!depth.has(nx)) {\n        depth.set(nx, (depth.get(id) ?? 0) + 1);\n        queue.push(nx);\n      }\n    }\n  }\n  const maxDepth = Math.max(0, ...depth.values());\n  const levels = Array.from({ length: maxDepth + 1 }, (_, d) =>\n    nodes.filter((n) => (depth.get(n.id) ?? 0) === d),\n  );\n\n  const box = new Map<string, { x: number; y: number; w: number }>();\n  if (tall) {\n    const pitch = 150;\n    const top = (CH - (maxDepth * pitch + NODE_H)) / 2 + 8;\n    levels.forEach((row, d) => {\n      const gap = 28;\n      const w =\n        row.length === 1\n          ? 264\n          : Math.min(240, (CW - 64 - gap * (row.length - 1)) / row.length);\n      const total = row.length * w + (row.length - 1) * gap;\n      row.forEach((n, k) => {\n        box.set(n.id, {\n          x: (CW - total) / 2 + k * (w + gap),\n          y: top + d * pitch,\n          w,\n        });\n      });\n    });\n  } else {\n    const colW = levels.map((row) =>\n      Math.max(...row.map((n) => n.width ?? 196)),\n    );\n    const gaps = levels\n      .slice(1)\n      .map((_, i) => (i === levels.length - 2 ? 66 : 42));\n    const total =\n      colW.reduce((a, b) => a + b, 0) + gaps.reduce((a, b) => a + b, 0);\n    let x = (CW - total) / 2;\n    levels.forEach((row, d) => {\n      const pitch = NODE_H + 76;\n      const top = CH / 2 - NODE_H / 2 - ((row.length - 1) * pitch) / 2 + 6;\n      row.forEach((n, k) => {\n        const w = n.width ?? 196;\n        box.set(n.id, { x: x + (colW[d] - w) / 2, y: top + k * pitch, w });\n      });\n      x += colW[d] + (gaps[d] ?? 0);\n    });\n  }\n\n  const edges = nodes.flatMap((n) =>\n    (n.next ?? []).flatMap((id) => {\n      const to = byId.get(id);\n      const fb = box.get(n.id);\n      const tb = to ? box.get(id) : undefined;\n      if (!to || !fb || !tb) return [];\n      const a = tall\n        ? { x: fb.x + fb.w / 2, y: fb.y + NODE_H }\n        : { x: fb.x + fb.w, y: fb.y + NODE_H / 2 };\n      const b = tall\n        ? { x: tb.x + tb.w / 2, y: tb.y }\n        : { x: tb.x, y: tb.y + NODE_H / 2 };\n      const c1 = tall\n        ? { x: a.x, y: lerp(a.y, b.y, 0.55) }\n        : { x: lerp(a.x, b.x, 0.55), y: a.y };\n      const c2 = tall\n        ? { x: b.x, y: lerp(a.y, b.y, 0.45) }\n        : { x: lerp(a.x, b.x, 0.45), y: b.y };\n      return [{ from: n, to, a, c1, c2, b }];\n    }),\n  );\n  type Edge = (typeof edges)[number];\n  const bez = (e: Edge, t: number): Pt => {\n    const m = 1 - t;\n    const k = (p: keyof Pt) =>\n      m * m * m * e.a[p] +\n      3 * m * m * t * e.c1[p] +\n      3 * m * t * t * e.c2[p] +\n      t * t * t * e.b[p];\n    return { x: k(\"x\"), y: k(\"y\") };\n  };\n\n  const dropAt = (i: number) => B.node + i * B.nodeStep;\n  const arriveAt = (d: number) => B.run + 4 + d * B.level;\n  const checkAt = (d: number) => arriveAt(d) + 8;\n  const runEnd = checkAt(maxDepth);\n  const PUBLISH = runEnd + 12;\n\n  const RUN_W = 88;\n  const PUB_W = 112;\n  const pubX = CW - 12 - PUB_W;\n  const runX = pubX - 8 - RUN_W;\n  const runPt = { x: runX + RUN_W / 2 + 4, y: BAR / 2 + 4 };\n  const pubPt = { x: pubX + PUB_W / 2 + 8, y: BAR / 2 + 4 };\n  const keys: Key[] = [\n    { t: B.cursor, x: CW + 70, y: WIN.h * 0.62 },\n    { t: B.run - 2, ...runPt },\n    { t: runEnd, ...runPt },\n    { t: PUBLISH - 2, ...pubPt },\n    { t: PUBLISH + 18, ...pubPt },\n    { t: PUBLISH + 42, x: pubPt.x - 70, y: pubPt.y + 120 },\n  ];\n  let cur: Pt = keys[0];\n  for (let i = 1; i < keys.length; i++) {\n    const a = keys[i - 1];\n    const b = keys[i];\n    if (frame < a.t) break;\n    const p = spring({\n      frame: frame - a.t,\n      fps,\n      config: { mass: 0.9, damping: 22, stiffness: 140 },\n      durationInFrames: b.t - a.t,\n    });\n    const dx = b.x - a.x;\n    const dy = b.y - a.y;\n    const d = Math.hypot(dx, dy) || 1;\n    const arc = Math.sin(clamp(p) * Math.PI) * Math.min(42, d * 0.14);\n    cur = {\n      x: lerp(a.x, b.x, p) + (dy / d) * arc,\n      y: lerp(a.y, b.y, p) - (dx / d) * arc,\n    };\n  }\n  const pressAt = (at: number) => sp(at - 2, PRESS) - sp(at + 3, PRESS);\n  const runPress = pressAt(B.run);\n  const pubPress = pressAt(PUBLISH);\n  const press = Math.max(runPress, pubPress);\n  const cursorIn = interpolate(frame, [B.cursor, B.cursor + 8], [0, 1], {\n    ...CL,\n    easing: ENTER,\n  });\n\n  const win = sp(0, HEAVY);\n  const winFade = interpolate(frame, [0, 12], [0, 1], { ...CL, easing: ENTER });\n  const running = frame >= B.run && frame < runEnd + 2;\n  const published = frame >= PUBLISH + 2;\n  const pubS = sp(PUBLISH + 2, CARD);\n  const toastS = sp(PUBLISH + 8, HEAVY);\n  const toastFade = interpolate(frame, [PUBLISH + 8, PUBLISH + 18], [0, 1], {\n    ...CL,\n    easing: ENTER,\n  });\n  const cam = interpolate(frame, [0, 205], [1, 1.035], {\n    ...CL,\n    easing: CAMERA,\n  });\n  const headIn = interpolate(frame, [4, 20], [0, 1], { ...CL, easing: ENTER });\n\n  const nodeEls = nodes.map((n, i) => {\n    const bx = box.get(n.id);\n    if (!bx) return null;\n    const s = sp(dropAt(i), CARD);\n    const o = interpolate(frame, [dropAt(i), dropAt(i) + 8], [0, 1], {\n      ...CL,\n      easing: ENTER,\n    });\n    const d = depth.get(n.id) ?? 0;\n    const active = interpolate(\n      frame,\n      [arriveAt(d) - 1, arriveAt(d) + 4, checkAt(d), checkAt(d) + 6],\n      [0, 1, 1, 0],\n      { ...CL, easing: MOVE },\n    );\n    const chipOn = frame >= arriveAt(d);\n    const done = frame >= checkAt(d);\n    const chipS = sp(done ? checkAt(d) : arriveAt(d), CHIP);\n    const draw = interpolate(frame, [checkAt(d) + 2, checkAt(d) + 10], [0, 1], {\n      ...CL,\n      easing: ENTER,\n    });\n    return (\n      <div key={n.id}>\n        <div\n          style={{\n            position: \"absolute\",\n            left: bx.x,\n            top: bx.y,\n            width: bx.w,\n            height: NODE_H,\n            opacity: o,\n            transform: `translateY(${(1 - s) * 18}px) scale(${0.94 + 0.06 * s})`,\n            zIndex: 3,\n          }}\n        >\n          <NodeCard node={n} pal={pal} accent={accent} active={active} />\n        </div>\n        {chipOn ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: bx.x + bx.w - 4,\n              top: bx.y - 28,\n              transform: `translateX(-100%) scale(${0.7 + 0.3 * chipS})`,\n              transformOrigin: \"100% 100%\",\n              opacity: clamp(chipS * 2),\n              display: \"flex\",\n              alignItems: \"center\",\n              gap: 5,\n              fontFamily: mono.fontFamily,\n              fontSize: 13,\n              color: done ? pal.success : pal.muted,\n              whiteSpace: \"nowrap\",\n              zIndex: 3,\n            }}\n          >\n            {done ? (\n              <Check color={pal.success} size={14} draw={draw} />\n            ) : (\n              <Spinner\n                color={accent}\n                track={pal.rule}\n                frame={frame}\n                size={13}\n              />\n            )}\n            {done ? n.duration : \"running\"}\n          </div>\n        ) : null}\n      </div>\n    );\n  });\n\n  const edgeEls = edges.map((e) => {\n    const ti = nodes.indexOf(e.to);\n    const fi = nodes.indexOf(e.from);\n    const start = Math.max(dropAt(ti), dropAt(fi)) + 6;\n    const draw = interpolate(frame, [start, start + 12], [0, 1], {\n      ...CL,\n      easing: MOVE,\n    });\n    const d0 = depth.get(e.from.id) ?? 0;\n    const lit = interpolate(\n      frame,\n      [checkAt(d0), checkAt(d0) + B.travel],\n      [0, 1],\n      { ...CL, easing: MOVE },\n    );\n    const p = `M ${e.a.x} ${e.a.y} C ${e.c1.x} ${e.c1.y} ${e.c2.x} ${e.c2.y} ${e.b.x} ${e.b.y}`;\n    return (\n      <g key={`${e.from.id}-${e.to.id}`}>\n        <path\n          d={p}\n          fill=\"none\"\n          stroke={pal.edge}\n          strokeWidth={1.25}\n          pathLength={1}\n          strokeDasharray=\"1 1\"\n          strokeDashoffset={1 - draw}\n        />\n        {lit > 0 ? (\n          <path\n            d={p}\n            fill=\"none\"\n            stroke={accent}\n            strokeWidth={1.75}\n            strokeLinecap=\"round\"\n            pathLength={1}\n            strokeDasharray=\"1 1\"\n            strokeDashoffset={1 - lit}\n          />\n        ) : null}\n        <circle\n          cx={e.b.x}\n          cy={e.b.y}\n          r={3}\n          fill={lit >= 1 ? accent : pal.card}\n          stroke={lit >= 1 ? accent : pal.edge}\n          strokeWidth={1.25}\n          opacity={clamp((draw - 0.8) / 0.2)}\n        />\n      </g>\n    );\n  });\n\n  const packets = edges.map((e) => {\n    const d0 = depth.get(e.from.id) ?? 0;\n    const t = (frame - checkAt(d0)) / B.travel;\n    if (t < 0 || t > 1.25) return null;\n    const pt = bez(e, MOVE(clamp(t)));\n    const fade = interpolate(t, [0, 0.12, 1, 1.25], [0, 1, 1, 0], CL);\n    return (\n      <div\n        key={`k-${e.to.id}`}\n        style={{\n          position: \"absolute\",\n          left: pt.x - 5,\n          top: pt.y - 5,\n          width: 10,\n          height: 10,\n          borderRadius: 999,\n          background: accent,\n          boxShadow: `0 0 0 3px ${withAlpha(accent, 0.2)}, 0 0 14px ${withAlpha(accent, 0.6)}`,\n          opacity: fade,\n          zIndex: 4,\n        }}\n      />\n    );\n  });\n\n  const btn: React.CSSProperties = {\n    position: \"absolute\",\n    top: (BAR - 34) / 2,\n    height: 34,\n    borderRadius: 9,\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    gap: 7,\n    fontSize: 15,\n    boxSizing: \"border-box\",\n    whiteSpace: \"nowrap\",\n  };\n\n  return (\n    <AbsoluteFill\n      style={{\n        fontFamily: hanken.fontFamily,\n        WebkitFontSmoothing: \"antialiased\",\n        overflow: \"hidden\",\n      }}\n    >\n      <GshGround\n        preset={ground ?? (pal.dark ? \"earthshine\" : \"glasswork\")}\n        params={\n          groundParams ??\n          (pal.dark || ground\n            ? undefined\n            : { colors: [\"#b9c8ff\", \"#3552ff\", \"#1d2f9e\", \"#8fb0ff\"] })\n        }\n        timeOffset={pal.dark ? 7 : 6}\n        timeScale={0.4}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left: (width - SW * u) / 2,\n          top: (height - SH * u) / 2,\n          width: SW,\n          height: SH,\n          transform: `scale(${u})`,\n          transformOrigin: \"0 0\",\n        }}\n      >\n        {tall ? (\n          <div\n            style={{\n              position: \"absolute\",\n              left: WIN.x,\n              top: 150,\n              width: WIN.w,\n              opacity: headIn,\n              transform: `translateY(${(1 - headIn) * 18}px)`,\n              color: pal.dark ? \"#F2F3F5\" : \"#16181D\",\n            }}\n          >\n            <div\n              style={{\n                fontSize: 52,\n                fontWeight: 300,\n                letterSpacing: \"-0.035em\",\n                lineHeight: 1.04,\n              }}\n            >\n              {title}\n            </div>\n          </div>\n        ) : null}\n        <div\n          style={{\n            position: \"absolute\",\n            left: WIN.x,\n            top: WIN.y,\n            width: WIN.w,\n            height: WIN.h,\n            opacity: winFade,\n            transformOrigin: \"50% 45%\",\n            transform: `translateY(${(1 - win) * 60}px) scale(${(0.96 + 0.04 * win) * cam})`,\n          }}\n        >\n          <div\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              borderRadius: 16,\n              overflow: \"hidden\",\n              background: pal.window,\n              boxShadow: pal.depth,\n            }}\n          >\n            <div\n              style={{\n                position: \"absolute\",\n                left: 0,\n                top: 0,\n                right: 0,\n                height: BAR,\n                display: \"flex\",\n                alignItems: \"center\",\n                gap: 10,\n                padding: \"0 18px\",\n                boxShadow: `inset 0 -1px 0 ${pal.rule}`,\n                fontSize: 15,\n              }}\n            >\n              <span style={{ color: pal.muted }}>{workspace}</span>\n              <span style={{ color: pal.faint }}>/</span>\n              <span style={{ color: pal.text }}>\n                {tall ? \"Workflows\" : title}\n              </span>\n              <div\n                style={{\n                  position: \"relative\",\n                  height: 24,\n                  padding: \"0 9px\",\n                  borderRadius: 7,\n                  fontSize: 13,\n                  display: \"flex\",\n                  alignItems: \"center\",\n                  gap: 6,\n                  background: published\n                    ? withAlpha(pal.success, 0.12)\n                    : pal.pill,\n                  color: published ? pal.success : pal.muted,\n                  transform: `scale(${published ? 0.9 + 0.1 * pubS : 1})`,\n                }}\n              >\n                {published ? (\n                  <div\n                    style={{\n                      width: 6,\n                      height: 6,\n                      borderRadius: 3,\n                      background: pal.success,\n                    }}\n                  />\n                ) : null}\n                {published ? \"Live\" : \"Draft\"}\n              </div>\n            </div>\n            <div\n              style={{\n                position: \"absolute\",\n                left: 0,\n                top: BAR,\n                right: 0,\n                bottom: 0,\n                background: pal.canvas,\n                backgroundImage: `radial-gradient(circle, ${pal.dot} 1px, transparent 1.4px)`,\n                backgroundSize: \"22px 22px\",\n              }}\n            >\n              <svg\n                width={CW}\n                height={CH}\n                style={{\n                  position: \"absolute\",\n                  left: 0,\n                  top: 0,\n                  overflow: \"visible\",\n                }}\n              >\n                {edgeEls}\n              </svg>\n              {packets}\n              {nodeEls}\n              {frame >= PUBLISH + 8 ? (\n                <div\n                  style={{\n                    position: \"absolute\",\n                    left: CW / 2,\n                    top: CH - 64,\n                    transform: `translateX(-50%) translateY(${(1 - toastS) * 26}px)`,\n                    opacity: toastFade,\n                    height: 40,\n                    padding: \"0 16px 0 12px\",\n                    borderRadius: 12,\n                    background: pal.dark ? \"#1E2128\" : \"#1B1A18\",\n                    color: \"#F4F3F0\",\n                    display: \"flex\",\n                    alignItems: \"center\",\n                    gap: 9,\n                    fontSize: 15,\n                    boxShadow: pal.dark\n                      ? \"0 0 0 1px rgba(255,255,255,0.1), 0 16px 32px -12px rgba(0,0,0,0.6)\"\n                      : \"0 16px 32px -12px rgba(20,18,14,0.35), 0 4px 10px -4px rgba(20,18,14,0.2)\",\n                    whiteSpace: \"nowrap\",\n                    zIndex: 10,\n                  }}\n                >\n                  <Check\n                    color=\"#5BE39B\"\n                    size={16}\n                    draw={interpolate(\n                      frame,\n                      [PUBLISH + 14, PUBLISH + 24],\n                      [0, 1],\n                      { ...CL, easing: ENTER },\n                    )}\n                  />\n                  {toast}\n                </div>\n              ) : null}\n            </div>\n          </div>\n          <div\n            style={{\n              ...btn,\n              left: runX,\n              width: RUN_W,\n              background: pal.card,\n              boxShadow: pal.cardDepth,\n              color: pal.text,\n              transform: `scale(${1 - 0.04 * runPress})`,\n            }}\n          >\n            {running ? (\n              <Spinner color={accent} track={pal.rule} frame={frame} />\n            ) : (\n              <svg width={11} height={11} viewBox=\"0 0 12 12\">\n                <path\n                  d=\"M3 1.8v8.4L10 6z\"\n                  fill=\"none\"\n                  stroke={pal.text}\n                  strokeWidth={1.3}\n                  strokeLinejoin=\"round\"\n                />\n              </svg>\n            )}\n            {running ? \"Running\" : \"Run\"}\n          </div>\n          <div\n            style={{\n              ...btn,\n              left: pubX,\n              width: PUB_W,\n              background: accent,\n              color: \"#fff\",\n              boxShadow: `inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px ${withAlpha(accent, 0.4)}, 0 6px 14px -6px ${withAlpha(accent, 0.6)}`,\n              transform: `scale(${1 - 0.04 * pubPress})`,\n            }}\n          >\n            {published ? (\n              <Check color=\"#fff\" size={14} draw={clamp(pubS * 1.4)} />\n            ) : null}\n            {published ? \"Published\" : \"Publish\"}\n          </div>\n          <div\n            style={{\n              position: \"absolute\",\n              left: cur.x - 2,\n              top: cur.y - 2,\n              opacity: cursorIn,\n              zIndex: 50,\n            }}\n          >\n            <Cursor press={press} />\n          </div>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/flow-builder.tsx"
    }
  ],
  "type": "registry:component"
}
