{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "border-beam",
  "title": "Border Beam",
  "description": "A beam laps the border of a dark card, then reveals 'remocn 2.0' with a logo and description.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "registryDependencies": ["@remocn/gsh"],
  "files": [
    {
      "path": "registry/remocn/border-beam/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadGeist } from \"@remotion/google-fonts/Geist\";\nimport { loadFont as loadGeistMono } from \"@remotion/google-fonts/GeistMono\";\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport { type CSSProperties, useId } 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 geist = loadGeist(\"normal\", {\n  weights: [\"300\", \"400\"],\n  subsets: [\"latin\"],\n});\nconst geistMono = loadGeistMono(\"normal\", {\n  weights: [\"400\"],\n  subsets: [\"latin\"],\n});\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"600\"],\n  subsets: [\"latin\"],\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\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.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\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\nexport interface BorderBeamProps {\n  title?: string;\n  description?: string;\n  command?: string;\n  showLogo?: boolean;\n  accent?: string;\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n  ground?: GshPresetId;\n  groundParams?: Partial<GradientParams>;\n}\n\nconst THEMES = {\n  dark: {\n    card: \"rgba(9,9,10,0.9)\",\n    depth: \"0 2px 6px rgba(0,0,0,0.35), 0 30px 80px -20px rgba(0,0,0,0.7)\",\n    hair: \"rgba(255,255,255,0.1)\",\n    trail: \"rgba(255,255,255,0.1)\",\n    rule: \"rgba(255,255,255,0.08)\",\n    head: \"#FFFFFF\",\n    fg: \"#EDEDED\",\n    muted: \"#8F8F94\",\n    logo: \"#EDEDED\",\n    glow: 0.55,\n    ground: \"charcoal-satin\" as GshPresetId,\n    groundParams: {\n      colors: [\"#2c3036\", \"#7d838c\", \"#4f555e\"],\n    } as Partial<GradientParams> | undefined,\n  },\n  light: {\n    card: \"rgba(255,255,255,0.9)\",\n    depth:\n      \"0 1px 2px rgba(20,24,60,0.06), 0 30px 70px -24px rgba(20,24,60,0.22)\",\n    hair: \"rgba(10,10,10,0.1)\",\n    trail: \"rgba(10,10,10,0.1)\",\n    rule: \"rgba(10,10,10,0.07)\",\n    head: \"\",\n    fg: \"#0F0F10\",\n    muted: \"#6E6E73\",\n    logo: \"#0F0F10\",\n    glow: 0.3,\n    ground: \"paper-crease\" as GshPresetId,\n    groundParams: undefined as Partial<GradientParams> | undefined,\n  },\n};\n\nconst LAYERS = 28;\n\nconst EASE_ENTER = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_MOVE = Easing.bezier(0.65, 0, 0.35, 1);\nconst SPRING_HEAVY = { mass: 1, damping: 26, stiffness: 120 };\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nconst T = {\n  lapStart: 14,\n  lapDur: 80,\n  lag: 8,\n  contentStart: 84,\n  stagger: 5,\n  contentDur: 18,\n};\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  const h = hex.replace(\"#\", \"\");\n  const n = Number.parseInt(\n    h.length === 3\n      ? h\n          .split(\"\")\n          .map((ch) => ch + ch)\n          .join(\"\")\n      : h.slice(0, 6),\n    16,\n  );\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\n\nfunction mix(a: string, b: string, t: number) {\n  const ca = hexToRgb(a);\n  const cb = hexToRgb(b);\n  const ch = ca.map((v, i) => Math.round(v + (cb[i] - v) * t));\n  return `rgb(${ch[0]},${ch[1]},${ch[2]})`;\n}\n\nexport function BorderBeam({\n  title = \"Version 2.0\",\n  description = \"Launch videos, written in React. Installed like shadcn.\",\n  command = \"npx shadcn add @remocn/launch-film\",\n  showLogo = true,\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n  ground,\n  groundParams,\n}: BorderBeamProps) {\n  const frame = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const c = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const portrait = H > W * 1.1;\n  const preset = ground ?? (groundParams ? undefined : c.ground);\n  const gParams = groundParams ?? (ground ? undefined : c.groundParams);\n\n  const w = portrait ? W * 0.84 : W * 0.6;\n  const h = portrait ? H * 0.36 : H * 0.52;\n  const r = portrait ? W * 0.03 : W * 0.0125;\n  const P = 2 * (w - 2 * r) + 2 * (h - 2 * r) + 2 * Math.PI * r;\n  const pad = portrait ? W * 0.07 : W * 0.034;\n  const mx = w / 2;\n  const d = [\n    `M ${mx} 0`,\n    `H ${w - r}`,\n    `A ${r} ${r} 0 0 1 ${w} ${r}`,\n    `V ${h - r}`,\n    `A ${r} ${r} 0 0 1 ${w - r} ${h}`,\n    `H ${r}`,\n    `A ${r} ${r} 0 0 1 0 ${h - r}`,\n    `V ${r}`,\n    `A ${r} ${r} 0 0 1 ${r} 0`,\n    \"Z\",\n  ].join(\" \");\n\n  const enter = spring({ frame, fps, config: SPRING_HEAVY });\n  const enterO = interpolate(frame, [0, 12], [0, 1], {\n    ...CLAMP,\n    easing: EASE_ENTER,\n  });\n  const lapAt = (start: number) =>\n    interpolate(frame, [start, start + T.lapDur], [0, P], {\n      ...CLAMP,\n      easing: EASE_MOVE,\n    });\n  const headD = lapAt(T.lapStart);\n  const tailD = lapAt(T.lapStart + T.lag);\n  const L = headD - tailD;\n\n  const head = c.head || accent;\n  const target = (m: number) =>\n    m > LAYERS ? 0 : Math.min(0.98, (1 - (m - 1) / LAYERS) ** 1.6);\n  const layers: { j: number; len: number; color: string; alpha: number }[] = [];\n  if (L > 0.5) {\n    for (let j = LAYERS; j >= 1; j--) {\n      const alpha = 1 - (1 - target(j)) / (1 - target(j + 1));\n      const tt = (j - 1) / (LAYERS - 1);\n      layers.push({\n        j,\n        len: (L * j) / LAYERS,\n        color:\n          theme === \"dark\"\n            ? mix(head, accent, Math.min(1, tt * 1.25))\n            : mix(accent, \"#0A0A0A\", 0.15 * (1 - tt)),\n        alpha,\n      });\n    }\n  }\n\n  const beamPaths = (strokeWidth: number) =>\n    layers.map((layer) => (\n      <path\n        key={layer.j}\n        d={d}\n        fill=\"none\"\n        stroke={layer.color}\n        strokeOpacity={layer.alpha}\n        strokeWidth={strokeWidth}\n        strokeDasharray={`${layer.len} ${P}`}\n        strokeDashoffset={-(headD - layer.len)}\n      />\n    ));\n\n  const item = (i: number): CSSProperties => {\n    const p = interpolate(\n      frame,\n      [\n        T.contentStart + i * T.stagger,\n        T.contentStart + i * T.stagger + T.contentDur,\n      ],\n      [0, 1],\n      { ...CLAMP, easing: EASE_ENTER },\n    );\n    return {\n      opacity: p,\n      filter: `blur(${(1 - p) * 6}px)`,\n      transform: `translateY(${(1 - p) * H * 0.02}px)`,\n    };\n  };\n\n  const s = Math.min(W / 1280, H / 720);\n  const titleFs = portrait ? W * 0.1 : W * 0.056;\n  const descFs = portrait ? W * 0.038 : H * 0.032;\n  const monoFs = portrait ? W * 0.03 : H * 0.025;\n  const logoFs = portrait ? W * 0.045 : H * 0.036;\n\n  return (\n    <AbsoluteFill style={{ overflow: \"hidden\" }}>\n      <GshGround\n        preset={preset}\n        params={gParams}\n        timeOffset={theme === \"light\" ? 3 : 5}\n        timeScale={0.4}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          left: (W - w) / 2,\n          top: (H - h) / 2,\n          width: w,\n          height: h,\n          opacity: enterO,\n          transform: `translateY(${(1 - enter) * H * 0.04}px) scale(${0.97 + 0.03 * enter})`,\n        }}\n      >\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            borderRadius: r,\n            backgroundColor: c.card,\n            boxShadow: c.depth,\n          }}\n        />\n        <svg\n          width={w}\n          height={h}\n          viewBox={`0 0 ${w} ${h}`}\n          style={{ position: \"absolute\", inset: 0, overflow: \"visible\" }}\n          aria-hidden\n        >\n          <defs>\n            <filter\n              id={`${uid}-glow`}\n              x={-40 * s}\n              y={-40 * s}\n              width={w + 80 * s}\n              height={h + 80 * s}\n              filterUnits=\"userSpaceOnUse\"\n            >\n              <feGaussianBlur stdDeviation={5 * s} />\n            </filter>\n          </defs>\n          <path d={d} fill=\"none\" stroke={c.hair} strokeWidth={1} />\n          {tailD > 0 ? (\n            <path\n              d={d}\n              fill=\"none\"\n              stroke={c.trail}\n              strokeWidth={1}\n              strokeDasharray={`${tailD} ${P}`}\n            />\n          ) : null}\n          <g filter={`url(#${uid}-glow)`} opacity={c.glow}>\n            {beamPaths(4 * s)}\n          </g>\n          <g>{beamPaths(Math.max(1, 1.5 * s))}</g>\n        </svg>\n\n        <div\n          style={{\n            position: \"absolute\",\n            inset: 0,\n            padding: pad,\n            display: \"flex\",\n            flexDirection: \"column\",\n            justifyContent: \"space-between\",\n          }}\n        >\n          <div style={{ ...item(0), minHeight: logoFs }}>\n            {showLogo ? <RemocnLogo fontSize={logoFs} color={c.logo} /> : null}\n          </div>\n          <div>\n            <div\n              style={{\n                ...item(1),\n                fontFamily: geist.fontFamily,\n                fontWeight: 400,\n                fontSize: titleFs,\n                letterSpacing: \"-0.045em\",\n                lineHeight: 1,\n                color: c.fg,\n                whiteSpace: \"pre\",\n              }}\n            >\n              {title}\n            </div>\n            <div\n              style={{\n                ...item(2),\n                marginTop: titleFs * 0.32,\n                fontFamily: geist.fontFamily,\n                fontWeight: 300,\n                fontSize: descFs,\n                letterSpacing: \"-0.01em\",\n                lineHeight: 1.4,\n                color: c.muted,\n                maxWidth: w - pad * 2,\n                textWrap: \"balance\",\n              }}\n            >\n              {description}\n            </div>\n            {command ? (\n              <div\n                style={{\n                  ...item(3),\n                  marginTop: titleFs * 0.5,\n                  paddingTop: monoFs * 1.1,\n                  borderTop: `1px solid ${c.rule}`,\n                  display: \"flex\",\n                  gap: monoFs * 0.7,\n                  fontFamily: geistMono.fontFamily,\n                  fontWeight: 400,\n                  fontSize: monoFs,\n                  letterSpacing: \"-0.01em\",\n                  whiteSpace: \"pre\",\n                  color: c.fg,\n                }}\n              >\n                <span style={{ color: c.muted }}>$</span>\n                <span style={{ flex: 1 }}>{command}</span>\n                <svg\n                  width={monoFs * 1.1}\n                  height={monoFs * 1.1}\n                  viewBox=\"0 0 24 24\"\n                  fill=\"none\"\n                  stroke={c.muted}\n                  strokeWidth={1.5}\n                  strokeLinejoin=\"round\"\n                  aria-hidden\n                  style={{ alignSelf: \"center\" }}\n                >\n                  <rect\n                    x=\"8.5\"\n                    y=\"8.5\"\n                    width=\"11\"\n                    height=\"11\"\n                    rx=\"2.5\"\n                    vectorEffect=\"non-scaling-stroke\"\n                  />\n                  <path\n                    d=\"M15.5 8.5V6.5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h2\"\n                    vectorEffect=\"non-scaling-stroke\"\n                  />\n                </svg>\n              </div>\n            ) : null}\n          </div>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/border-beam.tsx"
    }
  ],
  "type": "registry:component"
}
