DEV Community

shrey vijayvargiya
shrey vijayvargiya

Posted on

Animated Gradient Generator App

Animated c Generator app

How and Why I build one

Tags: Gradient Generator, CSS, Animation, Tool, Nextjs

Hello and welcome to new blog

Today's story is about building an Animated Gradient Generator. Well, the idea came into mind because of gettemplate.website premium-templates, most premium-templates for our PRO clients of gettemplate need catchy and animated websites, including animated backgrounds

And every time I've to provide the same prompt to the cursor AI agent or probably use other websites and that's where I thought why can't I make one and release it for others to use.

Check demo: https://www.gettemplate.website/tool/gradient-generator

I usually make tools to automate my own work, and if I find a good tool, I launch it, giving a good UI.

A few examples can be found on iHateReading jobs portals, Universo, github trending repositories and roadmap templates. These are the features or products that I needed and then launched for others to use.

This one trick is my go-to approach for any new products. If I want and can make something good, I can launch it for the people as well.

This gradient generator provides background CSS that can be directly copied and pasted for use, and it is also available for download for other purposes. Since it includes animation, I've used ffmpeg to create videos in MP4 and GIF formats for people to download.

I do need this tool to create some background animations for YouTube videos quickly, banner images as well, and the reason I made it is that I am done opening figma and canva and logging in every time just for a small gradient generator.

These apps are good, but sometimes we need small apps that load quickly and work well for small or the smallest tasks. This is another reason why I am not building this app further or adding any more features; its whole purpose is to provide background gradients, nothing else.

Let's begin with how I made this tool

It's simple because I've experience, that's why I can say that, but it is simple in general in 2025 with the use of AI.

I am a big fan of the code file component as a product. What it means is, I write code in one single file to create the entire MVP of the product and avoid reuse and any other concepts. One single file is the MVP, and in general, I stick to the plan of writing almost 5k lines of code for every MVP

Our gradient generator, as shown below, has 3k lines of code

import React, {
    useState,
    useRef,
    useCallback,
    useEffect,
    useMemo,
} from "react";
import {
    Copy,
    Plus,
    Trash2,
    X,
    ChevronDown,
    Download,
    InfoIcon,
} from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";

