{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "synthwave",
  "title": "Synthwave",
  "description": "A retro neon horizon: a scrolling perspective grid, a rising striped sun and mountain ranges that slide in.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/synthwave/index.tsx",
      "content": "\"use client\";\n\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  random,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type SynthwaveColors = {\n  skyTop: string;\n  skyHorizon: string;\n  sunTop: string;\n  sunBottom: string;\n  grid: string;\n  floor: string;\n  mountainsBack: string;\n  mountainsFront: string;\n};\n\nexport type SynthwaveProps = {\n  colors?: Partial<SynthwaveColors>;\n  /** Grid line color. Overrides `colors.grid` when set. */\n  grid?: string;\n  /** Top of the sun gradient. Overrides `colors.sunTop` when set. */\n  sunTop?: string;\n  /** Bottom of the sun gradient. Overrides `colors.sunBottom` when set. */\n  sunBottom?: string;\n  /** Sky glow at the horizon. Overrides `colors.skyHorizon` when set. */\n  skyHorizon?: string;\n  /** Grid rows that pass the viewer per second. */\n  gridSpeed?: number;\n  /** Horizon height as a fraction of the frame. */\n  horizon?: number;\n  /** Scroll the sun stripes downward. */\n  sunStripes?: boolean;\n  speed?: number;\n};\n\nexport const synthwaveDefaults = {\n  colors: {\n    skyTop: \"#08080B\",\n    skyHorizon: \"#3B0C10\",\n    sunTop: \"#FFC46B\",\n    sunBottom: \"#E5322D\",\n    grid: \"#E5322D\",\n    floor: \"#0A0A0C\",\n    mountainsBack: \"#2A0A0E\",\n    mountainsFront: \"#111111\",\n  } as SynthwaveColors,\n  gridSpeed: 2,\n  horizon: 0.6,\n  sunStripes: true,\n  speed: 1,\n};\n\n/** Frames until the sun and mountains are in place; the grid loops forever. */\nexport const synthwaveLength = 40;\n\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\n\nfunction ridge(\n  seed: string,\n  width: number,\n  base: number,\n  peak: number,\n  count: number,\n) {\n  // Spans well past both edges so the slide never exposes an end.\n  const points: string[] = [`${-0.4 * width},${base}`];\n  for (let i = 0; i <= count; i++) {\n    const x = -0.4 * width + (i / count) * 1.8 * width;\n    const center = Math.min(1, Math.abs(x - width / 2) / (0.45 * width));\n    const valley = 0.18 + 0.82 * center * center;\n    const jag = 0.45 + 0.55 * random(`${seed}-${i}`);\n    const y = base - peak * valley * (i % 2 === 0 ? jag : jag * 0.45);\n    points.push(`${x},${y}`);\n  }\n  points.push(`${1.4 * width},${base}`);\n  return points.join(\" \");\n}\n\nexport function Synthwave({\n  colors,\n  grid,\n  sunTop,\n  sunBottom,\n  skyHorizon,\n  gridSpeed = synthwaveDefaults.gridSpeed,\n  horizon = synthwaveDefaults.horizon,\n  sunStripes = synthwaveDefaults.sunStripes,\n  speed = synthwaveDefaults.speed,\n}: SynthwaveProps) {\n  const frame = useCurrentFrame();\n  const { width: W, height: H, fps } = useVideoConfig();\n  const id = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const merged = { ...synthwaveDefaults.colors, ...colors };\n  const c: SynthwaveColors = {\n    ...merged,\n    grid: grid ?? merged.grid,\n    sunTop: sunTop ?? merged.sunTop,\n    sunBottom: sunBottom ?? merged.sunBottom,\n    skyHorizon: skyHorizon ?? merged.skyHorizon,\n  };\n  const u = Math.min(W, H) / 720;\n  const t = (frame / fps) * 30 * speed;\n  const cx = W / 2;\n  const yh = horizon * H;\n  const floorDepth = H - yh;\n\n  // Floor: screen y = yh + floorDepth / z, so z = 1 is the bottom edge.\n  const rowGap = 0.5;\n  const phase = ((((t / 30) * gridSpeed) % 1) + 1) % 1;\n  const rows: { y: number; o: number }[] = [];\n  for (let k = 0; k < 70; k++) {\n    const z = 0.7 + (k + 1 - phase) * rowGap;\n    const y = yh + floorDepth / z;\n    if (y > H + 4 * u) continue;\n    rows.push({ y, o: interpolate(z, [1, 14, 30], [1, 0.35, 0], clamp) });\n  }\n  const columns = Array.from({ length: 23 }, (_, i) => i - 11);\n  const colSpread = W * 0.16;\n  const nearZ = 0.35;\n\n  const rise = interpolate(t, [0, 40], [0, 1], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n  const R = 0.27 * H;\n  const sunY = interpolate(rise, [0, 1], [yh + R * 1.05, yh - R * 0.42]);\n  const stripePhase = sunStripes ? (((t / 36) % 1) + 1) % 1 : 0;\n  const stripes = Array.from({ length: 7 }, (_, i) => {\n    const p = (i + stripePhase) / 7;\n    const y = sunY + R * (-0.05 + p * 1.05);\n    const h = R * (0.012 + 0.09 * p);\n    return { y: y - h / 2, h };\n  });\n\n  const back = useMemo(\n    () => ridge(\"synthwave-back\", W, yh, 0.26 * H, 27),\n    [W, H, yh],\n  );\n  const front = useMemo(\n    () => ridge(\"synthwave-front\", W, yh, 0.15 * H, 39),\n    [W, H, yh],\n  );\n  // Ranges slide in sideways while rising from behind the horizon.\n  const slideBack = interpolate(t, [4, 34], [1, 0], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n  const slideFront = interpolate(t, [8, 38], [1, 0], {\n    ...clamp,\n    easing: Easing.out(Easing.cubic),\n  });\n  const stars = Array.from({ length: 70 }, (_, i) => ({\n    x: random(`synthwave-sx-${i}`) * W,\n    y: random(`synthwave-sy-${i}`) * yh * 0.8,\n    r: (0.6 + random(`synthwave-sr-${i}`) * 1.4) * u,\n    o:\n      0.25 +\n      0.5 * (0.5 + 0.5 * Math.sin(t * 0.15 + random(`synthwave-sp-${i}`) * 9)),\n  }));\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: c.floor, overflow: \"hidden\" }}>\n      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} aria-hidden>\n        <defs>\n          <linearGradient id={`${id}-sky`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0\" stopColor={c.skyTop} />\n            <stop offset=\"0.55\" stopColor={c.skyTop} />\n            <stop offset=\"1\" stopColor={c.skyHorizon} />\n          </linearGradient>\n          <linearGradient id={`${id}-sun`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0.1\" stopColor={c.sunTop} />\n            <stop offset=\"0.85\" stopColor={c.sunBottom} />\n          </linearGradient>\n          <linearGradient id={`${id}-floor`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0\" stopColor={c.skyHorizon} />\n            <stop offset=\"0.25\" stopColor={c.floor} />\n          </linearGradient>\n          <mask id={`${id}-stripes`}>\n            <rect width={W} height={H} fill=\"#fff\" />\n            {stripes.map((s, i) => (\n              <rect key={i} x={0} y={s.y} width={W} height={s.h} fill=\"#000\" />\n            ))}\n          </mask>\n          <clipPath id={`${id}-above`}>\n            <rect width={W} height={yh} />\n          </clipPath>\n          <filter\n            id={`${id}-glow`}\n            x=\"-20%\"\n            y=\"-20%\"\n            width=\"140%\"\n            height=\"140%\"\n          >\n            <feGaussianBlur stdDeviation={4 * u} result=\"blur\" />\n            <feMerge>\n              <feMergeNode in=\"blur\" />\n              <feMergeNode in=\"blur\" />\n              <feMergeNode in=\"SourceGraphic\" />\n            </feMerge>\n          </filter>\n          <filter\n            id={`${id}-haze`}\n            x=\"-50%\"\n            y=\"-50%\"\n            width=\"200%\"\n            height=\"200%\"\n          >\n            <feGaussianBlur stdDeviation={30 * u} />\n          </filter>\n        </defs>\n\n        <rect width={W} height={yh} fill={`url(#${id}-sky)`} />\n        <g fill=\"#FFFFFF\">\n          {stars.map((s, i) => (\n            <circle key={i} cx={s.x} cy={s.y} r={s.r} opacity={s.o} />\n          ))}\n        </g>\n\n        <g clipPath={`url(#${id}-above)`}>\n          <circle\n            cx={cx}\n            cy={sunY}\n            r={R * 1.15}\n            fill={c.sunBottom}\n            opacity={0.45}\n            filter={`url(#${id}-haze)`}\n          />\n          <circle\n            cx={cx}\n            cy={sunY}\n            r={R}\n            fill={`url(#${id}-sun)`}\n            mask={`url(#${id}-stripes)`}\n          />\n          <polygon\n            points={back}\n            fill={c.mountainsBack}\n            transform={`translate(${-0.25 * W * slideBack} ${0.3 * H * slideBack})`}\n          />\n          <g\n            transform={`translate(${0.25 * W * slideFront} ${0.2 * H * slideFront})`}\n          >\n            <polygon points={front} fill={c.mountainsFront} />\n            <polyline\n              points={front}\n              fill=\"none\"\n              stroke={c.grid}\n              strokeWidth={1.5 * u}\n              strokeOpacity={0.55}\n              strokeLinejoin=\"round\"\n            />\n          </g>\n        </g>\n\n        <rect y={yh} width={W} height={H - yh} fill={`url(#${id}-floor)`} />\n        <g\n          stroke={c.grid}\n          strokeWidth={2.2 * u}\n          filter={`url(#${id}-glow)`}\n          fill=\"none\"\n        >\n          {columns.map((j) => (\n            <line\n              key={j}\n              x1={cx}\n              y1={yh}\n              x2={cx + (j * colSpread) / nearZ}\n              y2={yh + floorDepth / nearZ}\n            />\n          ))}\n          {rows.map((row, i) => (\n            <line\n              key={i}\n              x1={0}\n              y1={row.y}\n              x2={W}\n              y2={row.y}\n              strokeOpacity={row.o}\n            />\n          ))}\n          <line x1={0} y1={yh} x2={W} y2={yh} strokeWidth={3 * u} />\n        </g>\n        {/* Haze that swallows the converging lines at the horizon. */}\n        <rect\n          y={yh - 6 * u}\n          width={W}\n          height={0.12 * H}\n          fill={c.skyHorizon}\n          opacity={0.5}\n          filter={`url(#${id}-haze)`}\n        />\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/synthwave.tsx"
    }
  ],
  "type": "registry:component"
}
