A sleek dark-mode card component featuring a custom dotted mesh pattern background, gradient light fade overlay, crisp media preview container, and action CTA buttons. Displays 3 responsive cards with custom media.
A surreal orange flower blooming against a deep cosmic backdrop and calm, with a luminous galaxy spiraling through its center.
Delicate pink cherry blossoms emerging from a soft winter-blue atmosphere, creating a calm and ethereal painted landscape.
A vibrant green and teal abstract landscape filled with soft organic textures and a flock of birds drifting through the scene.
1import React from "react";2import DotedPattern from "./doted-pattern";34export interface CardItemProps {5 id?: string | number;6 title?: string;7 description?: string;8 image?: string;9 imageAlt?: string;10 tag?: string;11 previewLabel?: string;12 docsLabel?: string;13 onPreview?: () => void;14 onDocs?: () => void;15 onActionClick?: () => void;16 className?: string;17}1819export interface CardsProps {20 cards?: CardItemProps[];21 className?: string;22 // Fallback single card props for backward compatibility23 title?: string;24 description?: string;25 image?: string;26 imageAlt?: string;27 tag?: string;28 previewLabel?: string;29 docsLabel?: string;30 onPreview?: () => void;31 onDocs?: () => void;32 onActionClick?: () => void;33}3435export const DEFAULT_CARDS: CardItemProps[] = [36 {37 id: "card-1",38 title: "Cosmic Bloom",39 description:40 "A surreal orange flower blooming against a deep cosmic backdrop and calm, with a luminous galaxy spiraling through its center.",41 image:42 "https://pbs.twimg.com/media/HP0V9iwaQAAsa56?format=jpg&name=large",43 imageAlt: "Cosmic orange flower with a glowing galaxy at its center",44 tag: "Surreal Art",45 previewLabel: "Preview",46 docsLabel: "Docs",47 },48 {49 id: "card-2",50 title: "Cherry Blossom Dream",51 description:52 "Delicate pink cherry blossoms emerging from a soft winter-blue atmosphere, creating a calm and ethereal painted landscape.",53 image:54 "https://pbs.twimg.com/media/HPtKeIlbQAA96T8?format=jpg&name=4096x4096",55 imageAlt: "Pink cherry blossom tree in a soft blue atmospheric landscape",56 tag: "Digital Painting",57 previewLabel: "Preview",58 docsLabel: "Docs",59 },60 {61 id: "card-3",62 title: "Emerald Flight",63 description:64 "A vibrant green and teal abstract landscape filled with soft organic textures and a flock of birds drifting through the scene.",65 image:66 "https://pbs.twimg.com/media/HPxBy4OXAAEG4Km?format=jpg&name=4096x4096",67 imageAlt: "Abstract green and teal landscape with flying white birds",68 tag: "Abstract Art",69 previewLabel: "Preview",70 docsLabel: "Docs",71 },72];7374export const CardItem: React.FC<CardItemProps> = ({75 title = "Gradient Effect",76 description = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facilis alias quaerat sint.",77 image = "/previews/card1.png",78 imageAlt = "Card preview",79 tag,80 previewLabel = "Preview",81 docsLabel = "Docs",82 onPreview,83 onDocs,84 onActionClick,85 className = "",86}) => {87 const repeatCount = 11;8889 return (90 <div className={`w-full max-w-95 rounded-4xl h-fit border border-neutral-700/60 bg-neutral-800 p-px shadow-xl hover:shadow-2xl transition-all duration-300 ${className}`}>91 <div className="relative w-full rounded-4xl border border-neutral-600 bg-neutral-800 overflow-hidden flex flex-col">92 {/* Background Dot Pattern at bottom */}93 <div className="absolute inset-x-0 bottom-0 h-1/2 flex overflow-hidden pointer-events-none z-0">94 {Array.from({ length: repeatCount }, (_, index) => (95 <div key={index} className="shrink-0">96 <DotedPattern />97 </div>98 ))}99 </div>100101 {/* Gradient Mask Over Background Pattern */}102 <div className="absolute inset-x-0 bottom-0 h-1/2 bg-linear-to-b from-neutral-800 from-40% to-neutral-800/10 pointer-events-none z-1 rounded-b-[30px]" />103104 {/* Card Content starting right at top */}105 <div className="relative z-10 flex flex-col justify-start w-full p-1.5 gap-2.5">106 {/* Image Section at top */}107 <div className="relative w-full rounded-4xl border border-neutral-600/80 p-px overflow-hidden bg-neutral-900/50">108 <div className="w-full h-60 overflow-hidden rounded-[28px] relative group p-px">109 <img110 className="w-full h-full object-cover flex self-center transition-transform duration-500 ease-out rounded-4xl"111 src={image}112 alt={imageAlt || title}113 />114115 </div>116 </div>117118 {/* Text Content & Buttons */}119 <div className="w-full flex flex-col gap-2 px-3 pb-2">120 <div className="w-full flex items-center justify-between gap-2 ">121 <h3 className="text-sm font-semibold text-neutral-100 truncate tracking-tight">{title}</h3>122 <div className="flex items-center justify-center p-1 rounded-full hover:bg-neutral-700/50 transition-colors shrink-0">123 <button124 type="button"125 aria-label={`Open ${title}`}126 onClick={onActionClick}127 className="cursor-pointer flex items-center justify-center text-blue-500 hover:text-blue-400 transition-colors"128 >129 <svg130 xmlns="http://www.w3.org/2000/svg"131 width="16"132 height="16"133 viewBox="0 0 24 24"134 fill="none"135 >136 <path137 d="M21.25 12C21.25 17.1086 17.1086 21.25 12 21.25C6.89137 21.25 2.75 17.1086 2.75 12C2.75 6.89137 6.89137 2.75 12 2.75C12.4142 2.75 12.75 2.41421 12.75 2C12.75 1.58579 12.4142 1.25 12 1.25C6.06294 1.25 1.25 6.06294 1.25 12C1.25 17.9371 6.06294 22.75 12 22.75C17.9371 22.75 22.75 17.9371 22.75 12C22.75 11.5858 22.4142 11.25 22 11.25C21.5858 11.25 21.25 11.5858 21.25 12Z"138 fill="#3B82F6"139 stroke="#3B82F6"140 strokeWidth="0.5"141 />142 <path143 d="M12.4697 10.4697C12.1768 10.7626 12.1768 11.2374 12.4697 11.5303C12.7626 11.8232 13.2374 11.8232 13.5303 11.5303L21.25 3.81066V7.34375C21.25 7.75796 21.5858 8.09375 22 8.09375C22.4142 8.09375 22.75 7.75796 22.75 7.34375V2C22.75 1.58579 22.4142 1.25 22 1.25H16.6562C16.242 1.25 15.9062 1.58579 15.9062 2C15.9062 2.41421 16.242 2.75 16.6562 2.75H20.1893L12.4697 10.4697Z"144 fill="#3B82F6"145 stroke="#3B82F6"146 strokeWidth="0.5"147 />148 </svg>149 </button>150 </div>151 </div>152153 <div className="w-full">154 <p className="text-[12px] text-neutral-400 leading-relaxed line-clamp-3">155 {description}156 </p>157 </div>158159 <div className="w-full pt-1.5 flex items-center gap-2">160 <button161 type="button"162 onClick={onPreview}163 className="text-[13px] font-medium bg-neutral-100 text-neutral-900 hover:bg-white rounded-full px-4 py-1.5 transition-all duration-200 cursor-pointer active:scale-95 shadow-sm"164 >165 {previewLabel}166 </button>167 <button168 type="button"169 onClick={onDocs}170 className="text-[13px] font-medium text-neutral-300 rounded-full border border-neutral-600 hover:border-neutral-500 hover:bg-neutral-700/50 px-4 py-1.5 transition-all duration-200 cursor-pointer active:scale-95"171 >172 {docsLabel}173 </button>174 </div>175 </div>176 </div>177 </div>178 </div>179 );180};181182const Cards: React.FC<CardsProps> = ({183 cards,184 className = "",185 title,186 description,187 image,188 imageAlt,189 tag,190 previewLabel,191 docsLabel,192 onPreview,193 onDocs,194 onActionClick,195}) => {196 // If specific single-card props are explicitly provided without a cards array197 if (!cards && (title || image || description)) {198 return (199 <div className={`w-full flex items-center justify-center p-4 ${className}`}>200 <CardItem201 title={title}202 description={description}203 image={image}204 imageAlt={imageAlt}205 tag={tag}206 previewLabel={previewLabel}207 docsLabel={docsLabel}208 onPreview={onPreview}209 onDocs={onDocs}210 onActionClick={onActionClick}211 />212 </div>213 );214 }215216 const items = cards || DEFAULT_CARDS;217218 return (219 <div className={`w-full flex flex-wrap items-center justify-center gap-6 p-4 sm:p-6 ${className}`}>220 {items.map((item, index) => (221 <CardItem222 key={item.id || index}223 title={item.title}224 description={item.description}225 image={item.image}226 imageAlt={item.imageAlt}227 tag={item.tag}228 previewLabel={item.previewLabel}229 docsLabel={item.docsLabel}230 onPreview={item.onPreview}231 onDocs={item.onDocs}232 onActionClick={item.onActionClick}233 />234 ))}235 </div>236 );237};238239export default Cards;