d2
componentbento-02source: ok · hit
Bento 02
A five-card feature bento with metallic-gradient glyphs on lifting icon tiles, a dot-matrix texture and color-dodged glow revealed on hover, and a holographic brand ring tracing each card. Motion-driven, theme-aware, and reduced-motion aware.
npx shadcn add "https://ui.d2studio.dev/r/bento-02.json"free
Where it goes
logo-cloudbento-gridfeature-grid
Visual style
bentogradient
Interaction
scroll-reveal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · mediumanimationmotion
Files1 · source cached 10/15/2026, 7:20:07 AM
registry/default/components/bento-02.tsx
"use client"
import { type ReactNode } from "react"
import { motion, useReducedMotion, type Variants } from "motion/react"
// Direction-aware hover easing: clean ease-in on the way up, a restrained
// settle on the return — a barely-there overshoot for a premium feel.
const DUR = 0.42
const EASE_IN = [0.22, 1, 0.36, 1] as const
const EASE_OUT = [0.34, 1.2, 0.64, 1] as const // slight overshoot, no big bounce
const cardMotion: Variants = {
rest: { y: 0, transition: { duration: DUR, ease: EASE_OUT } },
hover: { y: -4, transition: { duration: DUR, ease: EASE_IN } },
}
const tileMotion: Variants = {
rest: { x: 0, y: 0, transition: { duration: DUR, ease: EASE_OUT } },
hover: { x: -3, y: -4, transition: { duration: DUR, ease: EASE_IN } },
}
// Faithful to src-bento-2.tsx: a centered header + a 2-up top row and a 3-up
// bottom row of cards. Each card has an 80x80 icon tile (top-left), a title,
// and a short description. Holographic sweep on tile hover follows the D2 brand.
// Conversational-AI glyph — from public/Message Icon 1.svg. Metallic linear
// gradient fill (#FFF -> #101010) plus an inner shadow, kept inline so the
// filter and gradients render exactly as designed.
function MessageIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g clipPath="url(#msg_clip)">
<path
d="M32.2397 37.8861C32.5827 37.8861 32.9211 38.0166 33.1785 38.2652C34.8242 39.8543 37.2247 40.3834 39.3586 39.7211C37.9853 36.86 37.795 33.5822 38.8592 30.5472C40.18 26.7712 40.511 22.9865 39.0079 19.1906C37.6873 15.8561 35.1388 13.0857 31.9136 11.5059C32.112 12.5637 32.212 13.6415 32.212 14.7307C32.2121 19.354 30.4343 23.7023 27.2064 26.9748C23.9781 30.2476 19.6505 32.0879 15.0211 32.1567C14.161 32.1702 13.3048 32.1196 12.4583 32.008C15.0025 36.4677 19.8009 39.3723 25.0738 39.4505C27.3804 39.4862 29.5952 39.0029 31.6578 38.0179C31.8435 37.9291 32.0424 37.8861 32.2397 37.8861Z"
fill="url(#msg_paint0)"
/>
<path
d="M15.0592 0.00293902C6.84668 -0.16013 0.0198465 6.48363 4.4531e-05 14.6943C-0.00480695 16.7004 0.386777 18.6453 1.164 20.4754C1.17529 20.502 1.18579 20.529 1.19529 20.5563C2.25955 23.5913 2.06935 26.869 0.695886 29.7302C2.83004 30.3921 5.23024 29.8633 6.87599 28.2741C7.28143 27.8824 7.88806 27.7838 8.39678 28.027C10.4591 29.0119 12.6741 29.4967 14.9806 29.4597C23.1289 29.3387 29.5115 22.8689 29.5114 14.7306C29.5115 6.76987 23.0282 0.163137 15.0592 0.00293902ZM6.8849 16.9306C5.705 16.9306 4.74846 15.9756 4.74846 14.7977C4.74846 13.6198 5.705 12.6648 6.8849 12.6648C8.0648 12.6648 9.02133 13.6198 9.02133 14.7977C9.02123 15.9756 8.0648 16.9306 6.8849 16.9306ZM14.7558 16.9306C13.5759 16.9306 12.6194 15.9756 12.6194 14.7977C12.6194 13.6198 13.5759 12.6648 14.7558 12.6648C15.9357 12.6648 16.8922 13.6198 16.8922 14.7977C16.8922 15.9756 15.9357 16.9306 14.7558 16.9306ZM22.6268 16.9306C21.4469 16.9306 20.4903 15.9756 20.4903 14.7977C20.4903 13.6198 21.4469 12.6648 22.6268 12.6648C23.8067 12.6648 24.7632 13.6198 24.7632 14.7977C24.7632 15.9756 23.8067 16.9306 22.6268 16.9306Z"
fill="url(#msg_paint1)"
/>
</g>
<defs>
<linearGradient id="msg_paint0" x1="26.2292" y1="11.5059" x2="26.2292" y2="40.0001" gradientUnits="userSpaceOnUse">
<stop stopColor="white" />
<stop offset="1" stopColor="#101010" />
</linearGradient>
<linearGradient id="msg_paint1" x1="14.7557" y1="0" x2="14.7557" y2="30.009" gradientUnits="userSpaceOnUse">
<stop stopColor="white" />
<stop offset="1" stopColor="#101010" />
</linearGradient>
<clipPath id="msg_clip">
<rect width="40" height="40" fill="white" />
</clipPath>
</defs>
</svg>
)
}
// Image-recognition glyph — from public/Face ID Icon 1.svg. Same metallic
// gradient + inner-shadow treatment as MessageIcon, kept inline for fidelity.
function FaceIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g clipPath="url(#face_clip)">
<path d="M37.6562 37.6562H34.5271V39.9999H39.9999V34.5271H37.6562V37.6562Z" fill="url(#face_p0)" />
<path d="M2.34375 2.34375H5.47273V0H0V5.47273H2.34375V2.34375Z" fill="url(#face_p1)" />
<path d="M2.34375 34.5271H0V39.9999H5.47273V37.6562H2.34375V34.5271Z" fill="url(#face_p2)" />
<path d="M34.5271 0V2.34375H37.6562V5.47273H39.9999V0H34.5271Z" fill="url(#face_p3)" />
<path d="M26.9817 13.2987V10.9901C26.9817 7.11666 23.8304 3.96533 19.9569 3.96533C16.0834 3.96533 12.9321 7.11666 12.9321 10.9901V13.2987C12.9321 17.1721 16.0834 20.3234 19.9569 20.3234C23.8304 20.3234 26.9817 17.1721 26.9817 13.2987ZM19.1332 16.0568L16.1847 13.1083L17.842 11.451L19.1332 12.7423L22.4655 9.4101L24.1228 11.0674L19.1332 16.0568Z" fill="url(#face_p4)" />
<path d="M5.10449 34.5364H34.8097V31.0167C34.8097 28.6379 33.2837 26.5639 31.0125 25.856L24.283 23.7585V21.6064C22.9882 22.2834 21.5167 22.667 19.9571 22.667C18.3975 22.667 16.926 22.2834 15.6311 21.6064V23.7585L8.9016 25.856C6.63043 26.5639 5.10449 28.6378 5.10449 31.0167V34.5364Z" fill="url(#face_p5)" />
</g>
<defs>
<linearGradient id="face_p0" x1="37.2635" y1="34.5271" x2="37.2635" y2="39.9999" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="face_p1" x1="2.73637" y1="0" x2="2.73637" y2="5.47273" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="face_p2" x1="2.73637" y1="34.5271" x2="2.73637" y2="39.9999" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="face_p3" x1="37.2635" y1="0" x2="37.2635" y2="5.47273" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="face_p4" x1="19.9569" y1="3.96533" x2="19.9569" y2="20.3234" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="face_p5" x1="19.9571" y1="21.6064" x2="19.9571" y2="34.5364" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<clipPath id="face_clip"><rect width="40" height="40" fill="white" /></clipPath>
</defs>
</svg>
)
}
// Predictive-analytics glyph — from public/Analysis Icon 1.svg.
function AnalysisIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g>
<path fillRule="evenodd" clipRule="evenodd" d="M5.05258 16.5011L12.404 10.0671C12.3021 9.78878 12.2466 9.48831 12.2466 9.17479C12.2466 7.74136 13.4086 6.5794 14.842 6.5794C16.248 6.5794 17.3927 7.69737 17.4361 9.09284L23.2464 11.1271C23.7224 10.5682 24.4312 10.2138 25.2227 10.2138C25.6694 10.2138 26.0896 10.3266 26.4566 10.5253L33.1549 4.80222C33.0599 4.53237 33.008 4.24222 33.008 3.93987C33.008 2.50651 34.1701 1.34448 35.6034 1.34448C37.037 1.34448 38.199 2.50651 38.199 3.93987C38.199 5.37339 37.037 6.53542 35.6034 6.53542C35.1569 6.53542 34.7366 6.42261 34.3697 6.22394L27.6713 11.947C27.7663 12.2168 27.8182 12.507 27.8182 12.8093C27.8182 14.2427 26.6562 15.4047 25.2227 15.4047C23.8167 15.4047 22.672 14.2867 22.6287 12.8914L16.8184 10.8571C16.3423 11.416 15.6336 11.7704 14.842 11.7704C14.4061 11.7704 13.9955 11.6628 13.6348 11.473L6.28344 17.9071C6.38531 18.1853 6.44086 18.4858 6.44086 18.7993C6.44086 20.2328 5.27883 21.3947 3.84531 21.3947C2.41203 21.3946 1.25 20.2327 1.25 18.7992C1.25 17.3657 2.41203 16.2038 3.84539 16.2038C4.28125 16.2038 4.69195 16.3113 5.05258 16.5011ZM33.6291 11.661C32.9837 11.661 32.4572 12.1874 32.4572 12.8328V36.7491C32.4572 37.3946 32.9837 37.921 33.6291 37.921H37.5781C38.2237 37.921 38.7501 37.3946 38.7501 36.7491V12.8328C38.7501 12.1874 38.2238 11.661 37.5781 11.661H33.6291ZM23.2482 21.218C22.6027 21.218 22.0764 21.7444 22.0764 22.3899V36.9941C22.0764 37.6396 22.6028 38.166 23.2482 38.166H27.1974C27.8429 38.166 28.3692 37.6396 28.3692 36.9941C28.3692 32.1261 28.3692 27.258 28.3692 22.3899C28.3692 21.7444 27.8429 21.218 27.1974 21.218C25.8809 21.218 24.5647 21.218 23.2482 21.218ZM12.8675 17.787C12.222 17.787 11.6955 18.3134 11.6955 18.9589V37.2391C11.6955 37.8846 12.2219 38.411 12.8675 38.411H16.8166C17.462 38.411 17.9884 37.8846 17.9884 37.2391V18.9589C17.9884 18.3134 17.462 17.787 16.8166 17.787H12.8675ZM2.48672 27.8742H6.4357C7.08117 27.8742 7.60766 28.4007 7.60766 29.0461V37.484C7.60766 38.1295 7.08117 38.6559 6.4357 38.6559H2.48672C1.84125 38.6559 1.31477 38.1295 1.31477 37.484V29.0461C1.31484 28.4006 1.84133 27.8742 2.48672 27.8742Z" fill="url(#ana_p0)" />
</g>
<defs>
<linearGradient id="ana_p0" x1="20" y1="1.34448" x2="20" y2="38.6559" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
</defs>
</svg>
)
}
// Model-Ops glyph — from Dashboard Icon 1.svg. Four rounded tiles.
function DashboardIcon({ className }: { className?: string }) {
const gN = ["d_p0", "d_p1", "d_p2", "d_p3"]
return (
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g clipPath="url(#d_clip)">
<path d="M15.4166 0H2.91656C1.30829 0 0 1.30829 0 2.91656V10.4166C0 12.0251 1.30829 13.3334 2.91656 13.3334H15.4166C17.0251 13.3334 18.3334 12.0251 18.3334 10.4166V2.91656C18.3334 1.30829 17.0251 0 15.4166 0Z" fill={`url(#${gN[0]})`} />
<path d="M15.4166 16.6665H2.91656C1.30829 16.6665 0 17.9748 0 19.5834V37.0834C0 38.6917 1.30829 39.9999 2.91656 39.9999H15.4166C17.0251 39.9999 18.3334 38.6917 18.3334 37.0834V19.5834C18.3334 17.9748 17.0251 16.6665 15.4166 16.6665Z" fill={`url(#${gN[1]})`} />
<path d="M37.0834 26.6665H24.5834C22.9748 26.6665 21.6665 27.9748 21.6665 29.5834V37.0834C21.6665 38.6917 22.9748 39.9999 24.5834 39.9999H37.0834C38.6917 39.9999 39.9999 38.6917 39.9999 37.0834V29.5834C39.9999 27.9748 38.6917 26.6665 37.0834 26.6665Z" fill={`url(#${gN[2]})`} />
<path d="M37.0834 0H24.5834C22.9748 0 21.6665 1.30829 21.6665 2.91656V20.4166C21.6665 22.0251 22.9748 23.3334 24.5834 23.3334H37.0834C38.6917 23.3334 39.9999 22.0251 39.9999 20.4166V2.91656C39.9999 1.30829 38.6917 0 37.0834 0Z" fill={`url(#${gN[3]})`} />
</g>
<defs>
{[
{ id: "d_p0", x: 9.16672, y1: 0, y2: 13.3334 },
{ id: "d_p1", x: 9.16672, y1: 16.6665, y2: 39.9999 },
{ id: "d_p2", x: 30.8332, y1: 26.6665, y2: 39.9999 },
{ id: "d_p3", x: 30.8332, y1: 0, y2: 23.3334 },
].map((g) => (
<linearGradient key={g.id} id={g.id} x1={g.x} y1={g.y1} x2={g.x} y2={g.y2} gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
))}
<clipPath id="d_clip"><rect width="40" height="40" fill="white" /></clipPath>
</defs>
</svg>
)
}
// Privacy glyph — from Locked Padlock 1.svg.
function PadlockIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g clipPath="url(#pad_clip)">
<path d="M25.7865 13.2782C27.0688 13.8205 28.2546 14.5329 29.3302 15.4016V9.26117C29.3302 4.15489 25.1753 0 20.069 0H19.9265C14.8248 0 10.6699 4.15489 10.6699 9.26117V15.4016C11.7454 14.533 12.9312 13.8206 14.2135 13.2782C14.5582 13.1311 14.9121 12.9978 15.266 12.8783V9.26117C15.266 6.68735 17.3573 4.59612 19.9311 4.59612H20.0736C22.6474 4.59612 24.7386 6.68735 24.7386 9.26117V12.8829C25.0879 12.9978 25.4418 13.1311 25.7865 13.2782Z" fill="url(#pad_p0)" />
<path d="M6.97461 26.9746C6.97461 34.1675 12.8071 39.9999 20 39.9999C27.1929 39.9999 33.0254 34.172 33.0254 26.9746C33.0254 23.4402 31.6144 20.2321 29.3302 17.8834C28.0433 16.5644 26.4806 15.5164 24.734 14.8316C23.2679 14.2571 21.6684 13.9446 20 13.9446C18.3317 13.9446 16.7322 14.2571 15.266 14.8316C13.5195 15.5119 11.9568 16.5598 10.6699 17.8834C8.38102 20.2321 6.97461 23.4402 6.97461 26.9746ZM16.6448 24.3456C16.6908 22.5669 18.1385 21.1191 19.9172 21.0777C21.8062 21.0317 23.3551 22.5531 23.3551 24.4329C23.3551 24.6306 23.3367 24.8235 23.3046 25.0074C23.139 25.9588 22.5738 26.7769 21.7832 27.2733C21.4568 27.4801 21.2914 27.8662 21.3742 28.2385L22.3716 32.9679C22.4314 33.2529 22.2153 33.5241 21.9211 33.5241H18.0788C17.7847 33.5241 17.5686 33.2575 17.6284 32.9679L18.6257 28.2385C18.7039 27.8616 18.543 27.4755 18.2167 27.2687C17.4262 26.7723 16.8609 25.9588 16.6953 25.0028C16.6586 24.7914 16.6402 24.5708 16.6448 24.3456Z" fill="url(#pad_p1)" />
</g>
<defs>
<linearGradient id="pad_p0" x1="20" y1="0" x2="20" y2="11.4549" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<linearGradient id="pad_p1" x1="20" y1="13.9446" x2="20" y2="33.3233" gradientUnits="userSpaceOnUse"><stop stopColor="white" /><stop offset="1" stopColor="#101010" /></linearGradient>
<clipPath id="pad_clip"><rect width="40" height="40" fill="white" /></clipPath>
</defs>
</svg>
)
}
type Feature = {
custom: ReactNode
title: string
description: string
}
const topRow: Feature[] = [
{
custom: <FaceIcon className="h-10 w-10" />,
title: "Advanced Image Recognition",
description:
"Detect, classify, and tag images with industry-leading accuracy. Plug-and-play models for visual data with explainability tools.",
},
{
custom: <AnalysisIcon className="h-10 w-10" />,
title: "Predictive Analytics",
description:
"Forecast outcomes and trends with actionable confidence scores. Time-series, classification, and anomaly-detection models pre-configured.",
},
]
const bottomRow: Feature[] = [
{
custom: <MessageIcon className="h-10 w-10" />,
title: "NLP & Conversational AI",
description: "Semantic search, summarization, and chat solutions.",
},
{
custom: <DashboardIcon className="h-10 w-10" />,
title: "Model Ops",
description: "CI/CD for ML, drift detection, and performance dashboards.",
},
{
custom: <PadlockIcon className="h-10 w-10" />,
title: "Privacy & Compliance",
description: "Built with data protection, role-based access and audit logs.",
},
]
// Card decoration: the dot texture (revealed on hover) with a blurred
// cyan→bronze oval glow layered above it — color-dodged for a soft sheen.
// Card's own overflow-hidden clips the 150px blur bleed.
function CardPattern() {
return (
<>
{/* Dot texture — promoted to its own GPU layer (translateZ) so the
card's transform lift doesn't re-rasterize the masked gradient. */}
<span
aria-hidden
className="pointer-events-none absolute inset-0 z-0 opacity-0 transition-opacity duration-500 ease-out group-hover:opacity-100"
style={{
backgroundColor: "transparent",
backgroundImage:
"radial-gradient(rgba(128,128,128,0.1) 17%, transparent 18% 35%, transparent 36.5%), radial-gradient(rgba(128,128,128,0.1) 17%, transparent 18% 35%, transparent 36.5%), radial-gradient(transparent 34%, rgba(128,128,128,0.1) 36% 68%, transparent 70%), repeating-linear-gradient(45deg, rgba(128,128,128,0.1) -12.5% 12.5%, transparent 0 37.5%)",
backgroundPosition: "-2.2px -2.2px, 2.2px 2.2px, 0 0, 0 0",
backgroundSize: "8.8px 8.8px, 8.8px 8.8px, 4.4px 4.4px, 8.8px 8.8px",
WebkitMaskImage: "linear-gradient(to bottom, #000 0%, transparent 50%)",
maskImage: "linear-gradient(to bottom, #000 0%, transparent 50%)",
transform: "translateZ(0)",
backfaceVisibility: "hidden",
willChange: "transform",
}}
/>
{/* Oval glow — sits above the dot texture, below the card content. */}
<span
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 z-0 h-[80%] rounded-[50%]"
style={{
background: "linear-gradient(135deg, #70EAFF 0%, #8D4E36 100%)",
opacity: 0.3,
mixBlendMode: "color-dodge",
filter: "blur(40px)",
// Fade the ellipse out before its geometric edge so the
// hard oval boundary never reads as a line at low blur.
WebkitMaskImage: "radial-gradient(ellipse at center, #000 30%, transparent 72%)",
maskImage: "radial-gradient(ellipse at center, #000 30%, transparent 72%)",
}}
/>
</>
)
}
function FeatureCard({ custom, title, description }: Feature) {
const reduce = useReducedMotion()
return (
<motion.div
className="bento2-card group relative isolate flex h-full flex-col overflow-hidden rounded-2xl border border-[oklch(0.868_0.005_286.3)] bg-[oklch(0.972_0_0)] p-8 dark:border-white/10 dark:bg-zinc-900"
initial="rest"
animate="rest"
whileHover="hover"
variants={reduce ? undefined : cardMotion}
>
<CardPattern />
{/* Icon tile — 80x80, lifts off the card on hover (theme-aware
shadow + brand ring stay in CSS; Motion drives the movement) */}
<motion.div
className="bento2-tile relative z-10 mb-8 grid h-20 w-20 shrink-0 self-start place-items-center overflow-hidden rounded-[4px] border border-[oklch(0.868_0.005_286.3)] bg-[oklch(0.975_0_0)] dark:border-white/10 dark:bg-zinc-800"
variants={reduce ? undefined : tileMotion}
>
{custom}
{/* Brand holographic sweep on hover */}
<span
aria-hidden
className="pointer-events-none absolute inset-0 -translate-x-full bg-[var(--d2-flash-gradient)] opacity-0 mix-blend-overlay transition-all duration-700 ease-out group-hover:translate-x-0 group-hover:opacity-60"
/>
</motion.div>
<h3 className="relative z-10 text-lg font-semibold text-[oklch(0.21_0.006_285.9)] dark:text-zinc-50">{title}</h3>
<p className="relative z-10 mt-2 text-sm leading-relaxed text-[oklch(0.552_0.016_285.9)] dark:text-zinc-400">
{description}
</p>
</motion.div>
)
}
const Bento2 = () => {
return (
<section className="w-full px-6 py-16 sm:px-8 md:py-24">
<div className="mx-auto max-w-5xl">
{/* Header */}
<div className="mb-12 text-center">
<h2 className="text-3xl font-bold tracking-tight text-[oklch(0.21_0.006_285.9)] sm:text-4xl dark:text-zinc-50">
Solution & Features
</h2>
<p className="mx-auto mt-3 max-w-md text-base text-[oklch(0.552_0.016_285.9)] dark:text-zinc-400">
Focused, outcome driven features that translate ML into real business value.
</p>
</div>
{/* Top row — 2 wide cards */}
<div className="grid gap-6 sm:grid-cols-2">
{topRow.map((f) => (
<FeatureCard key={f.title} {...f} />
))}
</div>
{/* Bottom row — 3 cards */}
<div className="mt-6 grid gap-6 sm:grid-cols-3">
{bottomRow.map((f) => (
<FeatureCard key={f.title} {...f} />
))}
</div>
</div>
</section>
)
}
export default Bento2