{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "squonk",
  "type": "registry:block",
  "title": "Squonk",
  "description": "Squonk",
  "files": [
    {
      "path": "components/usages/squonkusage.tsx",
      "content": "\n\nimport { Squonk, SquonkContent } from \"@/registry/open-source/squonk\";\n\nexport default function Page() {\n    return (\n\n\n        <Squonk\n            size={96}\n            elasticity={1.2}\n            cycleDuration={4000}\n            easing='linear'\n            squashAmount={40}\n            stretchAmount={25}\n            bounceHeight={12}\n            radius={22}\n        >\n            <SquonkContent index={0} className='bg-indigo-500'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n            <SquonkContent index={1} className='bg-violet-400'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n            <SquonkContent index={2} className='bg-red-400'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n        </Squonk>\n    );\n}\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/squonkusage.tsx",
      "content": "\n\nimport { Squonk, SquonkContent } from \"@/registry/open-source/squonk\";\n\nexport default function Page() {\n    return (\n\n\n        <Squonk\n            size={96}\n            elasticity={1.2}\n            cycleDuration={4000}\n            easing='linear'\n            squashAmount={40}\n            stretchAmount={25}\n            bounceHeight={12}\n            radius={22}\n        >\n            <SquonkContent index={0} className='bg-indigo-500'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n            <SquonkContent index={1} className='bg-violet-400'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n            <SquonkContent index={2} className='bg-red-400'>\n                <img\n                    src='/itjustworks.jpg'\n                    alt=''\n                    className='w-full h-full object-cover'\n                />\n            </SquonkContent>\n        </Squonk>\n    );\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/squonk.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/registry/utilities/cn';\n\n// Credit: \n// https://scrollxui.dev/docs/components/squonk\n\ninterface Box {\n    x: number;\n    y: number;\n    w: number;\n    h: number;\n    opacity: number;\n    zIndex: number;\n    rotate: number;\n    scale: number;\n}\n\ninterface SquonkCtx {\n    size: number;\n    radius: number;\n    CX: number;\n    FLOOR: number;\n    boxes: Box[];\n    elasticity: number;\n    bounceHeight: number;\n    squashAmount: number;\n    stretchAmount: number;\n}\n\nconst SquonkContext = React.createContext<SquonkCtx | null>(null);\n\nfunction easeIn(t: number) {\n    return t * t * t;\n}\nfunction easeOut(t: number) {\n    return 1 - Math.pow(1 - t, 3);\n}\nfunction easeInOut(t: number) {\n    return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n}\nfunction easeBounce(t: number) {\n    const n1 = 7.5625;\n    const d1 = 2.75;\n    if (t < 1 / d1) {\n        return n1 * t * t;\n    } else if (t < 2 / d1) {\n        return n1 * (t -= 1.5 / d1) * t + 0.75;\n    } else if (t < 2.5 / d1) {\n        return n1 * (t -= 2.25 / d1) * t + 0.9375;\n    } else {\n        return n1 * (t -= 2.625 / d1) * t + 0.984375;\n    }\n}\nfunction lerp(a: number, b: number, t: number) {\n    return a + (b - a) * t;\n}\nfunction clamp(v: number, lo: number, hi: number) {\n    return Math.max(lo, Math.min(hi, v));\n}\n\nfunction compute(\n    p: number,\n    W: number,\n    H: number,\n    FLOOR: number,\n    CX: number,\n    ABOVE: number,\n    elasticity: number,\n    bounceHeight: number,\n    squashAmount: number,\n    stretchAmount: number,\n) {\n    const pFall = 0.28,\n        pSquish = 0.44,\n        pRebound = 0.54,\n        pSink = 0.8,\n        pSettle = 0.89;\n\n    const actualSquash = squashAmount * elasticity;\n    const actualStretch = stretchAmount * elasticity;\n    const actualBounce = bounceHeight * elasticity;\n\n    let ground: Box = {\n        x: CX,\n        y: FLOOR,\n        w: W,\n        h: H,\n        opacity: 1,\n        zIndex: 1,\n        rotate: 0,\n        scale: 1,\n    };\n    let faller: Box = {\n        x: CX,\n        y: ABOVE,\n        w: W,\n        h: H,\n        opacity: 0,\n        zIndex: 2,\n        rotate: 0,\n        scale: 1,\n    };\n\n    if (p < pFall) {\n        const tp = easeIn(p / pFall);\n        faller = {\n            x: CX,\n            y: lerp(ABOVE, FLOOR - H, tp),\n            w: W,\n            h: H,\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    } else if (p < pSquish) {\n        const tp = (p - pFall) / (pSquish - pFall);\n        const squish = Math.sin(tp * Math.PI);\n        const gH = H - squish * actualSquash;\n        const gW = W + squish * actualStretch;\n        const gX = CX - (gW - W) / 2;\n        const gY = FLOOR + (H - gH);\n        ground = {\n            x: gX,\n            y: gY,\n            w: gW,\n            h: gH,\n            opacity: 1,\n            zIndex: 1,\n            rotate: 0,\n            scale: 1,\n        };\n        faller = {\n            x: CX,\n            y: gY - H,\n            w: W,\n            h: H,\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    } else if (p < pRebound) {\n        const tp = easeOut((p - pSquish) / (pRebound - pSquish));\n        const gH = lerp(H, H - actualSquash * 0.24, tp);\n        const gY = FLOOR + (H - gH);\n        const bounce = Math.sin(tp * Math.PI) * actualBounce;\n        ground = {\n            x: CX,\n            y: gY,\n            w: W,\n            h: gH,\n            opacity: 1,\n            zIndex: 1,\n            rotate: 0,\n            scale: 1,\n        };\n        faller = {\n            x: CX,\n            y: gY - H - bounce,\n            w: W,\n            h: H,\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    } else if (p < pSink) {\n        const tp = easeInOut((p - pRebound) / (pSink - pRebound));\n        const gH = lerp(H - actualSquash * 0.24, 0, tp);\n        const gY = FLOOR + (H - gH);\n        ground = {\n            x: CX,\n            y: gY,\n            w: W,\n            h: gH,\n            opacity: 1,\n            zIndex: 1,\n            rotate: 0,\n            scale: 1,\n        };\n        faller = {\n            x: CX,\n            y: gY - H,\n            w: W,\n            h: H,\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    } else if (p < pSettle) {\n        const tp = easeOut((p - pSink) / (pSettle - pSink));\n        const squish = Math.sin(tp * Math.PI) * 0.4;\n        const nH = H - squish * actualSquash * 0.2;\n        const nW = W + squish * actualStretch * 0.2;\n        const nX = CX - (nW - W) / 2;\n        const nY = FLOOR + (H - nH);\n        ground = {\n            x: CX,\n            y: ABOVE,\n            w: W,\n            h: H,\n            opacity: 0,\n            zIndex: 1,\n            rotate: 0,\n            scale: 1,\n        };\n        faller = {\n            x: nX,\n            y: nY,\n            w: nW,\n            h: nH,\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    } else {\n        const tp = easeInOut((p - pSettle) / (1 - pSettle));\n        const settleSquish = Math.sin(easeOut(1) * Math.PI) * 0.4;\n        const startH = H - settleSquish * actualSquash * 0.2;\n        const startW = W + settleSquish * actualStretch * 0.2;\n        const startX = CX - (startW - W) / 2;\n        const startY = FLOOR + (H - startH);\n        ground = {\n            x: CX,\n            y: ABOVE,\n            w: W,\n            h: H,\n            opacity: lerp(0, 1, tp),\n            zIndex: 1,\n            rotate: 0,\n            scale: 1,\n        };\n        faller = {\n            x: lerp(startX, CX, tp),\n            y: lerp(startY, FLOOR, tp),\n            w: lerp(startW, W, tp),\n            h: lerp(startH, H, tp),\n            opacity: 1,\n            zIndex: 2,\n            rotate: 0,\n            scale: 1,\n        };\n    }\n\n    return { ground, faller };\n}\n\ninterface SquonkProps extends React.ComponentProps<'div'> {\n    size?: number;\n    radius?: number;\n    cycleDuration?: number;\n    elasticity?: number;\n    bounceHeight?: number;\n    squashAmount?: number;\n    stretchAmount?: number;\n    easing?: 'linear' | 'bounce' | 'smooth';\n}\n\nfunction Squonk({\n    className,\n    children,\n    size = 96,\n    radius,\n    cycleDuration = 4000,\n    elasticity = 1,\n    bounceHeight,\n    squashAmount,\n    stretchAmount,\n    easing = 'linear',\n    ...props\n}: SquonkProps) {\n    const W = size,\n        H = size;\n    const FLOOR = size * 2.2;\n    const CX = size * 0.18;\n    const ABOVE = -(size * 1.5);\n\n    const finalRadius = radius ?? size * 0.23;\n    const finalBounceHeight = bounceHeight ?? size * 0.094;\n    const finalSquashAmount = squashAmount ?? size * 0.365;\n    const finalStretchAmount = stretchAmount ?? size * 0.302;\n\n    const childrenArray = React.Children.toArray(children);\n    const N = childrenArray.length;\n\n    const rafRef = React.useRef<number>(0);\n    const t0Ref = React.useRef<number | null>(null);\n    const prevPRef = React.useRef(0);\n    const groundIndexRef = React.useRef(0);\n    const fallerIndexRef = React.useRef(1);\n\n    const [boxes, setBoxes] = React.useState<Box[]>(() => {\n        const initialBoxes: Box[] = [];\n        for (let i = 0; i < N; i++) {\n            if (i === 0) {\n                initialBoxes.push({\n                    x: CX,\n                    y: FLOOR,\n                    w: W,\n                    h: H,\n                    opacity: 1,\n                    zIndex: 1,\n                    rotate: 0,\n                    scale: 1,\n                });\n            } else if (i === 1) {\n                initialBoxes.push({\n                    x: CX,\n                    y: ABOVE,\n                    w: W,\n                    h: H,\n                    opacity: 0,\n                    zIndex: 2,\n                    rotate: 0,\n                    scale: 1,\n                });\n            } else {\n                initialBoxes.push({\n                    x: CX,\n                    y: ABOVE,\n                    w: W,\n                    h: H,\n                    opacity: 0,\n                    zIndex: 0,\n                    rotate: 0,\n                    scale: 1,\n                });\n            }\n        }\n        return initialBoxes;\n    });\n\n    React.useEffect(() => {\n        if (N < 2) return;\n\n        const tick = (ts: number) => {\n            if (!t0Ref.current) t0Ref.current = ts;\n            const p = clamp(\n                ((ts - t0Ref.current) % cycleDuration) / cycleDuration,\n                0,\n                1,\n            );\n\n            let easedP = p;\n            if (easing === 'bounce') {\n                easedP = easeBounce(p);\n            } else if (easing === 'smooth') {\n                easedP = easeInOut(p);\n            }\n\n            if (prevPRef.current > 0.92 && p < 0.05) {\n                groundIndexRef.current = (groundIndexRef.current + 1) % N;\n                fallerIndexRef.current = (fallerIndexRef.current + 1) % N;\n            }\n            prevPRef.current = p;\n\n            const { ground, faller } = compute(\n                easedP,\n                W,\n                H,\n                FLOOR,\n                CX,\n                ABOVE,\n                elasticity,\n                finalBounceHeight,\n                finalSquashAmount,\n                finalStretchAmount,\n            );\n\n            setBoxes((prev) => {\n                const newBoxes = [...prev];\n                newBoxes[groundIndexRef.current] = { ...ground, zIndex: 1 };\n                newBoxes[fallerIndexRef.current] = { ...faller, zIndex: 2 };\n\n                for (let i = 0; i < N; i++) {\n                    if (i !== groundIndexRef.current && i !== fallerIndexRef.current) {\n                        newBoxes[i] = { ...newBoxes[i], opacity: 0, zIndex: 0 };\n                    }\n                }\n\n                return newBoxes;\n            });\n\n            rafRef.current = requestAnimationFrame(tick);\n        };\n        rafRef.current = requestAnimationFrame(tick);\n        return () => cancelAnimationFrame(rafRef.current);\n    }, [\n        W,\n        H,\n        cycleDuration,\n        N,\n        FLOOR,\n        CX,\n        ABOVE,\n        elasticity,\n        finalBounceHeight,\n        finalSquashAmount,\n        finalStretchAmount,\n        easing,\n    ]);\n\n    React.useEffect(() => {\n        if (N < 2) return;\n        setBoxes((prev) => {\n            const newBoxes = [...prev];\n            while (newBoxes.length < N) {\n                newBoxes.push({\n                    x: CX,\n                    y: ABOVE,\n                    w: W,\n                    h: H,\n                    opacity: 0,\n                    zIndex: 0,\n                    rotate: 0,\n                    scale: 1,\n                });\n            }\n            while (newBoxes.length > N) {\n                newBoxes.pop();\n            }\n            return newBoxes;\n        });\n    }, [N, W, H, CX, ABOVE]);\n\n    return (\n        <SquonkContext.Provider\n            value={{\n                size,\n                radius: finalRadius,\n                CX,\n                FLOOR,\n                boxes,\n                elasticity,\n                bounceHeight: finalBounceHeight,\n                squashAmount: finalSquashAmount,\n                stretchAmount: finalStretchAmount,\n            }}\n        >\n            <div\n                data-slot='squonk'\n                className={cn('flex justify-center items-end', className)}\n                {...props}\n            >\n                <div\n                    style={{\n                        position: 'relative',\n                        width: W + CX + 20,\n                        height: FLOOR + H,\n                        overflow: 'hidden',\n                    }}\n                >\n                    {childrenArray.map((child, idx) => (\n                        <div key={idx} data-slot={`squonk-slot-${idx}`} data-index={idx}>\n                            {child}\n                        </div>\n                    ))}\n                </div>\n            </div>\n        </SquonkContext.Provider>\n    );\n}\n\nfunction SquonkContent({\n    className,\n    style,\n    children,\n    index,\n    ...props\n}: React.ComponentProps<'div'> & { index: number }) {\n    const ctx = React.useContext(SquonkContext);\n    if (!ctx) return null;\n\n    const { boxes, radius } = ctx;\n    const b = boxes[index];\n    if (!b) return null;\n\n    return (\n        <div\n            data-slot='squonk-content'\n            className={cn('absolute overflow-hidden', className)}\n            style={{\n                left: b.x,\n                top: b.y,\n                width: b.w,\n                height: Math.max(0, b.h),\n                borderRadius: Math.min(radius, Math.max(0, b.h) * 0.3),\n                opacity: b.opacity,\n                zIndex: b.zIndex,\n                transform: `rotate(${b.rotate}deg) scale(${b.scale})`,\n                transformOrigin: 'bottom center',\n                ...style,\n            }}\n            {...props}\n        >\n            {children}\n        </div>\n    );\n}\n\nexport { Squonk, SquonkContent };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}