UI Elements

Gooey SVG

A mesmerizing liquid morphing effect using SVG filters and animations for creating organic, blob-like transitions and shapes.

gooey-svg.tsx
"use client"
import { useState } from "react"
import { AnimatePresence, motion } from "motion/react"

import { Button } from "@/components/ui/button"
import useScreenSize from "@/hooks/useScreenSize"
import GooeySvgFilter from "../components/gooey-svg-filter"


const TAB_CONTENT = [
    {
        title: "2024",
        files: [
            "learning-to-meditate.md",
            "spring-garden-plans.md",
            "travel-wishlist.md",
            "new-coding-projects.md",
        ],
    },
    {
        title: "2023",
        files: [
            "year-in-review.md",
            "marathon-training-log.md",
            "recipe-collection.md",
            "book-reflections.md",
        ],
    },
    {
        title: "2022",
        files: [
            "moving-to-a-new-city.md",
            "starting-a-blog.md",
            "photography-basics.md",
            "first-coding-project.md",
        ],
    },
    {
        title: "2021",
        files: [
            "goals-and-aspirations.md",
            "daily-gratitude.md",
            "learning-to-cook.md",
            "remote-work-journal.md",
        ],
    },
]

export default function GooeyDemo() {
    const [activeTab, setActiveTab] = useState(0)
    const [isGooeyEnabled, setIsGooeyEnabled] = useState(true)
    const screenSize = useScreenSize()

    return (
        <div className="relative w-full h-150 flex justify-center p-8 font-calendas md:text-base text-xs sm:text-sm ">
            <GooeySvgFilter
                id="gooey-filter"
                strength={screenSize.lessThan("md") ? 8 : 15}
            />

            <Button
                variant="outline"
                onClick={() => setIsGooeyEnabled(!isGooeyEnabled)}
                className="absolute top-4 left-4 font-overused-grotesk"
            >
                {isGooeyEnabled ? "Disable filter" : "Enable filter"}
            </Button>

            <div className="w-11/12 md:w-4/5 relative mt-24">
                <div
                    className="absolute inset-0"
                    style={{ filter: isGooeyEnabled ? "url(#gooey-filter)" : "none" }}
                >
                    <div className="flex w-full ">
                        {TAB_CONTENT.map((tab, index) => (
                            <div key={tab.title} className="relative flex-1 h-8 md:h-12">
                                {activeTab === index && (
                                    <motion.div
                                        layoutId="active-tab"
                                        className="absolute inset-0 bg-[#404040]"
                                        transition={{
                                            type: "spring",
                                            bounce: 0.0,
                                            duration: 0.4,
                                        }}
                                    />
                                )}
                            </div>
                        ))}
                    </div>
                    {/* Content panel */}
                    <div className="w-full h-50 sm:h-62.5 md:h-75 bg-[#404040] overflow-hidden text-neutral-200">
                        <AnimatePresence mode="popLayout">
                            <motion.div
                                key={activeTab}
                                initial={{
                                    opacity: 0,
                                    y: 40,
                                    filter: "blur(8px)",
                                }}
                                animate={{
                                    opacity: 1,
                                    y: 0,
                                    filter: "blur(0px)",
                                }}
                                exit={{
                                    opacity: 0,
                                    y: -40,
                                    filter: "blur(8px)",
                                }}
                                transition={{
                                    duration: 0.25,
                                    ease: [0.23, 1, 0.32, 1],
                                }}
                                className="p-8 md:p-12"
                            >
                                <div className="space-y-2 mt-4 sm:mt-8 md:mt-8">
                                    <ul>
                                        {TAB_CONTENT[activeTab].files.map((file, fileIndex) => (
                                            <motion.li
                                                key={file}
                                                initial={{ opacity: 0, x: -8 }}
                                                animate={{ opacity: 1, x: 0 }}
                                                transition={{
                                                    duration: 0.3,
                                                    delay: fileIndex * 0.04,
                                                    ease: [0.23, 1, 0.32, 1],
                                                }}
                                                className="border-b border-muted-foreground/50 pt-2 pb-1 text-white"
                                            >
                                                {file}
                                            </motion.li>
                                        ))}
                                    </ul>
                                </div>
                            </motion.div>
                        </AnimatePresence>
                    </div>
                </div>

                {/* Interactive text overlay, no filter */}
                <div className="relative flex w-full ">
                    {TAB_CONTENT.map((tab, index) => (
                        <button
                            key={tab.title}
                            onClick={() => setActiveTab(index)}
                            className="flex-1 h-8 md:h-12 cursor-pointer"
                            aria-label={`Show ${tab.title} content`}
                        >
                            <span
                                className={`
                w-full h-full flex items-center justify-center transition-colors duration-200
                ${activeTab === index ? "text-neutral-300" : "text-white"}
              `}
                            >
                                {tab.title}
                            </span>
                        </button>
                    ))}
                </div>
            </div>
        </div>
    )
}