{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "torn-edge",
  "title": "Torn Edge",
  "description": "A torn paper sheet rips away to reveal a bold headline printed on the gradient stage.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/torn-edge/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadDisplay } from \"@remotion/google-fonts/BricolageGrotesque\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: display } = loadDisplay(\"normal\", {\n  weights: [\"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface TornEdgeProps {\n  /** Headline printed on the stage underneath the sheet. */\n  title?: string;\n  /** Mid colour of the sheet's gradient (gradients.sh \"fluted\"). */\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;\n\nconst EASE_TEAR = Easing.bezier(0.42, 0, 0.7, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\n\n// gradients.sh \"fluted\" palette: #1d2a8f, #2f7bff, #ff7a3d, #ffe2c4. The sheet\n// is printed with it; `accent` takes the blue middle stop.\nconst FLUTED = [\"#1D2A8F\", \"#FF7A3D\", \"#FFE2C4\"] as const;\n\nconst TEAR_START = 2;\nconst TEAR_END = 36;\nconst PART_START = 32;\nconst PART_END = 70;\n\nconst THEMES = {\n  dark: {\n    stage: \"#0E0E10\",\n    fg: \"#F3F2EF\",\n    fiber: \"#ECE9E1\",\n    shadow: \"rgba(0,0,0,0.85)\",\n    light: \"rgba(255,255,255,0.08)\",\n    vignette: \"rgba(0,0,0,0.45)\",\n    grain: 0.06,\n  },\n  light: {\n    stage: \"#F2F0EA\",\n    fg: \"#141413\",\n    fiber: \"#FBFAF6\",\n    shadow: \"rgba(40,38,60,0.42)\",\n    light: \"rgba(255,252,245,0.14)\",\n    vignette: \"rgba(60,50,35,0.16)\",\n    grain: 0.05,\n  },\n} as const;\n\nfunction hash(n: number) {\n  const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;\n  return x - Math.floor(x);\n}\n\n/** Smooth 1D value noise in [0, 1]. */\nfunction noise(x: number, seed: number) {\n  const i = Math.floor(x);\n  const f = x - i;\n  const s = f * f * (3 - 2 * f);\n  const a = hash(i + seed * 101.3);\n  const b = hash(i + 1 + seed * 101.3);\n  return a + (b - a) * s;\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={17}\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\ntype Pt = [number, number];\n\n/** Closed polygon: the torn edge plus two far corners on one side. */\nfunction piece(edge: Pt[], farY: number) {\n  const first = edge[0];\n  const last = edge[edge.length - 1];\n  const pts = edge.map(([x, y]) => `${x.toFixed(2)} ${y.toFixed(2)}`);\n  return `M${first[0].toFixed(2)} ${farY}L${pts.join(\"L\")}L${last[0].toFixed(2)} ${farY}Z`;\n}\n\nexport function TornEdge({\n  title = \"Make it yours.\",\n  accent = \"#3B5BFD\",\n  speed = 1,\n  theme = \"dark\",\n}: TornEdgeProps) {\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 paperId = `pa${uid}`;\n  const frayId = `fr${uid}`;\n  const shTopId = `st${uid}`;\n  const shBotId = `sb${uid}`;\n  const inkId = `ink${uid}`;\n\n  const M = 60 * u;\n  const tilt = -0.045;\n  const step = 2.5 * u;\n  const count = Math.ceil((W + 2 * M) / step) + 1;\n\n  const tip = interpolate(f, [TEAR_START, TEAR_END], [-M * 0.5, W + M], {\n    ...CLAMP,\n    easing: EASE_TEAR,\n  });\n  const part = interpolate(f, [PART_START, PART_END], [0, 1], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n\n  const D = 0.205 * H;\n  const OPEN_MAX = 10 * u;\n\n  const topFiber: Pt[] = [];\n  const topBody: Pt[] = [];\n  const topShadow: Pt[] = [];\n  const botFiber: Pt[] = [];\n  const botBody: Pt[] = [];\n  const botShadow: Pt[] = [];\n\n  for (let i = 0; i < count; i++) {\n    const x = -M + i * step;\n    const y =\n      H / 2 +\n      tilt * (x - W / 2) +\n      u *\n        (34 * (noise(x / (300 * u), 1) - 0.5) +\n          12 * (noise(x / (84 * u), 2) - 0.5) +\n          5 * (noise(x / (22 * u), 3) - 0.5) +\n          2.2 * (noise(x / (6 * u), 4) - 0.5));\n    const behind = tip - x;\n    const r = interpolate(behind, [0, 26 * u], [0, 1], CLAMP);\n    const open =\n      Math.min(OPEN_MAX, Math.max(0, behind) * 0.03) * r * (1 - part);\n    const fwTop =\n      u *\n      (2.4 + 7 * noise(x / (58 * u), 7) ** 1.4 + 1.6 * noise(x / (7 * u), 8)) *\n      r;\n    const fwBot = u * (0.7 + 1.3 * noise(x / (34 * u), 9)) * r;\n\n    const yt = y - open;\n    const yb = y + open;\n    topFiber.push([x, yt - 3 * u * (1 - r)]);\n    topBody.push([x, yt - fwTop]);\n    topShadow.push([x, yt]);\n    botFiber.push([x, yb + 3 * u * (1 - r)]);\n    botBody.push([x, yb + fwBot - 2.5 * u * (1 - r)]);\n    botShadow.push([x, yb]);\n  }\n\n  const rot = -tilt * 0.45 * (180 / Math.PI) * part;\n  const topT = `translate(${-10 * u * part} ${-D * part}) rotate(${rot} ${W / 2} ${H / 2})`;\n  const botT = `translate(${8 * u * part} ${D * part}) rotate(${rot * 0.8} ${W / 2} ${H / 2})`;\n  const farTop = -H * 1.5;\n  const farBot = H * 2.5;\n\n  const sOff = u * (1.5 + 6 * part);\n  const sBlur = u * (2.2 + 5 * part);\n\n  const push = interpolate(f, [0, PART_END + 40], [1, 1.035], {\n    ...CLAMP,\n    easing: EASE_IO,\n  });\n  const drift = interpolate(f, [0, PART_END + 40], [0, 1], CLAMP);\n  const cam = `translate(${W / 2} ${H / 2}) scale(${push}) translate(${-W / 2} ${-H / 2})`;\n\n  const fs = Math.min(150 * u, (W * 0.78) / (Math.max(title.length, 1) * 0.47));\n  const settle = interpolate(f, [PART_START, PART_END + 6], [0, 1], {\n    ...CLAMP,\n    easing: EASE_OUT,\n  });\n  const headScale = 1.045 - 0.045 * settle;\n  const headY = H / 2 + 0.34 * fs + (1 - settle) * 6 * u;\n\n  const userRegion = {\n    filterUnits: \"userSpaceOnUse\" as const,\n    x: -W,\n    y: -H * 2,\n    width: W * 3,\n    height: H * 5,\n  };\n\n  return (\n    <AbsoluteFill style={{ background: p.stage, overflow: \"hidden\" }}>\n      <svg width={W} height={H} aria-hidden>\n        <defs>\n          <filter\n            id={paperId}\n            x=\"0\"\n            y=\"0\"\n            width=\"1\"\n            height=\"1\"\n            colorInterpolationFilters=\"sRGB\"\n          >\n            <feTurbulence\n              type=\"fractalNoise\"\n              baseFrequency=\"0.7\"\n              numOctaves={3}\n              seed={21}\n              result=\"tooth\"\n            />\n            <feDiffuseLighting\n              in=\"tooth\"\n              surfaceScale={1.2}\n              lightingColor=\"#FFFFFF\"\n              result=\"lit\"\n            >\n              <feDistantLight azimuth={235} elevation={58} />\n            </feDiffuseLighting>\n            <feComposite\n              in=\"SourceGraphic\"\n              in2=\"lit\"\n              operator=\"arithmetic\"\n              k1={0.16}\n              k2={0.88}\n              result=\"paper\"\n            />\n            <feComposite in=\"paper\" in2=\"SourceAlpha\" operator=\"in\" />\n          </filter>\n          <filter id={frayId} {...userRegion} colorInterpolationFilters=\"sRGB\">\n            <feTurbulence\n              type=\"fractalNoise\"\n              baseFrequency={0.45 / u}\n              numOctaves={2}\n              seed={3}\n              result=\"n\"\n            />\n            <feDisplacementMap\n              in=\"SourceGraphic\"\n              in2=\"n\"\n              scale={3.6 * u}\n              xChannelSelector=\"R\"\n              yChannelSelector=\"G\"\n              result=\"frayed\"\n            />\n            <feTurbulence\n              type=\"fractalNoise\"\n              baseFrequency={`${0.7 / u} ${0.06 / u}`}\n              numOctaves={2}\n              seed={5}\n              result=\"strands\"\n            />\n            <feColorMatrix\n              in=\"strands\"\n              values=\"0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.55 0 0 0 0.62\"\n              result=\"strandA\"\n            />\n            <feComposite in=\"frayed\" in2=\"strandA\" operator=\"in\" />\n          </filter>\n          <linearGradient\n            id={inkId}\n            gradientUnits=\"userSpaceOnUse\"\n            colorInterpolation=\"linearRGB\"\n            x1={W * (0.08 + 0.04 * drift)}\n            y1={-H * 0.1}\n            x2={W * (0.92 - 0.04 * drift)}\n            y2={H * 1.1}\n          >\n            <stop offset=\"0\" stopColor={FLUTED[0]} />\n            <stop offset=\"0.4\" stopColor={accent} />\n            <stop offset=\"0.68\" stopColor={FLUTED[1]} />\n            <stop offset=\"1\" stopColor={FLUTED[2]} />\n          </linearGradient>\n          <filter id={shTopId} {...userRegion}>\n            <feGaussianBlur stdDeviation={sBlur} />\n          </filter>\n          <filter id={shBotId} {...userRegion}>\n            <feGaussianBlur stdDeviation={sBlur * 0.6} />\n          </filter>\n        </defs>\n\n        <g transform={cam}>\n          <rect\n            x={-W}\n            y={-H}\n            width={W * 3}\n            height={H * 3}\n            fill={p.stage}\n            filter={`url(#${paperId})`}\n          />\n          <text\n            x={W / 2}\n            y={headY}\n            fontFamily={display}\n            fontWeight={500}\n            fontSize={fs}\n            letterSpacing={`${-0.035 * fs}px`}\n            textAnchor=\"middle\"\n            fill={p.fg}\n            transform={`translate(${W / 2} ${H / 2}) scale(${headScale}) translate(${-W / 2} ${-H / 2})`}\n          >\n            {title}\n          </text>\n\n          <g transform={topT}>\n            <g transform={`translate(0 ${sOff})`} opacity={0.9}>\n              <path\n                d={piece(topShadow, farTop)}\n                fill={p.shadow}\n                filter={`url(#${shTopId})`}\n              />\n            </g>\n          </g>\n          <g transform={botT}>\n            <g transform={`translate(0 ${-0.8 * u})`} opacity={0.55}>\n              <path\n                d={piece(botShadow, farBot)}\n                fill={p.shadow}\n                filter={`url(#${shBotId})`}\n              />\n            </g>\n            <path\n              d={piece(botFiber, farBot)}\n              fill={p.fiber}\n              filter={`url(#${frayId})`}\n            />\n            <path\n              d={piece(botBody, farBot)}\n              fill={`url(#${inkId})`}\n              filter={`url(#${paperId})`}\n            />\n          </g>\n          <g transform={topT}>\n            <path\n              d={piece(topFiber, farTop)}\n              fill={p.fiber}\n              filter={`url(#${frayId})`}\n            />\n            <path\n              d={piece(topBody, farTop)}\n              fill={`url(#${inkId})`}\n              filter={`url(#${paperId})`}\n            />\n          </g>\n        </g>\n      </svg>\n\n      <AbsoluteFill\n        style={{\n          background: `radial-gradient(ellipse 70% 80% at 30% 16%, ${p.light}, transparent 70%), radial-gradient(ellipse 90% 90% at 50% 50%, transparent 55%, ${p.vignette} 100%)`,\n          pointerEvents: \"none\",\n        }}\n      />\n      <Grain opacity={p.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/torn-edge.tsx"
    }
  ],
  "type": "registry:component"
}
