Focus Cards
A grid of cards where hovering one card blurs and grayscales the others, creating a cinematic spotlight effect.
Installation
Add this component to your project using the CLI:
npx vui-registry-cli-v1 add focus-cardsSource Code
'use client'
import React, { useState } from 'react'
import { cn } from '@/lib/utils'
interface Card {
id: string
title: string
subtitle: string
image: string
}
const DEFAULT_CARDS: Card[] = [
{ id: '1', title: 'Kyoto', subtitle: 'The Spirit of Japan', image: 'https://images.unsplash.com/photo-1493976040374-85c8e12f0c0e?auto=format&fit=crop&q=80&w=800' },
{ id: '2', title: 'New York', subtitle: 'Metropolitan Pulse', image: 'https://images.unsplash.com/photo-1514565131-fce0801e5785?auto=format&fit=crop&q=80&w=800' }, // Clean, architectural NY
{ id: '3', title: 'Iceland', subtitle: 'Eternal Silence', image: 'https://images.unsplash.com/photo-1476610182048-b716b8518aae?auto=format&fit=crop&q=80&w=800' },
{ id: '4', title: 'Paris', subtitle: 'The Golden Hour', image: 'https://images.unsplash.com/photo-1502602898657-3e91760cbb34?auto=format&fit=crop&q=80&w=800' },
{ id: '5', title: 'Swiss Alps', subtitle: 'Peak Serenity', image: 'https://images.unsplash.com/photo-1531366936337-7c912a4589a7?auto=format&fit=crop&q=80&w=800' },
{ id: '6', title: 'Tokyo', subtitle: 'Futurist Dream', image: 'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?auto=format&fit=crop&q=80&w=800' },
]
export default function AtelierFocusCards({ cards = DEFAULT_CARDS }: { cards?: Card[] }) {
const [hovered, setHovered] = useState<string | null>(null)
return (
<div className="w-full min-h-screen bg-white dark:bg-[#0a0a0a] flex items-center justify-center p-4">
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 max-w-6xl w-full">
{cards.map((card) => (
<div
key={card.id}
onMouseEnter={() => setHovered(card.id)}
onMouseLeave={() => setHovered(null)}
className={cn(
"relative aspect-[2/3] overflow-hidden bg-neutral-200 dark:bg-neutral-900",
"transition-all duration-700 ease-[cubic-bezier(0.19,1,0.22,1)]",
hovered !== null && hovered !== card.id && "opacity-30 grayscale-[0.5]"
)}
>
{/* Image with slow-motion zoom */}
<img
src={card.image}
alt={card.title}
className={cn(
"absolute inset-0 w-full h-full object-cover transition-transform duration-[1.5s] ease-out",
hovered === card.id ? "scale-110" : "scale-100"
)}
/>
{/* Ultra-subtle overlay */}
<div className={cn(
"absolute inset-0 bg-black/20 transition-opacity duration-500",
hovered === card.id ? "opacity-100" : "opacity-0"
)} />
{/* Minimalist Text Content */}
<div className="absolute inset-x-0 bottom-0 p-8 flex flex-col justify-end">
{/* Vertical line accent */}
<div className={cn(
"w-[1px] bg-white transition-all duration-700 mb-4 origin-bottom",
hovered === card.id ? "h-12 opacity-100" : "h-0 opacity-0"
)} />
<h3 className={cn(
"text-2xl font-light text-white tracking-tighter transition-all duration-500 delay-75",
hovered === card.id ? "translate-x-0 opacity-100" : "-translate-x-4 opacity-0"
)}>
{card.title}
</h3>
<p className={cn(
"text-[10px] uppercase tracking-[0.3em] text-neutral-300 mt-1 transition-all duration-500 delay-150",
hovered === card.id ? "translate-x-0 opacity-100" : "-translate-x-2 opacity-0"
)}>
{card.subtitle}
</p>
</div>
{/* Thin hairline border for light mode contrast */}
<div className="absolute inset-0 border border-black/5 pointer-events-none" />
</div>
))}
</div>
</div>
)
}
Dependencies
framer-motion: latestlucide-react: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| cards | Card[] | Demo Cards | Array of card objects with title, subtitle, and image. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