// Gradient Presets - 20 presets with 2 color stops each
const gradientPresets = [
    {
        id: 1,
        name: "Sunset",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#FF6B6B", position: { x: 0, y: 0 } },
            { id: 2, color: "#FFE66D", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 2,
        name: "Ocean",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#4ECDC4", position: { x: 0, y: 0 } },
            { id: 2, color: "#44A08D", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 3,
        name: "Purple Dream",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#667EEA", position: { x: 0, y: 0 } },
            { id: 2, color: "#764BA2", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 4,
        name: "Forest",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#11998E", position: { x: 0, y: 0 } },
            { id: 2, color: "#38EF7D", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 5,
        name: "Coral",
        type: "linear",
        angle: 120,
        stops: [
            { id: 1, color: "#FF9A9E", position: { x: 0, y: 0 } },
            { id: 2, color: "#FECFEF", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 6,
        name: "Blue Sky",
        type: "linear",
        angle: 180,
        stops: [
            { id: 1, color: "#3494E6", position: { x: 0, y: 0 } },
            { id: 2, color: "#EC6EAD", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 7,
        name: "Peach",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#FFECD2", position: { x: 0, y: 0 } },
            { id: 2, color: "#FCB69F", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 8,
        name: "Midnight",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#0F2027", position: { x: 0, y: 0 } },
            { id: 2, color: "#203A43", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 9,
        name: "Rose Gold",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#F093FB", position: { x: 0, y: 0 } },
            { id: 2, color: "#F5576C", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 10,
        name: "Mint",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#A8EDEA", position: { x: 0, y: 0 } },
            { id: 2, color: "#FED6E3", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 11,
        name: "Fire",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#FF416C", position: { x: 0, y: 0 } },
            { id: 2, color: "#FF4B2B", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 12,
        name: "Lavender",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#E0C3FC", position: { x: 0, y: 0 } },
            { id: 2, color: "#8EC5FC", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 13,
        name: "zinc Tea",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#D4FC79", position: { x: 0, y: 0 } },
            { id: 2, color: "#96E6A1", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 14,
        name: "Cherry",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#EB3349", position: { x: 0, y: 0 } },
            { id: 2, color: "#F45C43", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 15,
        name: "Sky Blue",
        type: "linear",
        angle: 180,
        stops: [
            { id: 1, color: "#89F7FE", position: { x: 0, y: 0 } },
            { id: 2, color: "#66A6FF", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 16,
        name: "Orange",
        type: "linear",
        angle: 120,
        stops: [
            { id: 1, color: "#FFB347", position: { x: 0, y: 0 } },
            { id: 2, color: "#FFCC33", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 17,
        name: "Violet",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#8360C3", position: { x: 0, y: 0 } },
            { id: 2, color: "#2EBF91", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 18,
        name: "Pink",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#FF6B95", position: { x: 0, y: 0 } },
            { id: 2, color: "#FFC796", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 19,
        name: "Cyan",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#00F5FF", position: { x: 0, y: 0 } },
            { id: 2, color: "#00D4FF", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 20,
        name: "Golden",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#F6D365", position: { x: 0, y: 0 } },
            { id: 2, color: "#FDA085", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 21,
        name: "Dark Night",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#1a1a2e", position: { x: 0, y: 0 } },
            { id: 2, color: "#16213e", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 22,
        name: "Deep Purple",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#2d1b69", position: { x: 0, y: 0 } },
            { id: 2, color: "#11998e", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 23,
        name: "Charcoal",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#2c3e50", position: { x: 0, y: 0 } },
            { id: 2, color: "#34495e", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 24,
        name: "Dark Blue",
        type: "linear",
        angle: 180,
        stops: [
            { id: 1, color: "#0c0c0c", position: { x: 0, y: 0 } },
            { id: 2, color: "#1a237e", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 25,
        name: "Dark Forest",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#0f2027", position: { x: 0, y: 0 } },
            { id: 2, color: "#203a43", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 26,
        name: "Ebony",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#232526", position: { x: 0, y: 0 } },
            { id: 2, color: "#414345", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 27,
        name: "Dark Red",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#1a0000", position: { x: 0, y: 0 } },
            { id: 2, color: "#4a0000", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 28,
        name: "Midnight Blue",
        type: "linear",
        angle: 120,
        stops: [
            { id: 1, color: "#0f0c29", position: { x: 0, y: 0 } },
            { id: 2, color: "#302b63", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 29,
        name: "Dark Teal",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#134e5e", position: { x: 0, y: 0 } },
            { id: 2, color: "#71b280", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 30,
        name: "Shadow",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#1e3c72", position: { x: 0, y: 0 } },
            { id: 2, color: "#2a5298", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 31,
        name: "Dark Violet",
        type: "linear",
        angle: 90,
        stops: [
            { id: 1, color: "#4b0082", position: { x: 0, y: 0 } },
            { id: 2, color: "#6a0dad", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 32,
        name: "Obsidian",
        type: "linear",
        angle: 180,
        stops: [
            { id: 1, color: "#000000", position: { x: 0, y: 0 } },
            { id: 2, color: "#1a1a1a", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 33,
        name: "Dark Emerald",
        type: "linear",
        angle: 135,
        stops: [
            { id: 1, color: "#0d2818", position: { x: 0, y: 0 } },
            { id: 2, color: "#1a5f3f", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 34,
        name: "Deep Space",
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#000000", position: { x: 0, y: 0 } },
            { id: 2, color: "#1a1a2e", position: { x: 100, y: 100 } },
        ],
    },
    {
        id: 35,
        name: "Dark Orange",
        type: "linear",
        angle: 120,
        stops: [
            { id: 1, color: "#2c1810", position: { x: 0, y: 0 } },
            { id: 2, color: "#8b4513", position: { x: 100, y: 100 } },
        ],
    },
];

// Background Pattern Presets
const backgroundPatternPresets = [
    {
        id: 1,
        name: "None",
        css: "",
        preview: "transparent",
    },
    {
        id: 2,
        name: "Grid Squares",
        css: `linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
            linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)`,
        backgroundSize: "20px 20px",
        preview: `linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
            linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)`,
    },
    {
        id: 3,
        name: "Vertical Lines",
        css: `repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
        backgroundSize: "20px 20px",
        preview: `repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
    },
    {
        id: 4,
        name: "Horizontal Lines",
        css: `repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
        backgroundSize: "20px 20px",
        preview: `repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
    },
    {
        id: 5,
        name: "Diagonal Lines",
        css: `repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
        backgroundSize: "20px 20px",
        preview: `repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 20px)`,
    },
    {
        id: 6,
        name: "Dots Pattern",
        css: `radial-gradient(circle, rgba(0, 0, 0, 0.15) 1px, transparent 1px)`,
        backgroundSize: "20px 20px",
        preview: `radial-gradient(circle, rgba(0, 0, 0, 0.15) 1px, transparent 1px)`,
    },
    {
        id: 7,
        name: "Large Circle",
        css: `radial-gradient(circle at center, rgba(0, 0, 0, 0.1) 0%, transparent 50%)`,
        backgroundSize: "100% 100%",
        preview: `radial-gradient(circle at center, rgba(0, 0, 0, 0.1) 0%, transparent 50%)`,
    },
    {
        id: 8,
        name: "Ellipse Center",
        css: `radial-gradient(ellipse at center, rgba(0, 0, 0, 0.1) 0%, transparent 70%)`,
        backgroundSize: "100% 100%",
        preview: `radial-gradient(ellipse at center, rgba(0, 0, 0, 0.1) 0%, transparent 70%)`,
    },
    {
        id: 9,
        name: "Cross Pattern",
        css: `linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
            linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)`,
        backgroundSize: "30px 30px",
        preview: `linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),
            linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)`,
    },
    {
        id: 10,
        name: "Hexagon Pattern",
        css: `repeating-linear-gradient(60deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px),
            repeating-linear-gradient(120deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px),
            repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px)`,
        backgroundSize: "30px 30px",
        preview: `repeating-linear-gradient(60deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px),
            repeating-linear-gradient(120deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px),
            repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0px, rgba(0, 0, 0, 0.1) 1px, transparent 1px, transparent 30px)`,
    },
    {
        id: 11,
        name: "Wavy Lines",
        css: `repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 0, 0, 0.1) 2px, rgba(0, 0, 0, 0.1) 4px)`,
        backgroundSize: "20px 20px",
        preview: `repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 0, 0, 0.1) 2px, rgba(0, 0, 0, 0.1) 4px)`,
    },
    {
        id: 12,
        name: "Checkerboard",
        css: `linear-gradient(45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%),
            linear-gradient(-45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%),
            linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%),
            linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%)`,
        backgroundSize: "20px 20px",
        backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
        preview: `linear-gradient(45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%),
            linear-gradient(-45deg, rgba(0, 0, 0, 0.1) 25%, transparent 25%),
            linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%),
            linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.1) 75%)`,
    },
];

// Helper function to generate gradient CSS from preset
const generatePresetGradientCSS = (preset) => {
    if (preset.type === "linear") {
        return `linear-gradient(${preset.angle || 45}deg, ${preset.stops[0].color}, ${preset.stops[1].color})`;
    } else if (preset.type === "radial") {
        return `radial-gradient(circle, ${preset.stops[0].color}, ${preset.stops[1].color})`;
    } else if (preset.type === "conic") {
        return `conic-gradient(${preset.stops[0].color}, ${preset.stops[1].color})`;
    }
    return `linear-gradient(45deg, ${preset.stops[0].color}, ${preset.stops[1].color})`;
};

// Custom Dropdown Component
const Dropdown = ({
    value,
    onChange,
    options,
    placeholder,
    className = "",
}) => {
    const [isOpen, setIsOpen] = useState(false);
    const dropdownRef = useRef(null);

    const handleClickOutside = useCallback((event) => {
        if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
            setIsOpen(false);
        }
    }, []);

    useEffect(() => {
        document.addEventListener("mousedown", handleClickOutside);
        return () => document.removeEventListener("mousedown", handleClickOutside);
    }, [handleClickOutside]);

    const selectedOption = options.find((option) => option.value === value);

    return (
        <div ref={dropdownRef} className={`relative ${className}`}>
            <button
                type="button"
                onClick={() => setIsOpen(!isOpen)}
                className="w-full h-8 px-2 text-xs border border-zinc-200 bg-white rounded-xl focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:border-zinc-400 transition-colors flex items-center justify-between"
            >
                <span className="text-left">
                    {selectedOption?.label || placeholder}
                </span>
                <ChevronDown
                    className={`w-3 h-3 transition-transform ${
                        isOpen ? "rotate-180" : ""
                    }`}
                />
            </button>

            <AnimatePresence>
                {isOpen && (
                    <motion.div
                        initial={{ opacity: 0, y: -10 }}
                        animate={{ opacity: 1, y: 0 }}
                        exit={{ opacity: 0, y: -10 }}
                        transition={{ duration: 0.15 }}
                        className="absolute z-50 w-full mt-1 bg-white border border-zinc-200 rounded-xl shadow-lg overflow-hidden"
                    >
                        {options.map((option) => (
                            <button
                                key={option.value}
                                type="button"
                                onClick={() => {
                                    onChange(option.value);
                                    setIsOpen(false);
                                }}
                                className={`w-full px-2 py-1.5 text-xs text-left hover:bg-zinc-50 transition-colors ${
                                    value === option.value
                                        ? "bg-zinc-100 text-zinc-900"
                                        : "text-zinc-700"
                                }`}
                            >
                                {option.label}
                            </button>
                        ))}
                    </motion.div>
                )}
            </AnimatePresence>
        </div>
    );
};

const GradientGenerator = () => {
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [gradient, setGradient] = useState({
        type: "linear",
        angle: 45,
        stops: [
            { id: 1, color: "#FF8D00", position: { x: 0, y: 0 } },
            { id: 2, color: "#FFB870", position: { x: 50, y: 30 } },
        ],
        noise: {
            enabled: true,
            intensity: 0.3,
        },
        animation: {
            enabled: true,
            type: "rotate",
            duration: 3,
            easing: "ease-in-out",
            direction: "normal",
        },
        backgroundAnimation: {
            enabled: true,
            type: "slide",
            direction: "right",
            speed: 5,
            easing: "linear",
        },
        dimensions: {
            width: 1080,
            height: 1920,
        },
        backgroundPattern: {
            id: 1,
            name: "None",
        },
    });

    const [isPlaying, setIsPlaying] = useState(true);
    const [copied, setCopied] = useState("");
    const [selectedStop, setSelectedStop] = useState(null);
    const [isDragging, setIsDragging] = useState(false);
    const [downloadDimension, setDownloadDimension] = useState("desktop");
    const [previewFrameSize, setPreviewFrameSize] = useState("desktop");
    const [isDownloadDropdownOpen, setIsDownloadDropdownOpen] = useState(false);
    const [isPanelDownloadDropdownOpen, setIsPanelDownloadDropdownOpen] =
        useState(false);
    const [isGeneratingMP4, setIsGeneratingMP4] = useState(false);
    const [mp4Progress, setMp4Progress] = useState(0);
    const [isGradientPresetsOpen, setIsGradientPresetsOpen] = useState(false);
    const [isBackgroundPatternsOpen, setIsBackgroundPatternsOpen] =
        useState(false);
    const gradientPresetsRef = useRef(null);
    const backgroundPatternsRef = useRef(null);
    const previewRef = useRef(null);
    const modalPreviewRef = useRef(null);
    const downloadDropdownRef = useRef(null);
    const panelDownloadDropdownRef = useRef(null);

    const dimensionPresets = {
        mobile: { width: 1080, height: 1920, label: "Mobile (1080×1920)" },
        desktop: { width: 1920, height: 1080, label: "Desktop (1920×1080)" },
    };

    const previewFramePresets = {
        mobile: { width: 1080, height: 1920, label: "Mobile", icon: "📱" },
        tablet: { width: 1024, height: 1366, label: "Tablet", icon: "📱" },
        desktop: { width: 1920, height: 1080, label: "Desktop", icon: "🖥️" },
        laptop: { width: 1366, height: 768, label: "Laptop", icon: "💻" },
        ultrawide: { width: 2560, height: 1080, label: "Ultrawide", icon: "🖥️" },
        custom: { width: 1080, height: 1080, label: "Square", icon: "⬜" },
    };

    const addColorStop = () => {
        const newStop = {
            id: Date.now(),
            color: "#10b981",
            position: {
                x: Math.random() * 80 + 10,
                y: Math.random() * 80 + 10,
            },
        };
        setGradient((prev) => ({
            ...prev,
            stops: [...prev.stops, newStop],
        }));
    };

    const removeColorStop = (id) => {
        setGradient((prev) => ({
            ...prev,
            stops: prev.stops.filter((stop) => stop.id !== id),
        }));
    };

    const updateColorStop = (id, property, value) => {
        setGradient((prev) => ({
            ...prev,
            stops: prev.stops.map((stop) =>
                stop.id === id ? { ...stop, [property]: value } : stop
            ),
        }));
    };

    const handleMouseDown = useCallback((e, stopId) => {
        e.preventDefault();
        setIsDragging(true);
        setSelectedStop(stopId);

        const previewRect = previewRef.current.getBoundingClientRect();

        const handleMouseMove = (e) => {
            const x = ((e.clientX - previewRect.left) / previewRect.width) * 100;
            const y = ((e.clientY - previewRect.top) / previewRect.height) * 100;

            const clampedX = Math.max(0, Math.min(100, x));
            const clampedY = Math.max(0, Math.min(100, y));

            updateColorStop(stopId, "position", { x: clampedX, y: clampedY });
        };

        const handleMouseUp = () => {
            setIsDragging(false);
            setSelectedStop(null);
            document.removeEventListener("mousemove", handleMouseMove);
            document.removeEventListener("mouseup", handleMouseUp);
        };

        document.addEventListener("mousemove", handleMouseMove);
        document.addEventListener("mouseup", handleMouseUp);
    }, []);

    const handleKeyDown = useCallback(
        (e, stopId) => {
            if (!selectedStop || selectedStop !== stopId) return;

            const step = e.shiftKey ? 10 : 1;
            const stop = gradient.stops.find((s) => s.id === stopId);
            if (!stop) return;

            let newPosition = { ...stop.position };

            switch (e.key) {
                case "ArrowLeft":
                    newPosition.x = Math.max(0, stop.position.x - step);
                    break;
                case "ArrowRight":
                    newPosition.x = Math.min(100, stop.position.x + step);
                    break;
                case "ArrowUp":
                    newPosition.y = Math.max(0, stop.position.y - step);
                    break;
                case "ArrowDown":
                    newPosition.y = Math.min(100, stop.position.y + step);
                    break;
                case "Delete":
                case "Backspace":
                    removeColorStop(stopId);
                    return;
                default:
                    return;
            }

            e.preventDefault();
            updateColorStop(stopId, "position", newPosition);
        },
        [selectedStop, gradient.stops]
    );

    const generateGradientCSS = () => {
        // Handle empty case - return white background when no stops
        if (gradient.stops.length === 0) {
            return "#ffffff";
        }

        const sortedStops = [...gradient.stops].sort((a, b) => {
            const distA = Math.sqrt(
                a.position.x * a.position.x + a.position.y * a.position.y
            );
            const distB = Math.sqrt(
                b.position.x * b.position.x + b.position.y * b.position.y
            );
            return distA - distB;
        });

        if (gradient.type === "linear") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const stopsString = sortedStops
                .map((stop) => `${stop.color} ${Math.round(stop.position.x)}%`)
                .join(", ");

            return `linear-gradient(${Math.round(angle)}deg, ${stopsString})`;
        }

        if (gradient.type === "radial") {
            const stopsString = sortedStops
                .map(
                    (stop) =>
                        `${stop.color} ${Math.round(
                            Math.sqrt(
                                Math.pow(stop.position.x - 50, 2) +
                                    Math.pow(stop.position.y - 50, 2)
                            )
                        )}%`
                )
                .join(", ");

            return `radial-gradient(circle at 50% 50%, ${stopsString})`;
        }

        if (gradient.type === "conic") {
            const stopsString = sortedStops
                .map((stop) => `${stop.color} ${Math.round(stop.position.x)}%`)
                .join(", ");

            return `conic-gradient(from 0deg, ${stopsString})`;
        }

        if (gradient.type === "mesh") {
            // Mesh gradient using multiple radial gradients as background layers
            const meshGradients = sortedStops
                .map(
                    (stop) =>
                        `radial-gradient(circle at ${stop.position.x}% ${stop.position.y}%, ${stop.color} 0%, transparent 50%)`
                )
                .join(", ");
            return meshGradients;
        }

        if (gradient.type === "grid") {
            // Grid pattern using repeating linear gradients
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const stopsString = sortedStops
                .map((stop) => `${stop.color} ${Math.round(stop.position.x)}%`)
                .join(", ");

            return `repeating-linear-gradient(${Math.round(angle)}deg, ${stopsString})`;
        }

        if (gradient.type === "sharp") {
            // Sharp transitions - linear gradient with hard stops
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            // Create sharp transitions by duplicating colors at same position
            const sharpStops = sortedStops
                .flatMap((stop, index) => {
                    if (index === sortedStops.length - 1) {
                        return [`${stop.color} ${Math.round(stop.position.x)}%`];
                    }
                    const nextStop = sortedStops[index + 1];
                    const midPoint = (stop.position.x + nextStop.position.x) / 2;
                    return [
                        `${stop.color} ${Math.round(stop.position.x)}%`,
                        `${stop.color} ${Math.round(midPoint)}%`,
                        `${nextStop.color} ${Math.round(midPoint)}%`,
                    ];
                })
                .join(", ");

            return `linear-gradient(${Math.round(angle)}deg, ${sharpStops})`;
        }

        if (gradient.type === "soft") {
            // Soft transitions - linear gradient with extended color stops
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            // Create soft transitions by extending color stops
            const softStops = sortedStops
                .map((stop, index) => {
                    if (index === 0) {
                        return `${stop.color} ${Math.max(0, Math.round(stop.position.x) - 10)}%`;
                    }
                    if (index === sortedStops.length - 1) {
                        return `${stop.color} ${Math.min(100, Math.round(stop.position.x) + 10)}%`;
                    }
                    const prevStop = sortedStops[index - 1];
                    const nextStop = sortedStops[index + 1];
                    const startPos = Math.max(
                        prevStop.position.x,
                        Math.round(stop.position.x) - 15
                    );
                    const endPos = Math.min(
                        nextStop.position.x,
                        Math.round(stop.position.x) + 15
                    );
                    return `${stop.color} ${Math.round(startPos)}% ${Math.round(endPos)}%`;
                })
                .join(", ");

            return `linear-gradient(${Math.round(angle)}deg, ${softStops})`;
        }

        return "";
    };

    // Generate background pattern CSS
    const generateBackgroundPatternCSS = () => {
        const pattern = backgroundPatternPresets.find(
            (p) => p.id === gradient.backgroundPattern.id
        );
        if (!pattern || !pattern.css) return "";

        let css = `background-image: ${pattern.css};`;
        if (pattern.backgroundSize) {
            css += `\nbackground-size: ${pattern.backgroundSize};`;
        }
        if (pattern.backgroundPosition) {
            css += `\nbackground-position: ${pattern.backgroundPosition};`;
        }
        return css;
    };

    // Get background pattern style object for inline styles
    const getBackgroundPatternStyle = () => {
        const gradientCSS = generateGradientCSS();
        const pattern = backgroundPatternPresets.find(
            (p) => p.id === gradient.backgroundPattern.id
        );

        // If no pattern or pattern is "None", just return gradient
        if (!pattern || !pattern.css) {
            return {
                background: gradientCSS,
            };
        }

        // Combine gradient and pattern in background-image
        const combinedBackground = `${gradientCSS}, ${pattern.css}`;

        const style = {
            backgroundImage: combinedBackground,
        };
        if (pattern.backgroundSize) {
            // Combine background sizes: gradient uses auto, pattern uses its size
            style.backgroundSize = `auto, ${pattern.backgroundSize}`;
        }
        if (pattern.backgroundPosition) {
            // Combine background positions
            style.backgroundPosition = `0 0, ${pattern.backgroundPosition}`;
        }
        return style;
    };

    const generateAnimationCSS = () => {
        const {
            type: bgType,
            direction: bgDirection,
            speed,
            easing: bgEasing,
            repeat,
        } = gradient.backgroundAnimation;

        let backgroundAnimation = "";

        if (gradient.backgroundAnimation.enabled) {
            const infinite = repeat ? "infinite" : "1";
            switch (bgType) {
                case "slide":
                    if (bgDirection === "right") {
                        backgroundAnimation = `slideRight ${speed}s ${bgEasing} ${infinite}`;
                    } else if (bgDirection === "left") {
                        backgroundAnimation = `slideLeft ${speed}s ${bgEasing} ${infinite}`;
                    } else if (bgDirection === "up") {
                        backgroundAnimation = `slideUp ${speed}s ${bgEasing} ${infinite}`;
                    } else {
                        backgroundAnimation = `slideDown ${speed}s ${bgEasing} ${infinite}`;
                    }
                    break;
                case "wave":
                    backgroundAnimation = `wave ${speed}s ${bgEasing} ${infinite}`;
                    break;
            }
        }

        return backgroundAnimation;
    };

    const copyToClipboard = async (text, type) => {
        try {
            await navigator.clipboard.writeText(text);
            setCopied(type);
            setTimeout(() => setCopied(""), 2000);
        } catch (err) {
            console.error("Failed to copy: ", err);
        }
    };

    // Simple SVG download
    const downloadSVG = (dimensionType = downloadDimension) => {
        const dimensions =
            previewFramePresets[dimensionType] ||
            dimensionPresets[dimensionType] ||
            dimensionPresets.mobile;
        const width = dimensions.width;
        const height = dimensions.height;

        // Handle empty case - return white background SVG
        if (gradient.stops.length === 0) {
            const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
  <rect width="100%" height="100%" fill="#ffffff" />
</svg>`;
            const blob = new Blob([svg], { type: "image/svg+xml" });
            const url = URL.createObjectURL(blob);
            const link = document.createElement("a");
            link.href = url;
            link.download = `gradient-${dimensionType}-${width}x${height}-${Date.now()}.svg`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
            return;
        }

        const sortedStops = [...gradient.stops].sort((a, b) => {
            const distA = Math.sqrt(
                a.position.x * a.position.x + a.position.y * a.position.y
            );
            const distB = Math.sqrt(
                b.position.x * b.position.x + b.position.y * b.position.y
            );
            return distA - distB;
        });

        let gradientElement = "";
        if (gradient.type === "linear") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const radians = (angle * Math.PI) / 180;
            const x1 = 50 - 50 * Math.cos(radians);
            const y1 = 50 - 50 * Math.sin(radians);
            const x2 = 50 + 50 * Math.cos(radians);
            const y2 = 50 + 50 * Math.sin(radians);

            gradientElement = `
      <linearGradient id="gradientFill" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">
        ${sortedStops
                    .map(
                        (stop) =>
                            `<stop offset="${stop.position.x}%" stop-color="${stop.color}" />`
                    )
                    .join("\n        ")}
      </linearGradient>`;
        } else if (gradient.type === "radial") {
            gradientElement = `
      <radialGradient id="gradientFill" cx="50%" cy="50%" r="50%">
        ${sortedStops
                    .map((stop) => {
                        const distance = Math.sqrt(
                            Math.pow(stop.position.x - 50, 2) +
                                Math.pow(stop.position.y - 50, 2)
                        );
                        return `<stop offset="${distance}%" stop-color="${stop.color}" />`;
                    })
                    .join("\n        ")}
      </radialGradient>`;
        } else if (gradient.type === "conic") {
            gradientElement = `
      <linearGradient id="gradientFill" x1="50%" y1="0%" x2="50%" y2="100%">
        ${sortedStops
                    .map(
                        (stop) =>
                            `<stop offset="${stop.position.x}%" stop-color="${stop.color}" />`
                    )
                    .join("\n        ")}
      </linearGradient>`;
        } else if (gradient.type === "mesh") {
            // For mesh, use multiple radial gradients
            const meshGradients = sortedStops
                .map(
                    (stop, index) => `
      <radialGradient id="meshGradient${index}" cx="${stop.position.x}%" cy="${stop.position.y}%" r="50%">
        <stop offset="0%" stop-color="${stop.color}" stop-opacity="1" />
        <stop offset="50%" stop-color="${stop.color}" stop-opacity="0.5" />
        <stop offset="100%" stop-color="${stop.color}" stop-opacity="0" />
      </radialGradient>`
                )
                .join("");
            gradientElement = meshGradients;
        } else if (gradient.type === "grid") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);
            const radians = (angle * Math.PI) / 180;
            const x1 = 50 - 50 * Math.cos(radians);
            const y1 = 50 - 50 * Math.sin(radians);
            const x2 = 50 + 50 * Math.cos(radians);
            const y2 = 50 + 50 * Math.sin(radians);

            gradientElement = `
      <linearGradient id="gradientFill" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%" gradientUnits="userSpaceOnUse">
        ${sortedStops
                    .map(
                        (stop) =>
                            `<stop offset="${stop.position.x}%" stop-color="${stop.color}" />`
                    )
                    .join("\n        ")}
      </linearGradient>`;
        } else if (gradient.type === "sharp") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);
            const radians = (angle * Math.PI) / 180;
            const x1 = 50 - 50 * Math.cos(radians);
            const y1 = 50 - 50 * Math.sin(radians);
            const x2 = 50 + 50 * Math.cos(radians);
            const y2 = 50 + 50 * Math.sin(radians);

            const sharpStops = sortedStops.flatMap((stop, index) => {
                if (index === sortedStops.length - 1) {
                    return [
                        `<stop offset="${stop.position.x}%" stop-color="${stop.color}" />`,
                    ];
                }
                const nextStop = sortedStops[index + 1];
                const midPoint = (stop.position.x + nextStop.position.x) / 2;
                return [
                    `<stop offset="${stop.position.x}%" stop-color="${stop.color}" />`,
                    `<stop offset="${midPoint}%" stop-color="${stop.color}" />`,
                    `<stop offset="${midPoint}%" stop-color="${nextStop.color}" />`,
                ];
            });

            gradientElement = `
      <linearGradient id="gradientFill" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">
        ${sharpStops.join("\n        ")}
      </linearGradient>`;
        } else if (gradient.type === "soft") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);
            const radians = (angle * Math.PI) / 180;
            const x1 = 50 - 50 * Math.cos(radians);
            const y1 = 50 - 50 * Math.sin(radians);
            const x2 = 50 + 50 * Math.cos(radians);
            const y2 = 50 + 50 * Math.sin(radians);

            const softStops = sortedStops.map((stop, index) => {
                if (index === 0) {
                    return `<stop offset="${Math.max(0, Math.round(stop.position.x) - 10)}%" stop-color="${stop.color}" />`;
                }
                if (index === sortedStops.length - 1) {
                    return `<stop offset="${Math.min(100, Math.round(stop.position.x) + 10)}%" stop-color="${stop.color}" />`;
                }
                const prevStop = sortedStops[index - 1];
                const nextStop = sortedStops[index + 1];
                const startPos = Math.max(
                    prevStop.position.x,
                    Math.round(stop.position.x) - 15
                );
                const endPos = Math.min(
                    nextStop.position.x,
                    Math.round(stop.position.x) + 15
                );
                return `<stop offset="${Math.round(startPos)}%" stop-color="${stop.color}" />
        <stop offset="${Math.round(endPos)}%" stop-color="${stop.color}" />`;
            });

            gradientElement = `
      <linearGradient id="gradientFill" x1="${x1}%" y1="${y1}%" x2="${x2}%" y2="${y2}%">
        ${softStops.join("\n        ")}
      </linearGradient>`;
        } else {
            // Default to radial
            gradientElement = `
      <radialGradient id="gradientFill" cx="50%" cy="50%" r="50%">
        ${sortedStops
                    .map((stop) => {
                        const distance = Math.sqrt(
                            Math.pow(stop.position.x - 50, 2) +
                                Math.pow(stop.position.y - 50, 2)
                        );
                        return `<stop offset="${distance}%" stop-color="${stop.color}" />`;
                    })
                    .join("\n        ")}
      </radialGradient>`;
        }

        // For mesh gradients, we need multiple rects
        let svgContent = "";
        if (gradient.type === "mesh") {
            svgContent = sortedStops
                .map(
                    (stop, index) => `
  <rect width="100%" height="100%" fill="url(#meshGradient${index})" />`
                )
                .join("");
        } else {
            svgContent = `<rect width="100%" height="100%" fill="url(#gradientFill)" />`;
        }

        const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
  <defs>${gradientElement}
  </defs>${svgContent}
</svg>`;

        const blob = new Blob([svg], { type: "image/svg+xml" });
        const url = URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = url;
        link.download = `gradient-${dimensionType}-${width}x${height}-${Date.now()}.svg`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    };

    // Canvas-based download for PNG/JPEG
    const downloadRaster = (
        format = "png",
        dimensionType = downloadDimension
    ) => {
        const dimensions =
            previewFramePresets[dimensionType] ||
            dimensionPresets[dimensionType] ||
            dimensionPresets.mobile;
        const canvas = document.createElement("canvas");
        const width = dimensions.width;
        const height = dimensions.height;
        canvas.width = width;
        canvas.height = height;
        const ctx = canvas.getContext("2d");

        // Handle empty case - fill with white background
        if (gradient.stops.length === 0) {
            ctx.fillStyle = "#ffffff";
            ctx.fillRect(0, 0, width, height);
            canvas.toBlob(
                (blob) => {
                    if (!blob) return;
                    const url = URL.createObjectURL(blob);
                    const link = document.createElement("a");
                    link.href = url;
                    link.download = `gradient-${dimensionType}-${width}x${height}-${Date.now()}.${format}`;
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    URL.revokeObjectURL(url);
                },
                format === "jpeg" ? "image/jpeg" : "image/png",
                format === "jpeg" ? 0.92 : undefined
            );
            return;
        }

        let gradientObj;
        const sortedStops = [...gradient.stops].sort((a, b) => {
            const distA = Math.sqrt(
                a.position.x * a.position.x + a.position.y * a.position.y
            );
            const distB = Math.sqrt(
                b.position.x * b.position.x + b.position.y * b.position.y
            );
            return distA - distB;
        });

        if (gradient.type === "linear") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const radians = (angle * Math.PI) / 180;
            const x1 = width / 2 - (width / 2) * Math.cos(radians);
            const y1 = height / 2 - (width / 2) * Math.sin(radians);
            const x2 = width / 2 + (width / 2) * Math.cos(radians);
            const y2 = height / 2 + (width / 2) * Math.sin(radians);

            gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
            sortedStops.forEach((stop) => {
                const position = Math.round(stop.position.x) / 100;
                gradientObj.addColorStop(position, stop.color);
            });
        } else if (gradient.type === "radial") {
            gradientObj = ctx.createRadialGradient(
                width / 2,
                height / 2,
                0,
                width / 2,
                height / 2,
                Math.max(width, height) / 2
            );
            sortedStops.forEach((stop) => {
                const distance = Math.sqrt(
                    Math.pow(stop.position.x - 50, 2) + Math.pow(stop.position.y - 50, 2)
                );
                const position = Math.round(distance) / 100;
                gradientObj.addColorStop(position, stop.color);
            });
        } else if (gradient.type === "conic") {
            // Conic gradients are approximated with linear gradient
            gradientObj = ctx.createLinearGradient(width / 2, 0, width / 2, height);
            sortedStops.forEach((stop) => {
                const position = Math.round(stop.position.x) / 100;
                gradientObj.addColorStop(position, stop.color);
            });
        } else if (gradient.type === "mesh") {
            // For mesh, draw multiple radial gradients
            ctx.globalCompositeOperation = "multiply";
            sortedStops.forEach((stop) => {
                const meshGradient = ctx.createRadialGradient(
                    (stop.position.x / 100) * width,
                    (stop.position.y / 100) * height,
                    0,
                    (stop.position.x / 100) * width,
                    (stop.position.y / 100) * height,
                    Math.max(width, height) / 2
                );
                meshGradient.addColorStop(0, stop.color);
                meshGradient.addColorStop(0.5, stop.color);
                meshGradient.addColorStop(1, "transparent");
                ctx.fillStyle = meshGradient;
                ctx.fillRect(0, 0, width, height);
            });
            ctx.globalCompositeOperation = "source-over";
            return; // Early return for mesh
        } else if (gradient.type === "grid") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const radians = (angle * Math.PI) / 180;
            const x1 = width / 2 - (width / 2) * Math.cos(radians);
            const y1 = height / 2 - (width / 2) * Math.sin(radians);
            const x2 = width / 2 + (width / 2) * Math.cos(radians);
            const y2 = height / 2 + (width / 2) * Math.sin(radians);

            // Create pattern for repeating gradient
            const patternCanvas = document.createElement("canvas");
            patternCanvas.width = width;
            patternCanvas.height = height;
            const patternCtx = patternCanvas.getContext("2d");
            const patternGradient = patternCtx.createLinearGradient(x1, y1, x2, y2);
            sortedStops.forEach((stop) => {
                const position = Math.round(stop.position.x) / 100;
                patternGradient.addColorStop(position, stop.color);
            });
            patternCtx.fillStyle = patternGradient;
            patternCtx.fillRect(0, 0, width, height);
            const pattern = ctx.createPattern(patternCanvas, "repeat");
            ctx.fillStyle = pattern;
        } else if (gradient.type === "sharp") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const radians = (angle * Math.PI) / 180;
            const x1 = width / 2 - (width / 2) * Math.cos(radians);
            const y1 = height / 2 - (width / 2) * Math.sin(radians);
            const x2 = width / 2 + (width / 2) * Math.cos(radians);
            const y2 = height / 2 + (width / 2) * Math.sin(radians);

            gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
            sortedStops.forEach((stop, index) => {
                const position = Math.round(stop.position.x) / 100;
                gradientObj.addColorStop(position, stop.color);
                if (index < sortedStops.length - 1) {
                    const nextStop = sortedStops[index + 1];
                    const midPoint = (stop.position.x + nextStop.position.x) / 200;
                    gradientObj.addColorStop(midPoint, stop.color);
                    gradientObj.addColorStop(midPoint, nextStop.color);
                }
            });
        } else if (gradient.type === "soft") {
            const firstStop = sortedStops[0];
            const lastStop = sortedStops[sortedStops.length - 1];
            const angle =
                Math.atan2(
                    lastStop.position.y - firstStop.position.y,
                    lastStop.position.x - firstStop.position.x
                ) *
                (180 / Math.PI);

            const radians = (angle * Math.PI) / 180;
            const x1 = width / 2 - (width / 2) * Math.cos(radians);
            const y1 = height / 2 - (width / 2) * Math.sin(radians);
            const x2 = width / 2 + (width / 2) * Math.cos(radians);
            const y2 = height / 2 + (width / 2) * Math.sin(radians);

            gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
            sortedStops.forEach((stop, index) => {
                if (index === 0) {
                    gradientObj.addColorStop(
                        Math.max(0, (Math.round(stop.position.x) - 10) / 100),
                        stop.color
                    );
                } else if (index === sortedStops.length - 1) {
                    gradientObj.addColorStop(
                        Math.min(1, (Math.round(stop.position.x) + 10) / 100),
                        stop.color
                    );
                } else {
                    const prevStop = sortedStops[index - 1];
                    const nextStop = sortedStops[index + 1];
                    const startPos = Math.max(
                        prevStop.position.x / 100,
                        (Math.round(stop.position.x) - 15) / 100
                    );
                    const endPos = Math.min(
                        nextStop.position.x / 100,
                        (Math.round(stop.position.x) + 15) / 100
                    );
                    gradientObj.addColorStop(startPos, stop.color);
                    gradientObj.addColorStop(endPos, stop.color);
                }
            });
        } else {
            // Default to radial
            gradientObj = ctx.createRadialGradient(
                width / 2,
                height / 2,
                0,
                width / 2,
                height / 2,
                Math.max(width, height) / 2
            );
            sortedStops.forEach((stop) => {
                const distance = Math.sqrt(
                    Math.pow(stop.position.x - 50, 2) + Math.pow(stop.position.y - 50, 2)
                );
                const position = Math.round(distance) / 100;
                gradientObj.addColorStop(position, stop.color);
            });
        }

        if (gradientObj) {
            ctx.fillStyle = gradientObj;
            ctx.fillRect(0, 0, width, height);
        }

        const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
        const quality = format === "jpeg" ? 0.92 : undefined;

        canvas.toBlob(
            (blob) => {
                if (!blob) return;
                const url = URL.createObjectURL(blob);
                const link = document.createElement("a");
                link.href = url;
                link.download = `gradient-${dimensionType}-${width}x${height}-${Date.now()}.${format}`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(url);
            },
            mimeType,
            quality
        );
    };

    // GIF download function
    const downloadGIF = async (dimensionType = downloadDimension) => {
        alert(
            "GIF export is being processed. This feature will capture animated frames of your gradient."
        );
    };

    // MP4 download function
    const downloadMP4 = async (dimensionType = downloadDimension) => {
        if (typeof window === "undefined") return;

        // Check if animation is enabled - if not, download as PNG instead
        if (!gradient.backgroundAnimation.enabled) {
            // Download as PNG since there's no animation
            downloadRaster("png", dimensionType);
            return;
        }

        setIsGeneratingMP4(true);
        setMp4Progress(0);

        try {
            const dimensions =
                previewFramePresets[dimensionType] ||
                dimensionPresets[dimensionType] ||
                dimensionPresets.mobile;
            const width = dimensions.width;
            const height = dimensions.height;

            setMp4Progress(10);

            // Create a canvas for recording
            const canvas = document.createElement("canvas");
            canvas.width = width;
            canvas.height = height;
            canvas.style.position = "fixed";
            canvas.style.top = "-9999px";
            canvas.style.left = "-9999px";
            document.body.appendChild(canvas);
            const ctx = canvas.getContext("2d");

            // Get canvas stream for MediaRecorder
            const stream = canvas.captureStream(30); // 30 FPS
            const mediaRecorder = new MediaRecorder(stream, {
                mimeType: "video/webm;codecs=vp9",
            });

            const chunks = [];

            mediaRecorder.ondataavailable = (event) => {
                if (event.data.size > 0) {
                    chunks.push(event.data);
                }
            };

            mediaRecorder.onstop = () => {
                const blob = new Blob(chunks, { type: "video/webm" });
                const url = URL.createObjectURL(blob);
                const link = document.createElement("a");
                link.href = url;
                link.download = `gradient-${dimensionType}-${width}x${height}-${Date.now()}.webm`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(url);
                document.body.removeChild(canvas);
                setIsGeneratingMP4(false);
                setMp4Progress(0);
            };

            // Calculate animation parameters
            const animationSpeed = gradient.backgroundAnimation.speed;
            const animationType = gradient.backgroundAnimation.type;
            const animationDirection = gradient.backgroundAnimation.direction;
            const duration = 5; // 5 seconds of video
            const fps = 30;
            const totalFrames = duration * fps;

            const drawGradient = (progress = 0) => {
                // Clear canvas
                ctx.clearRect(0, 0, width, height);

                // Handle empty case - fill with white background
                if (gradient.stops.length === 0) {
                    ctx.fillStyle = "#ffffff";
                    ctx.fillRect(0, 0, width, height);
                    return;
                }

                // Calculate background position based on animation
                let bgX = 0;
                let bgY = 0;
                const bgSize = 200; // 200% for animation

                if (animationType === "slide") {
                    if (animationDirection === "right") {
                        bgX = progress * bgSize;
                    } else if (animationDirection === "left") {
                        bgX = (1 - progress) * bgSize;
                    } else if (animationDirection === "up") {
                        bgY = (1 - progress) * bgSize;
                    } else if (animationDirection === "down") {
                        bgY = progress * bgSize;
                    }
                } else if (animationType === "wave") {
                    bgX = Math.sin(progress * Math.PI * 2) * bgSize;
                    bgY = Math.cos(progress * Math.PI * 2) * bgSize;
                }

                // Save context for background pattern
                ctx.save();

                // Draw gradient
                let gradientObj;
                const sortedStops = [...gradient.stops].sort((a, b) => {
                    const distA = Math.sqrt(
                        a.position.x * a.position.x + a.position.y * a.position.y
                    );
                    const distB = Math.sqrt(
                        b.position.x * b.position.x + b.position.y * b.position.y
                    );
                    return distA - distB;
                });

                if (gradient.type === "linear") {
                    const firstStop = sortedStops[0];
                    const lastStop = sortedStops[sortedStops.length - 1];
                    const angle =
                        Math.atan2(
                            lastStop.position.y - firstStop.position.y,
                            lastStop.position.x - firstStop.position.x
                        ) *
                        (180 / Math.PI);

                    const radians = (angle * Math.PI) / 180;
                    const x1 = width / 2 - (width / 2) * Math.cos(radians);
                    const y1 = height / 2 - (width / 2) * Math.sin(radians);
                    const x2 = width / 2 + (width / 2) * Math.cos(radians);
                    const y2 = height / 2 + (width / 2) * Math.sin(radians);

                    gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
                    sortedStops.forEach((stop) => {
                        const position = Math.round(stop.position.x) / 100;
                        gradientObj.addColorStop(position, stop.color);
                    });
                } else if (gradient.type === "radial") {
                    gradientObj = ctx.createRadialGradient(
                        width / 2,
                        height / 2,
                        0,
                        width / 2,
                        height / 2,
                        Math.max(width, height) / 2
                    );
                    sortedStops.forEach((stop) => {
                        const distance = Math.sqrt(
                            Math.pow(stop.position.x - 50, 2) +
                                Math.pow(stop.position.y - 50, 2)
                        );
                        const position = Math.round(distance) / 100;
                        gradientObj.addColorStop(position, stop.color);
                    });
                } else if (gradient.type === "conic") {
                    gradientObj = ctx.createLinearGradient(
                        width / 2,
                        0,
                        width / 2,
                        height
                    );
                    sortedStops.forEach((stop) => {
                        const position = Math.round(stop.position.x) / 100;
                        gradientObj.addColorStop(position, stop.color);
                    });
                } else if (gradient.type === "mesh") {
                    ctx.globalCompositeOperation = "multiply";
                    sortedStops.forEach((stop) => {
                        const meshGradient = ctx.createRadialGradient(
                            (stop.position.x / 100) * width,
                            (stop.position.y / 100) * height,
                            0,
                            (stop.position.x / 100) * width,
                            (stop.position.y / 100) * height,
                            Math.max(width, height) / 2
                        );
                        meshGradient.addColorStop(0, stop.color);
                        meshGradient.addColorStop(0.5, stop.color);
                        meshGradient.addColorStop(1, "transparent");
                        ctx.fillStyle = meshGradient;
                        ctx.fillRect(0, 0, width, height);
                    });
                    ctx.globalCompositeOperation = "source-over";
                    ctx.restore();
                    return;
                } else if (gradient.type === "grid") {
                    const firstStop = sortedStops[0];
                    const lastStop = sortedStops[sortedStops.length - 1];
                    const angle =
                        Math.atan2(
                            lastStop.position.y - firstStop.position.y,
                            lastStop.position.x - firstStop.position.x
                        ) *
                        (180 / Math.PI);

                    const radians = (angle * Math.PI) / 180;
                    const x1 = width / 2 - (width / 2) * Math.cos(radians);
                    const y1 = height / 2 - (width / 2) * Math.sin(radians);
                    const x2 = width / 2 + (width / 2) * Math.cos(radians);
                    const y2 = height / 2 + (width / 2) * Math.sin(radians);

                    const patternCanvas = document.createElement("canvas");
                    patternCanvas.width = width;
                    patternCanvas.height = height;
                    const patternCtx = patternCanvas.getContext("2d");
                    const patternGradient = patternCtx.createLinearGradient(
                        x1,
                        y1,
                        x2,
                        y2
                    );
                    sortedStops.forEach((stop) => {
                        const position = Math.round(stop.position.x) / 100;
                        patternGradient.addColorStop(position, stop.color);
                    });
                    patternCtx.fillStyle = patternGradient;
                    patternCtx.fillRect(0, 0, width, height);
                    const pattern = ctx.createPattern(patternCanvas, "repeat");
                    ctx.fillStyle = pattern;
                } else if (gradient.type === "sharp") {
                    const firstStop = sortedStops[0];
                    const lastStop = sortedStops[sortedStops.length - 1];
                    const angle =
                        Math.atan2(
                            lastStop.position.y - firstStop.position.y,
                            lastStop.position.x - firstStop.position.x
                        ) *
                        (180 / Math.PI);

                    const radians = (angle * Math.PI) / 180;
                    const x1 = width / 2 - (width / 2) * Math.cos(radians);
                    const y1 = height / 2 - (width / 2) * Math.sin(radians);
                    const x2 = width / 2 + (width / 2) * Math.cos(radians);
                    const y2 = height / 2 + (width / 2) * Math.sin(radians);

                    gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
                    sortedStops.forEach((stop, index) => {
                        const position = Math.round(stop.position.x) / 100;
                        gradientObj.addColorStop(position, stop.color);
                        if (index < sortedStops.length - 1) {
                            const nextStop = sortedStops[index + 1];
                            const midPoint = (stop.position.x + nextStop.position.x) / 200;
                            gradientObj.addColorStop(midPoint, stop.color);
                            gradientObj.addColorStop(midPoint, nextStop.color);
                        }
                    });
                } else if (gradient.type === "soft") {
                    const firstStop = sortedStops[0];
                    const lastStop = sortedStops[sortedStops.length - 1];
                    const angle =
                        Math.atan2(
                            lastStop.position.y - firstStop.position.y,
                            lastStop.position.x - firstStop.position.x
                        ) *
                        (180 / Math.PI);

                    const radians = (angle * Math.PI) / 180;
                    const x1 = width / 2 - (width / 2) * Math.cos(radians);
                    const y1 = height / 2 - (width / 2) * Math.sin(radians);
                    const x2 = width / 2 + (width / 2) * Math.cos(radians);
                    const y2 = height / 2 + (width / 2) * Math.sin(radians);

                    gradientObj = ctx.createLinearGradient(x1, y1, x2, y2);
                    sortedStops.forEach((stop, index) => {
                        if (index === 0) {
                            gradientObj.addColorStop(
                                Math.max(0, (Math.round(stop.position.x) - 10) / 100),
                                stop.color
                            );
                        } else if (index === sortedStops.length - 1) {
                            gradientObj.addColorStop(
                                Math.min(1, (Math.round(stop.position.x) + 10) / 100),
                                stop.color
                            );
                        } else {
                            const prevStop = sortedStops[index - 1];
                            const nextStop = sortedStops[index + 1];
                            const startPos = Math.max(
                                prevStop.position.x / 100,
                                (Math.round(stop.position.x) - 15) / 100
                            );
                            const endPos = Math.min(
                                nextStop.position.x / 100,
                                (Math.round(stop.position.x) + 15) / 100
                            );
                            gradientObj.addColorStop(startPos, stop.color);
                            gradientObj.addColorStop(endPos, stop.color);
                        }
                    });
                } else {
                    gradientObj = ctx.createRadialGradient(
                        width / 2,
                        height / 2,
                        0,
                        width / 2,
                        height / 2,
                        Math.max(width, height) / 2
                    );
                    sortedStops.forEach((stop) => {
                        const distance = Math.sqrt(
                            Math.pow(stop.position.x - 50, 2) +
                                Math.pow(stop.position.y - 50, 2)
                        );
                        const position = Math.round(distance) / 100;
                        gradientObj.addColorStop(position, stop.color);
                    });
                }

                if (gradientObj) {
                    // Apply background size for animation
                    ctx.save();
                    ctx.translate(-bgX, -bgY);
                    ctx.scale(2, 2); // 200% size
                    ctx.fillStyle = gradientObj;
                    ctx.fillRect(bgX / 2, bgY / 2, width, height);
                    ctx.restore();
                }

                // Draw background pattern if enabled
                const pattern = backgroundPatternPresets.find(
                    (p) => p.id === gradient.backgroundPattern.id
                );
                if (pattern && pattern.css) {
                    // For patterns, we'll draw them on top
                    // This is a simplified version - full pattern rendering would be more complex
                    ctx.restore();
                } else {
                    ctx.restore();
                }
            };

            setMp4Progress(20);

            // Start recording
            mediaRecorder.start();

            // Record frames
            for (let frame = 0; frame < totalFrames; frame++) {
                const progress = (frame / totalFrames) * (duration / animationSpeed);
                const normalizedProgress = progress % 1; // Loop animation
                drawGradient(normalizedProgress);

                // Update progress
                const frameProgress = 20 + (frame / totalFrames) * 70;
                setMp4Progress(Math.min(95, frameProgress));

                // Wait for next frame
                await new Promise((resolve) => setTimeout(resolve, 1000 / fps));
            }

            setMp4Progress(95);

            // Stop recording
            mediaRecorder.stop();
        } catch (error) {
            console.error("Error generating MP4:", error);
            setIsGeneratingMP4(false);
            setMp4Progress(0);
            alert(
                "Error generating video. Your browser may not support video recording. Please try downloading as PNG instead."
            );
        }
    };

    // Handle click outside gradient presets dropdown
    const handleGradientPresetsClickOutside = useCallback((event) => {
        if (
            gradientPresetsRef.current &&
            !gradientPresetsRef.current.contains(event.target)
        ) {
            setIsGradientPresetsOpen(false);
        }
    }, []);

    useEffect(() => {
        if (isGradientPresetsOpen) {
            document.addEventListener("mousedown", handleGradientPresetsClickOutside);
            return () =>
                document.removeEventListener(
                    "mousedown",
                    handleGradientPresetsClickOutside
                );
        }
    }, [isGradientPresetsOpen, handleGradientPresetsClickOutside]);

    // Handle click outside background patterns dropdown
    const handleBackgroundPatternsClickOutside = useCallback((event) => {
        if (
            backgroundPatternsRef.current &&
            !backgroundPatternsRef.current.contains(event.target)
        ) {
            setIsBackgroundPatternsOpen(false);
        }
    }, []);

    useEffect(() => {
        if (isBackgroundPatternsOpen) {
            document.addEventListener(
                "mousedown",
                handleBackgroundPatternsClickOutside
            );
            return () =>
                document.removeEventListener(
                    "mousedown",
                    handleBackgroundPatternsClickOutside
                );
        }
    }, [isBackgroundPatternsOpen, handleBackgroundPatternsClickOutside]);

    // Handle click outside download dropdown
    const handleDownloadDropdownClickOutside = useCallback((event) => {
        if (
            downloadDropdownRef.current &&
            !downloadDropdownRef.current.contains(event.target)
        ) {
            setIsDownloadDropdownOpen(false);
        }
    }, []);

    useEffect(() => {
        if (isDownloadDropdownOpen) {
            document.addEventListener(
                "mousedown",
                handleDownloadDropdownClickOutside
            );
            return () =>
                document.removeEventListener(
                    "mousedown",
                    handleDownloadDropdownClickOutside
                );
        }
    }, [isDownloadDropdownOpen, handleDownloadDropdownClickOutside]);

    // Handle click outside panel download dropdown
    const handlePanelDownloadDropdownClickOutside = useCallback((event) => {
        if (
            panelDownloadDropdownRef.current &&
            !panelDownloadDropdownRef.current.contains(event.target)
        ) {
            setIsPanelDownloadDropdownOpen(false);
        }
    }, []);

    useEffect(() => {
        if (isPanelDownloadDropdownOpen) {
            document.addEventListener(
                "mousedown",
                handlePanelDownloadDropdownClickOutside
            );
            return () =>
                document.removeEventListener(
                    "mousedown",
                    handlePanelDownloadDropdownClickOutside
                );
        }
    }, [isPanelDownloadDropdownOpen, handlePanelDownloadDropdownClickOutside]);

    const backgroundAnimation = useMemo(
        () => generateAnimationCSS(),
        [gradient.backgroundAnimation]
    );

    // Calculate modal preview dimensions
    const modalDimensions = useMemo(() => {
        const { width: frameWidth, height: frameHeight } = gradient.dimensions;
        const aspectRatio = frameWidth / frameHeight;

        if (aspectRatio >= 1) {
            return {
                width: "90vw",
                maxWidth: "90vw",
                maxHeight: "90vh",
            };
        } else {
            return {
                height: "90vh",
                maxWidth: "90vw",
                maxHeight: "90vh",
            };
        }
    }, [gradient.dimensions, previewFrameSize]);

    // Find active gradient preset
    const activePreset = useMemo(() => {
        return gradientPresets.find((preset) => {
            // Check if type and angle match
            if (preset.type !== gradient.type) return false;
            if (preset.type === "linear" && preset.angle !== gradient.angle) {
                return false;
            }

            // Check if stops match (compare colors and positions)
            if (preset.stops.length !== gradient.stops.length) return false;

            // Sort stops by position for comparison
            const sortedPresetStops = [...preset.stops].sort((a, b) => {
                const distA = Math.sqrt(
                    a.position.x * a.position.x + a.position.y * a.position.y
                );
                const distB = Math.sqrt(
                    b.position.x * b.position.x + b.position.y * b.position.y
                );
                return distA - distB;
            });

            const sortedGradientStops = [...gradient.stops].sort((a, b) => {
                const distA = Math.sqrt(
                    a.position.x * a.position.x + a.position.y * a.position.y
                );
                const distB = Math.sqrt(
                    b.position.x * b.position.x + b.position.y * b.position.y
                );
                return distA - distB;
            });

            // Compare each stop's color (allow small position differences)
            return sortedPresetStops.every((presetStop, index) => {
                const gradientStop = sortedGradientStops[index];
                return (
                    presetStop.color.toLowerCase() === gradientStop.color.toLowerCase()
                );
            });
        });
    }, [gradient.type, gradient.angle, gradient.stops]);

    // Get current preset index
    const currentPresetIndex = useMemo(() => {
        if (!activePreset) return -1;
        return gradientPresets.findIndex((p) => p.id === activePreset.id);
    }, [activePreset]);

    // Navigate to next preset
    const navigateToNextPreset = useCallback(() => {
        const currentIndex = currentPresetIndex >= 0 ? currentPresetIndex : 0;
        const nextIndex = (currentIndex + 1) % gradientPresets.length;
        const nextPreset = gradientPresets[nextIndex];

        setGradient((prev) => ({
            ...prev,
            type: nextPreset.type,
            angle: nextPreset.angle,
            stops: nextPreset.stops.map((stop) => ({
                ...stop,
            })),
        }));
    }, [currentPresetIndex]);

    // Navigate to previous preset
    const navigateToPreviousPreset = useCallback(() => {
        const currentIndex = currentPresetIndex >= 0 ? currentPresetIndex : 0;
        const prevIndex =
            currentIndex === 0 ? gradientPresets.length - 1 : currentIndex - 1;
        const prevPreset = gradientPresets[prevIndex];

        setGradient((prev) => ({
            ...prev,
            type: prevPreset.type,
            angle: prevPreset.angle,
            stops: prevPreset.stops.map((stop) => ({
                ...stop,
            })),
        }));
    }, [currentPresetIndex]);

    // Keyboard navigation for presets
    useEffect(() => {
        const handleKeyPress = (e) => {
            // Only trigger if not typing in an input/textarea
            if (
                e.target.tagName === "INPUT" ||
                e.target.tagName === "TEXTAREA" ||
                e.target.isContentEditable
            ) {
                return;
            }

            // Arrow Right, Period (.), or Spacebar for next preset
            if (e.key === "ArrowRight" || e.key === "." || e.key === " ") {
                e.preventDefault();
                navigateToNextPreset();
            }
            // Arrow Left or Comma (,) for previous preset
            else if (e.key === "ArrowLeft" || e.key === ",") {
                e.preventDefault();
                navigateToPreviousPreset();
            }
        };

        window.addEventListener("keydown", handleKeyPress);
        return () => window.removeEventListener("keydown", handleKeyPress);
    }, [navigateToNextPreset, navigateToPreviousPreset]);

    return (
        <div className="min-h-screen bg-stone-100 p-6 canvas-dots-bg">
            <style jsx>{`
                .slider::-webkit-slider-thumb {
                    appearance: none;
                    height: 16px;
                    width: 16px;
                    border-radius: 50%;
                    background: #71717a;
                    cursor: pointer;
                    border: 2px solid #ffffff;
                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                }
                .slider::-moz-range-thumb {
                    height: 16px;
                    width: 16px;
                    border-radius: 50%;
                    background: #71717a;
                    cursor: pointer;
                    border: 2px solid #ffffff;
                    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
                }

                @keyframes slideRight {
                    0% {
                        background-position: 0% 0%;
                    }
                    100% {
                        background-position: 100% 0%;
                    }
                }
                @keyframes slideLeft {
                    0% {
                        background-position: 100% 0%;
                    }
                    100% {
                        background-position: 0% 0%;
                    }
                }
                @keyframes slideUp {
                    0% {
                        background-position: 0% 100%;
                    }
                    100% {
                        background-position: 0% 0%;
                    }
                }
                @keyframes slideDown {
                    0% {
                        background-position: 0% 0%;
                    }
                    100% {
                        background-position: 0% 100%;
                    }
                }
                @keyframes wave {
                    0% {
                        background-position: 0% 0%;
                    }
                    25% {
                        background-position: 100% 0%;
                    }
                    50% {
                        background-position: 100% 100%;
                    }
                    75% {
                        background-position: 0% 100%;
                    }
                    100% {
                        background-position: 0% 0%;
                    }
                }
                @keyframes meshGradient {
                    0% {
                        background-position: 0% 0%;
                    }
                    50% {
                        background-position: 100% 100%;
                    }
                    100% {
                        background-position: 0% 0%;
                    }
                }

                .canvas-dots-bg {
                    background-image: radial-gradient(
                        circle,
                        rgba(0, 0, 0, 0.15) 1px,
                        transparent 1px
                    );
                    background-size: 20px 20px;
                    background-position: 0 0;
                }
                .noise-overlay {
                    position: absolute;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    pointer-events: none;
                    opacity: var(--noise-opacity);
                    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
                    background-size: 200px 200px;
                    mix-blend-mode: overlay;
                }
            `}</style>
            <div className="max-w-7xl mx-auto">
                {/* Preview Header */}
                <div className="flex items-center justify-end gap-2 mb-4 flex-wrap">
                    {/* Frame Size Selector */}
                    <div className="flex ">
                        <Dropdown
                            value={previewFrameSize}
                            onChange={(value) => {
                                setPreviewFrameSize(value);
                                const frame = previewFramePresets[value];
                                if (frame) {
                                    setGradient((prev) => ({
                                        ...prev,
                                        dimensions: {
                                            width: frame.width,
                                            height: frame.height,
                                        },
                                    }));
                                }
                            }}
                            options={Object.entries(previewFramePresets).map(
                                ([key, preset]) => ({
                                    value: key,
                                    label: `${preset.icon} ${preset.label} (${preset.width}×${preset.height})`,
                                })
                            )}
                            placeholder="Select frame size"
                            className="min-w-[180px]"
                        />
                    </div>
                    {/* <button
                        onClick={() => setIsModalOpen(true)}
                        className="flex items-center gap-2 px-3 py-1.5 text-sm bg-zinc-100 hover:bg-zinc-200 rounded-xl transition-colors h-8"
                    >
                        <Maximize2 className="w-4 h-4" />
                        Preview
                    </button> */}
                </div>
                <div className="grid grid-cols-1 lg:grid-cols-3 w-full">
                    {/* Preview Section */}
                    <div className="space-y-6 col-span-2">
                        <div className="bg-white rounded-xl shadow-lg">
                            {/* Interactive Gradient Preview */}
                            <div
                                className="flex justify-center items-center w-full"
                                style={{
                                    minHeight:
                                        gradient.dimensions.height > gradient.dimensions.width
                                            ? "600px"
                                            : "400px",
                                }}
                            >
                                <div
                                    ref={previewRef}
                                    className="relative rounded-xl overflow-hidden cursor-crosshair "
                                    style={{
                                        aspectRatio: `${gradient.dimensions.width} / ${gradient.dimensions.height}`,
                                        width: "100%",
                                        maxWidth:
                                            gradient.dimensions.width > gradient.dimensions.height
                                                ? `${Math.min(gradient.dimensions.width * 0.6, 1200)}px`
                                                : "100%",
                                        maxHeight:
                                            gradient.dimensions.height > gradient.dimensions.width
                                                ? "90vh"
                                                : `${Math.min(gradient.dimensions.height * 0.6, 600)}px`,
                                        ...getBackgroundPatternStyle(),
                                        ...(isPlaying &&
                                            gradient.backgroundAnimation.enabled && {
                                                backgroundSize: (() => {
                                                    const patternStyle = getBackgroundPatternStyle();
                                                    if (patternStyle.backgroundSize) {
                                                        return patternStyle.backgroundSize.replace(
                                                            "auto",
                                                            "200% 200%"
                                                        );
                                                    }
                                                    return "200% 200%";
                                                })(),
                                            }),
                                        ...(isPlaying &&
                                            backgroundAnimation && {
                                                animation: backgroundAnimation,
                                            }),
                                    }}
                                >
                                    {/* Noise Overlay */}
                                    {gradient.noise.enabled && (
                                        <div
                                            className="noise-overlay"
                                            style={{
                                                "--noise-opacity": gradient.noise.intensity,
                                            }}
                                        />
                                    )}
                                    {/* Color Stop Handles */}
                                    {gradient.stops.map((stop) => (
                                        <div
                                            key={stop.id}
                                            className={`absolute w-6 h-6 flex items-center justify-center cursor-pointer group transition-transform ${
                                                selectedStop === stop.id
                                                    ? "scale-125"
                                                    : "hover:scale-110"
                                            }`}
                                            style={{
                                                left: `calc(${stop.position.x}% - 12px)`,
                                                top: `calc(${stop.position.y}% - 12px)`,
                                            }}
                                            onMouseDown={(e) => handleMouseDown(e, stop.id)}
                                            onKeyDown={(e) => handleKeyDown(e, stop.id)}
                                            tabIndex={0}
                                            role="button"
                                            aria-label={`Color stop at ${Math.round(
                                                stop.position.x
                                            )}%, ${Math.round(stop.position.y)}%`}
                                        >
                                            <div
                                                className="w-6 h-6 rounded-full border-2 border-white shadow-lg hover:shadow-xl transition-shadow"
                                                style={{ backgroundColor: stop.color }}
                                            />
                                            <div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 text-xs text-white bg-black bg-opacity-75 px-2 py-1 rounded whitespace-nowrap">
                                                {Math.round(stop.position.x)}%,{" "}
                                                {Math.round(stop.position.y)}%
                                            </div>
                                        </div>
                                    ))}
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* Control Panel */}
                    <div className="space-y-6 bg-white cols-span-1 ml-auto max-h-[80vh] max-w-xs overflow-y-auto hidescrollbar shadow-xl border border-zinc-100 rounded-xl">
                        {/* Color Stops */}
                        {/* Gradient Type & Controls */}
                        <div className="bg-white rounded-xl shadow-lg p-4">
                            <div className="flex items-center justify-between">
                                <h3 className="text-sm">Colors</h3>
                                <button
                                    onClick={addColorStop}
                                    className="flex items-center px-2 py-1 rounded-xl hover:bg-zinc-100 transition-colors text-xs h-8"
                                >
                                    <Plus className="w-3 h-3" />
                                </button>
                            </div>
                            <div className="mb-4">
                                {gradient.stops.map((stop) => (
                                    <div
                                        key={stop.id}
                                        className="flex items-center gap-2 p-1 hover:bg-gray-50 rounded-xl"
                                    >
                                        <input
                                            type="color"
                                            value={stop.color}
                                            onChange={(e) =>
                                                updateColorStop(stop.id, "color", e.target.value)
                                            }
                                            className="w-6 h-6 rounded border border-gray-300 cursor-pointer"
                                            aria-label={`Color for stop at ${Math.round(
                                                stop.position.x
                                            )}%, ${Math.round(stop.position.y)}%`}
                                        />
                                        <div className="flex-1 flex justify-between">
                                            <div className="text-xs text-gray-600 mb-0.5">
                                                Position: {Math.round(stop.position.x)}%,{" "}
                                                {Math.round(stop.position.y)}%
                                            </div>
                                            <div className="text-[10px] text-gray-500 font-mono">
                                                {stop.color}
                                            </div>
                                        </div>
                                        <button
                                            onClick={() => removeColorStop(stop.id)}
                                            className="p-1.5 text-red-500 hover:bg-red-50 rounded"
                                            aria-label={`Remove color stop`}
                                        >
                                            <Trash2 className="w-3 h-3" />
                                        </button>
                                    </div>
                                ))}
                            </div>
                            <div className="space-y-3">
                                <div>
                                    <label className="flex justify-between items-center text-xs font-medium text-gray-700 mb-1.5">
                                        Gradient Templates
                                        <div className="relative group flex items-center gap-1">
                                            <span className="w-fit border border-gray-200 group-hover:opacity-100 opacity-0 transition-all duration-75 ease-in invisible group-hover:visible bg-white z-50 p-1 rounded-xl text-[10px] text-gray-500">
                                                Use {"<, >"} arrow keys to shuffle
                                            </span>
                                            <InfoIcon className="w-3 h-3 text-gray-500 cursor-pointer" />
                                        </div>
                                    </label>
                                    <div ref={gradientPresetsRef} className="relative">
                                        <button
                                            type="button"
                                            onClick={() =>
                                                setIsGradientPresetsOpen(!isGradientPresetsOpen)
                                            }
                                            className="w-full h-8 px-2 text-xs border border-zinc-200 bg-white rounded-xl focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:border-zinc-400 transition-colors flex items-center justify-between"
                                        >
                                            <span className="text-left">
                                                {activePreset ? activePreset.name : "Choose Gradient"}
                                            </span>
                                            <ChevronDown
                                                className={`w-3 h-3 transition-transform ${
                                                    isGradientPresetsOpen ? "rotate-180" : ""
                                                }`}
                                            />
                                        </button>

                                        <AnimatePresence>
                                            {isGradientPresetsOpen && (
                                                <motion.div
                                                    initial={{ opacity: 0, y: -10 }}
                                                    animate={{ opacity: 1, y: 0 }}
                                                    exit={{ opacity: 0, y: -10 }}
                                                    transition={{ duration: 0.15 }}
                                                    className="absolute z-50 w-full mt-1 bg-white border border-zinc-200 rounded-xl shadow-lg overflow-hidden"
                                                    style={{ maxHeight: "400px", overflowY: "auto" }}
                                                >
                                                    <div className="p-3">
                                                        <div className="grid grid-cols-1 gap-2">
                                                            {gradientPresets.map((preset) => (
                                                                <button
                                                                    key={preset.id}
                                                                    type="button"
                                                                    onClick={() => {
                                                                        setGradient((prev) => ({
                                                                            ...prev,
                                                                            type: preset.type,
                                                                            angle: preset.angle,
                                                                            stops: preset.stops.map((stop) => ({
                                                                                ...stop,
                                                                            })),
                                                                        }));
                                                                        setIsGradientPresetsOpen(false);
                                                                    }}
                                                                    className="h-10 rounded-xl overflow-hidden hover:border-zinc-400 transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400"
                                                                    style={{
                                                                        background:
                                                                            generatePresetGradientCSS(preset),
                                                                    }}
                                                                    title={preset.name}
                                                                >
                                                                    <div className="w-full h-full flex items-center justify-center p-1">
                                                                        <span className="text-xs text-white px-1.5 py-0.5 rounded text-left truncate w-full">
                                                                            {preset.name}
                                                                        </span>
                                                                    </div>
                                                                </button>
                                                            ))}
                                                        </div>
                                                    </div>
                                                </motion.div>
                                            )}
                                        </AnimatePresence>
                                    </div>
                                </div>
                                {/* <div>
                                    <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                        Background Patterns
                                    </label>
                                    <div ref={backgroundPatternsRef} className="relative">
                                        <button
                                            type="button"
                                            onClick={() =>
                                                setIsBackgroundPatternsOpen(!isBackgroundPatternsOpen)
                                            }
                                            className="w-full h-8 px-2 text-xs border border-zinc-200 bg-white rounded-xl focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:border-zinc-400 transition-colors flex items-center justify-between"
                                        >
                                            <span className="text-left">
                                                {gradient.backgroundPattern.name || "Choose Pattern"}
                                            </span>
                                            <ChevronDown
                                                className={`w-3 h-3 transition-transform ${
                                                    isBackgroundPatternsOpen ? "rotate-180" : ""
                                                }`}
                                            />
                                        </button>

                                        <AnimatePresence>
                                            {isBackgroundPatternsOpen && (
                                                <motion.div
                                                    initial={{ opacity: 0, y: -10 }}
                                                    animate={{ opacity: 1, y: 0 }}
                                                    exit={{ opacity: 0, y: -10 }}
                                                    transition={{ duration: 0.15 }}
                                                    className="absolute z-50 w-full mt-1 bg-white border border-zinc-200 rounded-xl shadow-lg overflow-hidden"
                                                    style={{ maxHeight: "400px", overflowY: "auto" }}
                                                >
                                                    <div className="p-3">
                                                        <div className="grid grid-cols-1 gap-2">
                                                            {backgroundPatternPresets.map((pattern) => (
                                                                <button
                                                                    key={pattern.id}
                                                                    type="button"
                                                                    onClick={() => {
                                                                        setGradient((prev) => ({
                                                                            ...prev,
                                                                            backgroundPattern: {
                                                                                id: pattern.id,
                                                                                name: pattern.name,
                                                                            },
                                                                        }));
                                                                        setIsBackgroundPatternsOpen(false);
                                                                    }}
                                                                    className={`h-10 rounded-xl overflow-hidden hover:border-zinc-400 transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400 border ${
                                                                        gradient.backgroundPattern.id === pattern.id
                                                                            ? "border-zinc-400"
                                                                            : "border-transparent"
                                                                    }`}
                                                                    style={{
                                                                        background:
                                                                            pattern.preview || "transparent",
                                                                        backgroundSize:
                                                                            pattern.backgroundSize || "auto",
                                                                        backgroundPosition:
                                                                            pattern.backgroundPosition || "0 0",
                                                                    }}
                                                                    title={pattern.name}
                                                                >
                                                                    <div className="w-full h-full flex items-center justify-center p-1 bg-white bg-opacity-50">
                                                                        <span className="text-xs text-gray-900 px-1.5 py-0.5 rounded text-left truncate w-full font-medium">
                                                                            {pattern.name}
                                                                        </span>
                                                                    </div>
                                                                </button>
                                                            ))}
                                                        </div>
                                                    </div>
                                                </motion.div>
                                            )}
                                        </AnimatePresence>
                                    </div>
                                </div> */}
                                <div>
                                    <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                        Gradient Type
                                    </label>
                                    <Dropdown
                                        value={gradient.type}
                                        onChange={(value) =>
                                            setGradient((prev) => ({ ...prev, type: value }))
                                        }
                                        options={[
                                            { value: "linear", label: "Linear" },
                                            { value: "radial", label: "Radial" },
                                            { value: "conic", label: "Conic" },
                                            { value: "mesh", label: "Mesh" },
                                            { value: "grid", label: "Grid" },
                                            { value: "sharp", label: "Sharp" },
                                            { value: "soft", label: "Soft" },
                                        ]}
                                        placeholder="Select gradient type"
                                    />
                                </div>

                                {/* Noise Controls */}
                                <div className="border-t pt-3">
                                    <h3 className="text-base font-semibold mb-3">Noise</h3>

                                    <div className="space-y-3">
                                        <div>
                                            <label className="flex items-center space-x-2 text-xs font-medium cursor-pointer group mb-3">
                                                <div className="relative">
                                                    <input
                                                        type="checkbox"
                                                        checked={gradient.noise.enabled}
                                                        onChange={(e) =>
                                                            setGradient((prev) => ({
                                                                ...prev,
                                                                noise: {
                                                                    ...prev.noise,
                                                                    enabled: e.target.checked,
                                                                },
                                                            }))
                                                        }
                                                        className="sr-only"
                                                    />
                                                    <div
                                                        className={`w-4 h-4 rounded border-2 transition-all duration-200 flex items-center justify-center ${
                                                            gradient.noise.enabled
                                                                ? "bg-zinc-600 border-zinc-600"
                                                                : "bg-white border-zinc-300 group-hover:border-zinc-400"
                                                        }`}
                                                    >
                                                        {gradient.noise.enabled && (
                                                            <svg
                                                                className="w-2.5 h-2.5 text-white"
                                                                fill="none"
                                                                strokeLinecap="round"
                                                                strokeLinejoin="round"
                                                                strokeWidth="2.5"
                                                                viewBox="0 0 24 24"
                                                                stroke="currentColor"
                                                            >
                                                                <path d="M5 13l4 4L19 7"></path>
                                                            </svg>
                                                        )}
                                                    </div>
                                                </div>
                                                <span className="select-none">Enable Noise</span>
                                            </label>
                                        </div>

                                        {gradient.noise.enabled && (
                                            <div>
                                                <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                                    Intensity:{" "}
                                                    {Math.round(gradient.noise.intensity * 100)}%
                                                </label>
                                                <input
                                                    type="range"
                                                    min="0"
                                                    max="1"
                                                    step="0.01"
                                                    value={gradient.noise.intensity}
                                                    onChange={(e) =>
                                                        setGradient((prev) => ({
                                                            ...prev,
                                                            noise: {
                                                                ...prev.noise,
                                                                intensity: parseFloat(e.target.value),
                                                            },
                                                        }))
                                                    }
                                                    className="w-full h-1.5 bg-zinc-200 rounded-xl appearance-none cursor-pointer slider"
                                                />
                                            </div>
                                        )}
                                    </div>
                                </div>

                                {/* Background Animation Controls */}
                                <div className="border-t pt-3">
                                    <h3 className="text-base font-semibold mb-3">
                                        Background Animation
                                    </h3>

                                    <div className="space-y-3">
                                        <div>
                                            <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                                Animation Type
                                            </label>
                                            <Dropdown
                                                value={gradient.backgroundAnimation.type}
                                                onChange={(value) =>
                                                    setGradient((prev) => ({
                                                        ...prev,
                                                        backgroundAnimation: {
                                                            ...prev.backgroundAnimation,
                                                            type: value,
                                                        },
                                                    }))
                                                }
                                                options={[
                                                    { value: "slide", label: "Slide" },
                                                    { value: "wave", label: "Wave" },
                                                ]}
                                                placeholder="Select animation type"
                                            />
                                        </div>

                                        <div>
                                            <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                                Direction
                                            </label>
                                            <Dropdown
                                                value={gradient.backgroundAnimation.direction}
                                                onChange={(value) =>
                                                    setGradient((prev) => ({
                                                        ...prev,
                                                        backgroundAnimation: {
                                                            ...prev.backgroundAnimation,
                                                            direction: value,
                                                        },
                                                    }))
                                                }
                                                options={[
                                                    { value: "right", label: "Right" },
                                                    { value: "left", label: "Left" },
                                                    { value: "up", label: "Up" },
                                                    { value: "down", label: "Down" },
                                                ]}
                                                placeholder="Select direction"
                                            />
                                        </div>

                                        <div>
                                            <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                                Speed: {gradient.backgroundAnimation.speed}s
                                            </label>
                                            <input
                                                type="range"
                                                min="1"
                                                max="20"
                                                step="1"
                                                value={gradient.backgroundAnimation.speed}
                                                onChange={(e) =>
                                                    setGradient((prev) => ({
                                                        ...prev,
                                                        backgroundAnimation: {
                                                            ...prev.backgroundAnimation,
                                                            speed: parseInt(e.target.value),
                                                        },
                                                    }))
                                                }
                                                className="w-full h-1.5 bg-zinc-200 rounded-xl appearance-none cursor-pointer slider"
                                            />
                                        </div>

                                        <div>
                                            <label className="block text-xs font-medium text-gray-700 mb-1.5">
                                                Easing
                                            </label>
                                            <Dropdown
                                                value={gradient.backgroundAnimation.easing}
                                                onChange={(value) =>
                                                    setGradient((prev) => ({
                                                        ...prev,
                                                        backgroundAnimation: {
                                                            ...prev.backgroundAnimation,
                                                            easing: value,
                                                        },
                                                    }))
                                                }
                                                options={[
                                                    { value: "linear", label: "Linear" },
                                                    { value: "ease", label: "Ease" },
                                                    { value: "ease-in", label: "Ease In" },
                                                    { value: "ease-out", label: "Ease Out" },
                                                    { value: "ease-in-out", label: "Ease In Out" },
                                                ]}
                                                placeholder="Select easing"
                                            />
                                        </div>

                                        <div>
                                            <label className="flex items-center space-x-2 text-xs font-medium cursor-pointer group">
                                                <div className="relative">
                                                    <input
                                                        type="checkbox"
                                                        checked={gradient.backgroundAnimation.enabled}
                                                        onChange={(e) =>
                                                            setGradient((prev) => ({
                                                                ...prev,
                                                                backgroundAnimation: {
                                                                    ...prev.backgroundAnimation,
                                                                    enabled: e.target.checked,
                                                                },
                                                            }))
                                                        }
                                                        className="sr-only"
                                                    />
                                                    <div
                                                        className={`w-4 h-4 rounded border-2 transition-all duration-200 flex items-center justify-center ${
                                                            gradient.backgroundAnimation.enabled
                                                                ? "bg-zinc-600 border-zinc-600"
                                                                : "bg-white border-zinc-300 group-hover:border-zinc-400"
                                                        }`}
                                                    >
                                                        {gradient.backgroundAnimation.enabled && (
                                                            <svg
                                                                className="w-2.5 h-2.5 text-white"
                                                                fill="none"
                                                                strokeLinecap="round"
                                                                strokeLinejoin="round"
                                                                strokeWidth="2.5"
                                                                viewBox="0 0 24 24"
                                                                stroke="currentColor"
                                                            >
                                                                <path d="M5 13l4 4L19 7"></path>
                                                            </svg>
                                                        )}
                                                    </div>
                                                </div>
                                                <span className="select-none">Enable Animation</span>
                                            </label>
                                        </div>

                                        <div>
                                            <label className="flex items-center space-x-2 text-xs font-medium cursor-pointer group">
                                                <div className="relative">
                                                    <input
                                                        type="checkbox"
                                                        checked={
                                                            gradient.backgroundAnimation.repeat || false
                                                        }
                                                        onChange={(e) =>
                                                            setGradient((prev) => ({
                                                                ...prev,
                                                                backgroundAnimation: {
                                                                    ...prev.backgroundAnimation,
                                                                    repeat: e.target.checked,
                                                                },
                                                            }))
                                                        }
                                                        className="sr-only"
                                                    />
                                                    <div
                                                        className={`w-4 h-4 rounded border-2 transition-all duration-200 flex items-center justify-center ${
                                                            gradient.backgroundAnimation.repeat
                                                                ? "bg-zinc-600 border-zinc-600"
                                                                : "bg-white border-zinc-300 group-hover:border-zinc-400"
                                                        }`}
                                                    >
                                                        {gradient.backgroundAnimation.repeat && (
                                                            <svg
                                                                className="w-2.5 h-2.5 text-white"
                                                                fill="none"
                                                                strokeLinecap="round"
                                                                strokeLinejoin="round"
                                                                strokeWidth="2.5"
                                                                viewBox="0 0 24 24"
                                                                stroke="currentColor"
                                                            >
                                                                <path d="M5 13l4 4L19 7"></path>
                                                            </svg>
                                                        )}
                                                    </div>
                                                </div>
                                                <span className="select-none">
                                                    Repeat Animation (infinite)
                                                </span>
                                            </label>
                                        </div>

                                        {/* Background CSS Output */}
                                        <div className="border-t pt-3">
                                            <div className="flex items-center justify-between mb-1.5">
                                                <h4 className="text-xs font-medium">Background CSS</h4>
                                                <button
                                                    onClick={() => {
                                                        const patternCSS = generateBackgroundPatternCSS();
                                                        const css = `background: ${generateGradientCSS()};${
                                                            patternCSS ? `\n${patternCSS}` : ""
                                                        }${
                                                            backgroundAnimation
                                                                ? `\nbackground-size: 200% 200%;\nanimation: ${backgroundAnimation};`
                                                                : ""
                                                        }`;
                                                        copyToClipboard(css, "background-css");
                                                    }}
                                                    className="flex items-center gap-1 px-1.5 py-0.5 text-[10px] bg-zinc-100 hover:bg-zinc-200 rounded transition-colors"
                                                >
                                                    <Copy className="w-2.5 h-2.5" />
                                                    {copied === "background-css" ? "Copied!" : "Copy CSS"}
                                                </button>
                                            </div>
                                            <pre className="bg-gray-900 text-zinc-400 p-2 rounded text-[10px] overflow-x-auto max-h-28 overflow-y-auto mb-3">
                                                <code>{`background: ${generateGradientCSS()};${
                                                    generateBackgroundPatternCSS()
                                                        ? `\n${generateBackgroundPatternCSS()}`
                                                        : ""
                                                }${
                                                    backgroundAnimation
                                                        ? `\nbackground-size: 200% 200%;\nanimation: ${backgroundAnimation};`
                                                        : ""
                                                }`}</code>
                                            </pre>
                                            {/* Download Button */}
                                            <div ref={panelDownloadDropdownRef} className="relative">
                                                <button
                                                    onClick={() =>
                                                        setIsPanelDownloadDropdownOpen(
                                                            !isPanelDownloadDropdownOpen
                                                        )
                                                    }
                                                    className="w-full flex items-center justify-center gap-2 px-3 py-2 text-xs bg-zinc-800 hover:bg-black hover:shadow-zinc-200 hover:shadow-2xl text-white rounded-xl transition-all duration-75 ease-in hover:scale-105"
                                                >
                                                    <Download className="w-3 h-3" />
                                                    Download
                                                </button>
                                                <AnimatePresence>
                                                    {isPanelDownloadDropdownOpen && (
                                                        <motion.div
                                                            initial={{ opacity: 0, y: -10 }}
                                                            animate={{ opacity: 1, y: 0 }}
                                                            exit={{ opacity: 0, y: -10 }}
                                                            transition={{ duration: 0.15 }}
                                                            className="absolute bottom-full left-0 mb-2 w-full bg-white border border-zinc-200 rounded-xl shadow-lg overflow-hidden z-50"
                                                        >
                                                            <div className="flex flex-col">
                                                                <button
                                                                    type="button"
                                                                    onClick={() => {
                                                                        downloadSVG(previewFrameSize);
                                                                        setIsPanelDownloadDropdownOpen(false);
                                                                    }}
                                                                    className="w-full px-3 py-2 text-xs text-left hover:bg-zinc-50 transition-colors flex items-center gap-2"
                                                                >
                                                                    <Download className="w-3 h-3" />
                                                                    <span>
                                                                        SVG -{" "}
                                                                        {previewFramePresets[previewFrameSize]
                                                                            ?.label || "Current Frame"}
                                                                    </span>
                                                                </button>
                                                                <button
                                                                    type="button"
                                                                    onClick={() => {
                                                                        downloadRaster("png", previewFrameSize);
                                                                        setIsPanelDownloadDropdownOpen(false);
                                                                    }}
                                                                    className="w-full px-3 py-2 text-xs text-left hover:bg-zinc-50 transition-colors flex items-center gap-2 border-t border-zinc-100"
                                                                >
                                                                    <Download className="w-3 h-3" />
                                                                    <span>
                                                                        PNG -{" "}
                                                                        {previewFramePresets[previewFrameSize]
                                                                            ?.label || "Current Frame"}
                                                                    </span>
                                                                </button>
                                                                <button
                                                                    type="button"
                                                                    onClick={() => {
                                                                        downloadRaster("jpeg", previewFrameSize);
                                                                        setIsPanelDownloadDropdownOpen(false);
                                                                    }}
                                                                    className="w-full px-3 py-2 text-xs text-left hover:bg-zinc-50 transition-colors flex items-center gap-2 border-t border-zinc-100"
                                                                >
                                                                    <Download className="w-3 h-3" />
                                                                    <span>
                                                                        JPEG -{" "}
                                                                        {previewFramePresets[previewFrameSize]
                                                                            ?.label || "Current Frame"}
                                                                    </span>
                                                                </button>
                                                                {gradient.backgroundAnimation.enabled && (
                                                                    <button
                                                                        type="button"
                                                                        onClick={() => {
                                                                            downloadGIF(previewFrameSize);
                                                                            setIsPanelDownloadDropdownOpen(false);
                                                                        }}
                                                                        className="w-full px-3 py-2 text-xs text-left hover:bg-zinc-50 transition-colors flex items-center gap-2 border-t border-zinc-100"
                                                                    >
                                                                        <Download className="w-3 h-3" />
                                                                        <span>
                                                                            GIF -{" "}
                                                                            {previewFramePresets[previewFrameSize]
                                                                                ?.label || "Current Frame"}
                                                                        </span>
                                                                    </button>
                                                                )}
                                                                <button
                                                                    type="button"
                                                                    onClick={() => {
                                                                        downloadMP4(previewFrameSize);
                                                                        setIsPanelDownloadDropdownOpen(false);
                                                                    }}
                                                                    className="w-full px-3 py-2 text-xs text-left hover:bg-zinc-50 transition-colors flex items-center gap-2 border-t border-zinc-100"
                                                                >
                                                                    <Download className="w-3 h-3" />
                                                                    <span>
                                                                        MP4 -{" "}
                                                                        {previewFramePresets[previewFrameSize]
                                                                            ?.label || "Current Frame"}
                                                                    </span>
                                                                </button>
                                                            </div>
                                                        </motion.div>
                                                    )}
                                                </AnimatePresence>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            {/* Modal */}
            <AnimatePresence>
                {isModalOpen && (
                    <motion.div
                        initial={{ opacity: 0 }}
                        animate={{ opacity: 1 }}
                        exit={{ opacity: 0 }}
                        className="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50"
                        onClick={() => setIsModalOpen(false)}
                    >
                        <motion.div
                            initial={{ scale: 0.8, opacity: 0 }}
                            animate={{ scale: 1, opacity: 1 }}
                            exit={{ scale: 0.8, opacity: 0 }}
                            className="relative flex items-center justify-center w-full h-full"
                            onClick={(e) => e.stopPropagation()}
                        >
                            {/* Close Button, Download, and Frame Size Selector */}
                            <div className="absolute top-4 right-4 flex items-center gap-2 z-50">
                                {/* Frame Size Selector */}
                                <div className="flex items-center">
                                    <Dropdown
                                        value={previewFrameSize}
                                        onChange={(value) => {
                                            setPreviewFrameSize(value);
                                            const frame = previewFramePresets[value];
                                            if (frame) {
                                                setGradient((prev) => ({
                                                    ...prev,
                                                    dimensions: {
                                                        width: frame.width,
                                                        height: frame.height,
                                                    },
                                                }));
                                            }
                                        }}
                                        options={Object.entries(previewFramePresets).map(
                                            ([key, preset]) => ({
                                                value: key,
                                                label: `${preset.icon} ${preset.label} (${preset.width}×${preset.height})`,
                                            })
                                        )}
                                        placeholder="Select frame size"
                                        className="min-w-[180px]"
                                    />
                                </div>
                                {/* Download Button with Dropdown */}
                                <div ref={downloadDropdownRef} className="relative">
                                    <button
                                        onClick={() =>
                                            setIsDownloadDropdownOpen(!isDownloadDropdownOpen)
                                        }
                                        className="flex items-center gap-2 px-3 py-1.5 text-sm bg-white hover:bg-gray-100 rounded-xl transition-colors border border-gray-200 shadow-lg h-10"
                                    >
                                        <Download className="w-4 h-4" />
                                        Download
                                    </button>
                                    <AnimatePresence>
                                        {isDownloadDropdownOpen && (
                                            <motion.div
                                                initial={{ opacity: 0, y: -10 }}
                                                animate={{ opacity: 1, y: 0 }}
                                                exit={{ opacity: 0, y: -10 }}
                                                transition={{ duration: 0.15 }}
                                                className="absolute right-0 mt-2 bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden z-50 min-w-[200px]"
                                            >
                                                <div className="flex flex-col">
                                                    <button
                                                        type="button"
                                                        onClick={() => {
                                                            downloadSVG(previewFrameSize);
                                                            setIsDownloadDropdownOpen(false);
                                                        }}
                                                        className="w-full px-4 py-3 text-sm text-left hover:bg-gray-50 transition-colors flex items-center gap-2"
                                                    >
                                                        <Download className="w-4 h-4" />
                                                        <span>
                                                            Download as SVG -{" "}
                                                            {previewFramePresets[previewFrameSize]?.label ||
                                                                "Current Frame"}
                                                        </span>
                                                    </button>
                                                    <button
                                                        type="button"
                                                        onClick={() => {
                                                            downloadRaster("png", previewFrameSize);
                                                            setIsDownloadDropdownOpen(false);
                                                        }}
                                                        className="w-full px-4 py-3 text-sm text-left hover:bg-gray-50 transition-colors flex items-center gap-2 border-t border-gray-100"
                                                    >
                                                        <Download className="w-4 h-4" />
                                                        <span>
                                                            Download as PNG -{" "}
                                                            {previewFramePresets[previewFrameSize]?.label ||
                                                                "Current Frame"}
                                                        </span>
                                                    </button>
                                                    <button
                                                        type="button"
                                                        onClick={() => {
                                                            downloadRaster("jpeg", previewFrameSize);
                                                            setIsDownloadDropdownOpen(false);
                                                        }}
                                                        className="w-full px-4 py-3 text-sm text-left hover:bg-gray-50 transition-colors flex items-center gap-2 border-t border-gray-100"
                                                    >
                                                        <Download className="w-4 h-4" />
                                                        <span>
                                                            Download as JPEG -{" "}
                                                            {previewFramePresets[previewFrameSize]?.label ||
                                                                "Current Frame"}
                                                        </span>
                                                    </button>
                                                    {/* GIF Download - Only show if background animation is enabled */}
                                                    {gradient.backgroundAnimation.enabled && (
                                                        <button
                                                            type="button"
                                                            onClick={() => {
                                                                downloadGIF(previewFrameSize);
                                                                setIsDownloadDropdownOpen(false);
                                                            }}
                                                            className="w-full px-4 py-3 text-sm text-left hover:bg-gray-50 transition-colors flex items-center gap-2 border-t border-gray-100"
                                                        >
                                                            <Download className="w-4 h-4" />
                                                            <span>
                                                                Download as GIF -{" "}
                                                                {previewFramePresets[previewFrameSize]?.label ||
                                                                    "Current Frame"}
                                                            </span>
                                                        </button>
                                                    )}
                                                    {/* MP4 Download */}
                                                    <button
                                                        type="button"
                                                        onClick={() => {
                                                            downloadMP4(previewFrameSize);
                                                            setIsDownloadDropdownOpen(false);
                                                        }}
                                                        className="w-full px-4 py-3 text-sm text-left hover:bg-gray-50 transition-colors flex items-center gap-2 border-t border-gray-100"
                                                    >
                                                        <Download className="w-4 h-4" />
                                                        <span>
                                                            Download as MP4 -{" "}
                                                            {previewFramePresets[previewFrameSize]?.label ||
                                                                "Current Frame"}
                                                        </span>
                                                    </button>
                                                </div>
                                            </motion.div>
                                        )}
                                    </AnimatePresence>
                                </div>
                                {/* Close Button */}
                                <button
                                    onClick={() => setIsModalOpen(false)}
                                    className="w-10 h-10 bg-white hover:bg-gray-100 rounded-full flex items-center justify-center shadow-lg transition-colors border border-gray-200"
                                >
                                    <X className="w-5 h-5 text-gray-900" />
                                </button>
                            </div>

                            {/* Modal Preview Container */}
                            <div
                                ref={modalPreviewRef}
                                className="relative rounded-xl overflow-hidden shadow-2xl"
                                style={{
                                    aspectRatio: `${gradient.dimensions.width} / ${gradient.dimensions.height}`,
                                    ...modalDimensions,
                                    background: generateGradientCSS(),
                                    ...(isPlaying &&
                                        gradient.backgroundAnimation.enabled && {
                                            backgroundSize: "200% 200%",
                                        }),
                                    ...(isPlaying &&
                                        backgroundAnimation && {
                                            animation: backgroundAnimation,
                                        }),
                                }}
                            />
                        </motion.div>
                    </motion.div>
                )}
            </AnimatePresence>

            {/* MP4 Generation Loading Modal */}
            <AnimatePresence>
                {isGeneratingMP4 && (
                    <motion.div
                        initial={{ opacity: 0 }}
                        animate={{ opacity: 1 }}
                        exit={{ opacity: 0 }}
                        className="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50"
                    >
                        <motion.div
                            initial={{ scale: 0.8, opacity: 0 }}
                            animate={{ scale: 1, opacity: 1 }}
                            exit={{ scale: 0.8, opacity: 0 }}
                            className="bg-white rounded-xl p-8 max-w-md w-full mx-4"
                        >
                            <div className="text-center">
                                <div className="mb-4">
                                    <div className="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-purple-600"></div>
                                </div>
                                <h3 className="text-xl font-semibold mb-2">
                                    Generating MP4 Video
                                </h3>
                                <p className="text-gray-600 mb-4">
                                    {mp4Progress < 20
                                        ? "Initializing..."
                                        : mp4Progress < 70
                                            ? "Recording frames..."
                                            : mp4Progress < 90
                                                ? "Converting to MP4..."
                                                : "Finalizing..."}
                                </p>
                                <div className="w-full bg-gray-200 rounded-full h-2.5 mb-2">
                                    <div
                                        className="bg-purple-600 h-2.5 rounded-full transition-all duration-300"
                                        style={{ width: `${mp4Progress}%` }}
                                    ></div>
                                </div>
                                <p className="text-sm text-gray-500">
                                    {Math.round(mp4Progress)}%
                                </p>
                            </div>
                        </motion.div>
                    </motion.div>
                )}
            </AnimatePresence>
        </div>
    );
};

export default GradientGenerator;

Enter fullscreen mode Exit fullscreen mode

The Tech stack I am using is as follows

  1. Nextjs
  2. Reactjs
  3. Tailwind CSS
  4. Lucide React for icons
  5. Framer motion for animation 

The whole idea is to quickly make something that I can use and launch it as a URL, so I didn't pay attention to user authentication, CRUD operations by user, etc, and only one feature is added is to download the output.

One can look, I've added gradient presets to further allow people to quickly iterate over examples because I hate doing manual work every time, and templates save time. I'll add more gradients, almost 100,+ but I hope that won't confuse the user, so currently it only has 10/20 presets as examples.

That's the story for today, see you in the next one

Shrey

Top comments (0)