{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "squircle-wave",
  "title": "Squircle Wave",
  "description": "A wave of squircle cells sweeps across a dot grid and leaves the R mark behind.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/squircle-wave/index.tsx",
      "content": "\"use client\";\n\nimport { useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  interpolateColors,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport interface SquircleWaveProps {\n  /** Colour of the wave's leading edge just behind the crest. */\n  accent?: string;\n  /** Playback multiplier. */\n  speed?: number;\n  theme?: \"dark\" | \"light\";\n}\n\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\nconst EASE_IO = Easing.bezier(0.45, 0, 0.55, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_WAVE = Easing.bezier(0.3, 0, 0.45, 1);\n\n// gradients.sh \"limb\" palette: #e9ecff, #8a8bff, #4b2bd6, #160c3a on #030308.\n// It colours the wave's wake, the planet-edge ground and the R itself.\nconst THEMES = {\n  dark: {\n    bg: \"#030308\",\n    ground: [\n      \"rgba(75,43,214,0.42)\",\n      \"rgba(22,12,58,0.75)\",\n      \"rgba(138,139,255,0.22)\",\n    ],\n    idle: \"#26263A\",\n    rest: \"#1C1B2E\",\n    wake: \"#4B2BD6\",\n    crest: \"#E9ECFF\",\n    mark: [\"#E9ECFF\", \"#B9BBFF\", \"#8A8BFF\"],\n    grain: 0.06,\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    ground: [\n      \"rgba(138,139,255,0.3)\",\n      \"rgba(233,236,255,0.6)\",\n      \"rgba(75,43,214,0.16)\",\n    ],\n    idle: \"#D3D1D8\",\n    rest: \"#E0DEE2\",\n    wake: \"#8A8BFF\",\n    crest: \"#2A1C7A\",\n    mark: [\"#160C3A\", \"#4B2BD6\", \"#8A8BFF\"],\n    grain: 0.045,\n  },\n} as const;\n\nconst WAVE_START = 2;\nconst WAVE_END = 72;\nconst ANGLE = (16 * Math.PI) / 180;\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\";\nconst R_W = 124.06;\nconst R_H = 134.26;\n/** Stem width of the R in path units: the grid pitch is half of it. */\nconst R_STEM = 19;\n\n/** The R outline flattened to a polygon (path units), for point-in-mark tests. */\nconst R_POLY = (() => {\n  const tokens = REMOCN_R_PATH.replace(/,/g, \" \").trim().split(/\\s+/);\n  const pts: [number, number][] = [];\n  let cmd = \"\";\n  let i = 0;\n  let cx = 0;\n  let cy = 0;\n  const num = () => Number(tokens[i++]);\n  while (i < tokens.length) {\n    if (/^[A-Za-z]$/.test(tokens[i])) cmd = tokens[i++];\n    if (cmd === \"Z\") break;\n    if (cmd === \"M\" || cmd === \"L\") {\n      cx = num();\n      cy = num();\n      pts.push([cx, cy]);\n    } else if (cmd === \"C\") {\n      const x1 = num();\n      const y1 = num();\n      const x2 = num();\n      const y2 = num();\n      const x3 = num();\n      const y3 = num();\n      for (let k = 1; k <= 8; k++) {\n        const t = k / 8;\n        const v = 1 - t;\n        pts.push([\n          v * v * v * cx +\n            3 * v * v * t * x1 +\n            3 * v * t * t * x2 +\n            t * t * t * x3,\n          v * v * v * cy +\n            3 * v * v * t * y1 +\n            3 * v * t * t * y2 +\n            t * t * t * y3,\n        ]);\n      }\n      cx = x3;\n      cy = y3;\n    }\n  }\n  return pts;\n})();\n\n/** Even-odd point-in-polygon test in R path units. */\nfunction inMark(x: number, y: number) {\n  let inside = false;\n  for (let a = 0, b = R_POLY.length - 1; a < R_POLY.length; b = a++) {\n    const [xa, ya] = R_POLY[a];\n    const [xb, yb] = R_POLY[b];\n    if (ya > y !== yb > y && x < ((xb - xa) * (y - ya)) / (yb - ya) + xa) {\n      inside = !inside;\n    }\n  }\n  return inside;\n}\n\nfunction squircle(cx: number, cy: number, r: number, q: number) {\n  const N = 36;\n  let d = \"\";\n  for (let i = 0; i < N; i++) {\n    const a = (i / N) * Math.PI * 2;\n    const c = Math.cos(a);\n    const s = Math.sin(a);\n    const x = cx + Math.sign(c) * Math.abs(c) ** q * r;\n    const y = cy + Math.sign(s) * Math.abs(s) ** q * r;\n    d += `${i ? \"L\" : \"M\"}${x.toFixed(2)} ${y.toFixed(2)}`;\n  }\n  return `${d}Z`;\n}\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={11}\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\nexport function SquircleWave({\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n}: SquircleWaveProps) {\n  const f = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const p = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const maskId = `m${uid}`;\n  const fillId = `f${uid}`;\n\n  // Mark size and a grid whose cells are half the stem wide, so the stem's\n  // straight edges fall exactly on cell boundaries.\n  const mu = (0.5 * H) / R_H;\n  const P = (mu * R_STEM) / 2;\n  const markLeft = W / 2 - (R_W / 2) * mu;\n  const markTop = H / 2 - (R_H / 2) * mu;\n  const ox = markLeft - Math.ceil(markLeft / P) * P;\n  const oy = markTop - Math.ceil(markTop / P) * P;\n  const cols = Math.ceil((W - ox) / P);\n  const rows = Math.ceil((H - oy) / P);\n\n  // Which cells touch the R: sample a 6×6 lattice inside each. Frame-free.\n  const markCells = useMemo(() => {\n    const set = new Set<number>();\n    const cu = P / mu;\n    for (let i = 0; i < cols; i++) {\n      for (let j = 0; j < rows; j++) {\n        const mx0 = (ox + i * P - markLeft) / mu;\n        const my0 = (oy + j * P - markTop) / mu;\n        if (mx0 > R_W || my0 > R_H || mx0 + cu < 0 || my0 + cu < 0) continue;\n        let hit = false;\n        for (let a = 0; a < 6 && !hit; a++) {\n          for (let b = 0; b < 6 && !hit; b++) {\n            hit = inMark(\n              mx0 + ((a + 0.5) / 6) * cu,\n              my0 + ((b + 0.5) / 6) * cu,\n            );\n          }\n        }\n        if (hit) set.add(i * rows + j);\n      }\n    }\n    return set;\n  }, [P, mu, cols, rows, ox, oy, markLeft, markTop]);\n\n  const cosA = Math.cos(ANGLE);\n  const sinA = Math.sin(ANGLE);\n  const half = (W / 2) * cosA + (H / 2) * sinA;\n  // Enter just off-frame so the crest is on screen within a few frames, and\n  // run far enough past the edge that the whole wake clears.\n  const front = interpolate(\n    f,\n    [WAVE_START, WAVE_END],\n    [-half - 90 * u, half + 420 * u],\n    {\n      ...CLAMP,\n      easing: EASE_WAVE,\n    },\n  );\n  const sigAhead = 70 * u;\n  const sigBehind = 150 * u;\n\n  const drift = interpolate(f, [0, WAVE_END + 40], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const r0 = 0.15 * P;\n  const r1 = 0.43 * P;\n\n  const shapes: React.ReactNode[] = [];\n  const maskRects: React.ReactNode[] = [];\n\n  for (let i = 0; i < cols; i++) {\n    for (let j = 0; j < rows; j++) {\n      const x0 = ox + i * P;\n      const y0 = oy + j * P;\n      const cx = x0 + P / 2;\n      const cy = y0 + P / 2;\n\n      const mark = markCells.has(i * rows + j);\n\n      const proj = (cx - W / 2) * cosA + (cy - H / 2) * sinA;\n      const d = proj - front;\n      const sig = d > 0 ? sigAhead : sigBehind;\n      const m = Math.exp(-((d / sig) ** 2));\n      const passed = interpolate(-d, [0, 2 * sigBehind], [0, 1], CLAMP);\n\n      const dist = Math.hypot(cx - W / 2, cy - H / 2);\n      const appear = interpolate(\n        f,\n        [dist / (60 * u), dist / (60 * u) + 9],\n        [0, 1],\n        {\n          ...CLAMP,\n          easing: EASE_OUT,\n        },\n      );\n\n      const lock = mark\n        ? interpolate(-d, [0.1 * sigBehind, 0.9 * sigBehind], [0, 1], {\n            ...CLAMP,\n            easing: EASE_OUT,\n          })\n        : 0;\n      const shapeK = Math.max(\n        m,\n        mark ? Math.min(1, -d / (0.1 * sigBehind)) : 0,\n      );\n      const k = mark && d < 0 ? 1 : m;\n      const r = (r0 + (r1 - r0) * k) * (0.6 + 0.4 * appear);\n      const q = 1 - shapeK ** 0.7;\n      // Behind the crest the wake cools through the accent and limb violet.\n      const wake = d < 0 ? Math.exp(-((d / (1.6 * sigBehind)) ** 2)) : 0;\n      const idle = interpolateColors(passed, [0, 1], [p.idle, p.rest]);\n      const trail = interpolateColors(\n        wake,\n        [0, 0.55, 1],\n        [idle, p.wake, accent],\n      );\n      const color = interpolateColors(m, [0, 1], [trail, p.crest]);\n      const op = appear * (1 - lock);\n\n      if (op > 0.002) {\n        shapes.push(\n          shapeK < 0.01 ? (\n            <circle\n              key={`c${i}-${j}`}\n              cx={cx}\n              cy={cy}\n              r={r}\n              fill={color}\n              opacity={op}\n            />\n          ) : (\n            <path\n              key={`c${i}-${j}`}\n              d={squircle(cx, cy, r, Math.max(0, q))}\n              fill={mark && d < 0 ? p.mark[1] : color}\n              opacity={op}\n            />\n          ),\n        );\n      }\n      if (lock > 0) {\n        const h = r1 + (P / 2 + 0.6 * u - r1) * lock;\n        maskRects.push(\n          <rect\n            key={`m${i}-${j}`}\n            x={cx - h}\n            y={cy - h}\n            width={2 * h}\n            height={2 * h}\n            fill=\"#FFFFFF\"\n          />,\n        );\n      }\n    }\n  }\n\n  return (\n    <AbsoluteFill style={{ background: p.bg, overflow: \"hidden\" }}>\n      <AbsoluteFill\n        style={{\n          background: [\n            `radial-gradient(70% 34% at ${50 + 3 * drift}% 104%, ${p.ground[2]} 0%, transparent 70%)`,\n            `radial-gradient(95% 70% at ${50 + 3 * drift}% 118%, ${p.ground[0]} 0%, ${p.ground[1]} 45%, transparent 78%)`,\n          ].join(\", \"),\n        }}\n      />\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <linearGradient\n            id={fillId}\n            gradientUnits=\"userSpaceOnUse\"\n            x1={0}\n            y1={0}\n            x2={R_W * 0.6}\n            y2={R_H}\n          >\n            <stop offset=\"0\" stopColor={p.mark[0]} />\n            <stop offset=\"0.55\" stopColor={p.mark[1]} />\n            <stop offset=\"1\" stopColor={p.mark[2]} />\n          </linearGradient>\n          <mask\n            id={maskId}\n            maskUnits=\"userSpaceOnUse\"\n            x={0}\n            y={0}\n            width={W}\n            height={H}\n          >\n            {maskRects}\n          </mask>\n        </defs>\n        {shapes}\n        <g mask={`url(#${maskId})`}>\n          <g transform={`translate(${markLeft} ${markTop}) scale(${mu})`}>\n            <path d={REMOCN_R_PATH} fill={`url(#${fillId})`} />\n          </g>\n        </g>\n      </svg>\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/squircle-wave.tsx"
    }
  ],
  "type": "registry:component"
}
