2026-01-26 13:12:17 +00:00
|
|
|
import * as React from 'react'
|
|
|
|
|
import { cva, type VariantProps } from 'class-variance-authority'
|
|
|
|
|
|
|
|
|
|
import { cn } from '@/lib/utils'
|
2025-12-03 21:56:50 +00:00
|
|
|
|
2026-01-18 21:27:53 +00:00
|
|
|
const cardVariants = cva(
|
2026-02-12 01:09:29 +00:00
|
|
|
'flex flex-col rounded-lg text-card-foreground transition-[box-shadow,background-color,border-color] duration-[var(--sumi-duration-normal)] ease-out relative overflow-hidden',
|
2026-01-18 21:27:53 +00:00
|
|
|
{
|
|
|
|
|
variants: {
|
|
|
|
|
variant: {
|
fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header
Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 09:13:44 +00:00
|
|
|
/** Shadows wired to SUMI tokens via @theme — auto dark/light intensity */
|
2026-01-26 13:12:17 +00:00
|
|
|
default:
|
fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header
Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 09:13:44 +00:00
|
|
|
'bg-card border border-border shadow-card hover:shadow-card-hover hover:bg-card/95',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
elevated:
|
fix: UI remediation Phase 1 (S0-S5) + Phase 2 Sprint 6 shadow system
Phase 1:
- S0: Fix open redirect (safeNavigate), delete AuthContext/legacy auth, encrypt API keys, gitignore .env files
- S1: Split client.ts god object into 5 modules, unify toast system, delete unused Sidebar
- S2: Add glass button variant, migrate 32 z-index to SUMI tokens, fix card dark mode
- S3: Skip nav link, aria-hidden on icons, focus-visible ring fixes, alt attrs, aria-live regions
- S4: React.memo on list items, fix key={index}, loading=lazy on images
- S5: Branded loading screen, page transitions respect reduced-motion, LikeButton micro-interaction, i18n sidebar/header
Phase 2 Sprint 6:
- Wire Tailwind shadow utilities to SUMI tokens in @theme block (fixes 50+ files)
- Define shadow-card/shadow-card-hover tokens
- Remove dark:shadow-none workarounds from card.tsx (SUMI handles per-theme shadows)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 09:13:44 +00:00
|
|
|
'bg-card border border-border shadow-lg hover:shadow-xl hover:bg-card/95',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
ghost:
|
2026-02-07 07:03:28 +00:00
|
|
|
'bg-transparent border-0',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
outline:
|
2026-02-07 07:03:28 +00:00
|
|
|
'bg-transparent border border-border',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
muted:
|
2026-02-07 18:28:12 +00:00
|
|
|
'bg-muted/50 border border-border',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
interactive:
|
refactor: Phase 4 — Update UI primitives to SUMI design system
- button.tsx: Remove gaming/terminal/nature/glass variants, add link variant,
add focus-visible glow, remove scale transforms and neon shadows
- card.tsx: Remove glass/glow/glowMagenta variants, update radius to rounded-lg,
remove hover translate transforms
- modal.tsx: Update backdrop to bg-black/60 + blur(4px), bg-popover,
SUMI easing curves and durations
- badge.tsx: Terminal variant aliased to success, doc updates
- avatar.tsx: Already migrated, doc updates
- progress.tsx: Fix gradient colors to SUMI semantics
- input.tsx: bg-background, border-border, SUMI focus glow
- textarea.tsx: Add SUMI focus glow
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:58:15 +00:00
|
|
|
'bg-card border border-transparent shadow-card cursor-pointer hover:shadow-card-hover hover:border-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
2026-01-26 13:12:17 +00:00
|
|
|
|
|
|
|
|
spotlight:
|
2026-02-10 08:29:48 +00:00
|
|
|
'bg-card/80 border border-border hover:border-border',
|
2026-02-07 18:44:40 +00:00
|
|
|
|
|
|
|
|
/* Immersive surface: subtle border, lighter + diffuse shadow on hover */
|
|
|
|
|
surface:
|
2026-02-12 01:09:29 +00:00
|
|
|
'bg-card border border-border shadow-none hover:bg-card/90 hover:border-border hover:shadow-card-hover transition-all duration-[var(--sumi-duration-slow)] ease-in-out',
|
2026-02-12 23:32:08 +00:00
|
|
|
|
|
|
|
|
/* Glass effect: backdrop blur, transparent background */
|
|
|
|
|
glass:
|
|
|
|
|
'bg-card/80 dark:bg-black/20 backdrop-blur-xl border border-border',
|
2026-01-26 13:12:17 +00:00
|
|
|
},
|
|
|
|
|
padding: {
|
|
|
|
|
none: '',
|
|
|
|
|
sm: 'p-4',
|
|
|
|
|
default: 'p-6',
|
|
|
|
|
lg: 'p-8',
|
2026-01-18 21:27:53 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
defaultVariants: {
|
|
|
|
|
variant: 'default',
|
2026-01-26 13:12:17 +00:00
|
|
|
padding: 'none',
|
2026-01-18 21:27:53 +00:00
|
|
|
},
|
2026-01-26 13:12:17 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
interface CardProps
|
|
|
|
|
extends React.ComponentProps<'div'>,
|
|
|
|
|
VariantProps<typeof cardVariants> {
|
|
|
|
|
spotlight?: boolean;
|
|
|
|
|
spotlightColor?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Card({ className, variant, padding, spotlight, spotlightColor = 'rgba(255, 255, 255, 0.1)', ...props }: CardProps) {
|
|
|
|
|
const divRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
|
const [position, setPosition] = React.useState({ x: 0, y: 0 });
|
|
|
|
|
const [opacity, setOpacity] = React.useState(0);
|
|
|
|
|
|
|
|
|
|
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
if (!divRef.current || (variant !== 'spotlight' && !spotlight)) return;
|
|
|
|
|
|
|
|
|
|
const div = divRef.current;
|
|
|
|
|
const rect = div.getBoundingClientRect();
|
2026-01-18 21:27:53 +00:00
|
|
|
|
2026-01-26 13:12:17 +00:00
|
|
|
setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
|
|
|
};
|
2026-01-18 21:27:53 +00:00
|
|
|
|
2026-01-26 13:12:17 +00:00
|
|
|
const handleMouseEnter = () => {
|
|
|
|
|
setOpacity(1);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = () => {
|
|
|
|
|
setOpacity(0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isSpotlight = variant === 'spotlight' || spotlight;
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-18 21:27:53 +00:00
|
|
|
<div
|
2026-01-26 13:12:17 +00:00
|
|
|
ref={divRef}
|
|
|
|
|
onMouseMove={handleMouseMove}
|
|
|
|
|
onMouseEnter={handleMouseEnter}
|
|
|
|
|
onMouseLeave={handleMouseLeave}
|
|
|
|
|
data-slot="card"
|
|
|
|
|
className={cn(cardVariants({ variant, padding }), className)}
|
2026-01-18 21:27:53 +00:00
|
|
|
{...props}
|
|
|
|
|
>
|
2026-01-26 13:12:17 +00:00
|
|
|
{isSpotlight && (
|
|
|
|
|
<div
|
2026-02-12 01:09:29 +00:00
|
|
|
className="pointer-events-none absolute -inset-px opacity-0 transition duration-[var(--sumi-duration-normal)]"
|
2026-01-26 13:12:17 +00:00
|
|
|
style={{
|
|
|
|
|
opacity,
|
|
|
|
|
background: `radial-gradient(600px circle at ${position.x}px ${position.y}px, ${spotlightColor}, transparent 40%)`,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<div className="relative z-10 w-full h-full flex flex-col">{props.children}</div>
|
2026-01-18 21:27:53 +00:00
|
|
|
</div>
|
|
|
|
|
)
|
2026-01-26 13:12:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-slot="card-header"
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex flex-col gap-1.5 p-6 pb-0',
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardTitle({ className, ...props }: React.ComponentProps<'h3'>) {
|
|
|
|
|
return (
|
|
|
|
|
<h3
|
|
|
|
|
data-slot="card-title"
|
2026-02-07 07:03:28 +00:00
|
|
|
className={cn('text-lg font-semibold leading-tight tracking-tight text-foreground', className)}
|
2026-01-26 13:12:17 +00:00
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardDescription({ className, ...props }: React.ComponentProps<'p'>) {
|
|
|
|
|
return (
|
|
|
|
|
<p
|
|
|
|
|
data-slot="card-description"
|
2026-02-07 07:03:28 +00:00
|
|
|
className={cn('text-sm text-muted-foreground/90', className)}
|
2026-01-26 13:12:17 +00:00
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-slot="card-action"
|
|
|
|
|
className={cn(
|
|
|
|
|
'absolute top-4 right-4',
|
|
|
|
|
className,
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-slot="card-content"
|
|
|
|
|
className={cn('p-6 pt-4', className)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
data-slot="card-footer"
|
|
|
|
|
className={cn('flex items-center gap-3 p-6 pt-0', className)}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-12-03 21:56:50 +00:00
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export {
|
|
|
|
|
Card,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardFooter,
|
|
|
|
|
CardTitle,
|
2026-01-26 13:12:17 +00:00
|
|
|
CardAction,
|
2026-01-13 18:47:57 +00:00
|
|
|
CardDescription,
|
|
|
|
|
CardContent,
|
2026-01-26 13:12:17 +00:00
|
|
|
cardVariants,
|
|
|
|
|
}
|