Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/frontend/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function Layout({ children }: LayoutProps) {
<div className='lucky-shell flex min-h-screen'>
<Sidebar />
<div className='flex min-w-0 flex-1 flex-col'>
<header className='sticky top-0 z-20 border-b border-lucky-border bg-lucky-bg-primary/70 backdrop-blur'>
<header className='sticky top-0 z-20 border-b border-lucky-border bg-lucky-bg-primary/70 backdrop-blur relative'>
<div className='mx-auto flex w-full max-w-[1400px] flex-wrap items-end justify-between gap-4 px-4 py-4 md:px-8'>
<div className='space-y-1'>
<p className='type-meta text-lucky-text-tertiary'>
Expand All @@ -87,6 +87,7 @@ function Layout({ children }: LayoutProps) {
</div>
)}
</div>
<div className='lucky-header-accent-line' aria-hidden='true' />
</header>

<main className='flex-1 min-w-0 overflow-y-auto'>
Expand Down
30 changes: 23 additions & 7 deletions packages/frontend/src/components/Music/NowPlaying.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
if (isLoading) return <NowPlayingSkeleton />

return (
<Card className='p-0 overflow-hidden contain-layout'>
<Card className='p-0 overflow-hidden contain-layout' glow={isPlaying}>
<div className='flex flex-col sm:flex-row'>
<AlbumArt track={currentTrack} isPlaying={isPlaying} />
<div className='flex-1 p-4 sm:p-6 flex flex-col justify-between min-w-0'>
Expand Down Expand Up @@ -127,9 +127,18 @@

return (
<div
className='relative w-full sm:w-56 md:w-64 h-44 sm:h-56 md:h-64 bg-lucky-bg-tertiary shrink-0'
className='relative w-full sm:w-56 md:w-64 h-44 sm:h-56 md:h-64 bg-lucky-bg-tertiary shrink-0 overflow-hidden'
aria-hidden='true'
>
{isPlaying && (
<div
className='pointer-events-none absolute inset-0 z-10'
style={{
background: 'radial-gradient(circle at center, rgb(139 92 246 / 0.15) 0%, transparent 70%)',
}}
aria-hidden='true'
/>
)}
{track?.thumbnail ? (
<>
{!imgLoaded && (
Expand All @@ -146,18 +155,22 @@
</>
) : (
<div className='w-full h-full flex items-center justify-center'>
<div
className='absolute inset-0 bg-gradient-to-br from-purple-500/10 to-transparent'
aria-hidden='true'
/>
<Disc3
className={`h-16 w-16 sm:h-20 sm:w-20 text-lucky-text-secondary opacity-30 ${isPlaying ? 'animate-spin motion-reduce:animate-none' : ''}`}
className={`relative h-16 w-16 sm:h-20 sm:w-20 text-lucky-text-secondary opacity-30 ${isPlaying ? 'animate-spin motion-reduce:animate-none' : ''}`}
style={{ animationDuration: '3s' }}
/>
</div>
)}
{isPlaying && (
<div
className='absolute bottom-2 left-2 flex items-center gap-1 bg-black/60 rounded-full px-2 py-1'
className='absolute bottom-2 left-2 flex items-center gap-1 bg-black/70 backdrop-blur-sm rounded-full px-2 py-1 border border-white/10'
role='status'
aria-label='Now playing'
>

Check warning on line 173 in packages/frontend/src/components/Music/NowPlaying.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use <output> instead of the "status" role to ensure accessibility across all devices.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_NexusBot&issues=AZz1P6K-YIscLWHBWggf&open=AZz1P6K-YIscLWHBWggf&pullRequest=317
<span
className='flex gap-0.5 motion-reduce:hidden'
aria-hidden='true'
Expand All @@ -165,7 +178,7 @@
{[1, 2, 3].map((i) => (
<span
key={i}
className='w-0.5 bg-primary rounded-full animate-pulse'
className='w-0.5 bg-purple-400 rounded-full animate-pulse'
style={{
height: `${8 + i * 3}px`,
animationDelay: `${i * 0.15}s`,
Expand Down Expand Up @@ -283,8 +296,11 @@
tabIndex={0}
>
<div
className='bg-primary h-full rounded-full transition-[width] duration-150 group-hover:h-2.5 sm:group-hover:h-2'
style={{ width: `${pct}%` }}
className='h-full rounded-full transition-[width] duration-150 group-hover:h-2.5 sm:group-hover:h-2'
style={{
width: `${pct}%`,
background: 'linear-gradient(90deg, #8b5cf6 0%, #d4a017 100%)',
}}
/>
</div>
<div className='flex justify-between text-xs text-lucky-text-secondary mt-1'>
Expand Down
16 changes: 8 additions & 8 deletions packages/frontend/src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,20 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<button
ref={ref}
className={cn(
'inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-all duration-200',
'relative inline-flex cursor-pointer items-center justify-center gap-2 rounded-lg font-medium transition-all duration-200',
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background',
'disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed',
'active:scale-[0.98]',
'active:scale-[0.97]',
{
'bg-primary hover:bg-primary/90 text-white':
'bg-gradient-to-br from-purple-500 to-purple-700 text-white shadow-[0_2px_12px_rgb(139_92_246/0.35)] hover:shadow-[0_4px_20px_rgb(139_92_246/0.5)] hover:from-purple-400 hover:to-purple-600':
variant === 'primary',
'bg-secondary hover:bg-secondary/90 text-white':
'bg-lucky-bg-tertiary border border-lucky-border text-lucky-text-primary hover:border-lucky-border-strong hover:bg-lucky-bg-active hover:text-white':
variant === 'secondary',
'bg-accent hover:bg-accent/90 text-white':
'bg-gradient-to-br from-yellow-500 to-amber-600 text-black shadow-[0_2px_12px_rgb(212_160_23/0.4)] hover:shadow-[0_4px_20px_rgb(212_160_23/0.6)] hover:from-yellow-400 hover:to-amber-500':
variant === 'accent',
'bg-transparent hover:bg-lucky-bg-tertiary text-white':
'bg-transparent hover:bg-lucky-bg-tertiary text-lucky-text-secondary hover:text-white':
variant === 'ghost',
'bg-destructive hover:bg-destructive/90 text-destructive-foreground':
'bg-destructive hover:bg-destructive/90 text-destructive-foreground shadow-[0_2px_12px_rgb(239_68_68/0.3)] hover:shadow-[0_4px_20px_rgb(239_68_68/0.45)]':
variant === 'destructive',
'px-3 py-1.5 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
Expand All @@ -52,7 +52,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
>
{loading && (
<Loader2
className='mr-2 h-4 w-4 animate-spin'
className='h-4 w-4 animate-spin'
aria-hidden='true'
/>
)}
Expand Down
26 changes: 20 additions & 6 deletions packages/frontend/src/components/ui/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,33 @@ import { cn } from '../../lib/utils'
interface CardProps extends HTMLAttributes<HTMLDivElement> {
hover?: boolean
interactive?: boolean
glow?: boolean
}

const Card = forwardRef<HTMLDivElement, CardProps>(
({ className, hover = false, interactive = false, ...props }, ref) => {
({ className, hover = false, interactive = false, glow = false, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
'bg-lucky-bg-secondary border border-lucky-border rounded-lg',
'transition-all duration-200 ease-out',
hover && 'hover:border-primary/50 hover:shadow-md',
interactive &&
'cursor-pointer hover:border-primary/50 hover:shadow-md hover:-translate-y-0.5 active:translate-y-0',
'bg-lucky-bg-secondary border border-lucky-border rounded-xl',
'shadow-[0_4px_24px_rgb(7_2_20/0.3)]',
'transition-all duration-220 ease-out',
hover && [
'hover:border-lucky-border-strong',
'hover:shadow-[0_8px_32px_rgb(139_92_246/0.12),0_4px_24px_rgb(7_2_20/0.3)]',
],
interactive && [
'cursor-pointer',
'hover:border-lucky-border-strong',
'hover:shadow-[0_8px_40px_rgb(139_92_246/0.15),0_4px_24px_rgb(7_2_20/0.3)]',
'hover:-translate-y-0.5',
'active:translate-y-0 active:shadow-[0_4px_24px_rgb(7_2_20/0.3)]',
],
glow && [
'border-purple-500/30',
'shadow-[0_0_0_1px_rgb(139_92_246/0.15),0_4px_24px_rgb(139_92_246/0.12),0_12px_40px_rgb(7_2_20/0.3)]',
],
className,
)}
{...props}
Expand Down
18 changes: 14 additions & 4 deletions packages/frontend/src/components/ui/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,28 @@ export default function EmptyState({
return (
<section
className={cn(
'surface-panel flex min-h-[240px] flex-col items-center justify-center px-6 py-8 text-center',
'surface-panel relative flex min-h-[240px] flex-col items-center justify-center overflow-hidden px-6 py-10 text-center',
className,
)}
>
<div
className='pointer-events-none absolute inset-0 bg-gradient-to-b from-purple-500/5 via-transparent to-transparent'
aria-hidden='true'
/>
{icon && (
<div className='mb-4 rounded-2xl bg-lucky-bg-active/60 p-4 text-lucky-text-secondary'>
{icon}
<div className='relative mb-5 flex items-center justify-center'>
<div
className='absolute h-16 w-16 rounded-full bg-purple-500/10 blur-xl'
aria-hidden='true'
/>
<div className='relative rounded-2xl border border-purple-500/20 bg-gradient-to-br from-purple-500/10 to-purple-800/10 p-4 text-lucky-text-secondary shadow-[0_0_20px_rgb(139_92_246/0.15)]'>
{icon}
</div>
</div>
)}
<h2 className='type-h2 text-lucky-text-primary'>{title}</h2>
<p className='mt-2 max-w-lg type-body text-lucky-text-secondary'>{description}</p>
{action && <div className='mt-5'>{action}</div>}
{action && <div className='mt-6'>{action}</div>}
</section>
)
}
61 changes: 54 additions & 7 deletions packages/frontend/src/components/ui/StatTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ const toneClass: Record<StatTone, string> = {
neutral: 'bg-lucky-bg-active text-lucky-text-secondary',
}

const toneGlowClass: Record<StatTone, string> = {
brand: 'drop-shadow-[0_0_6px_rgb(139_92_246/0.6)]',
accent: 'drop-shadow-[0_0_6px_rgb(212_160_23/0.6)]',
success: 'drop-shadow-[0_0_6px_rgb(34_197_94/0.6)]',
warning: 'drop-shadow-[0_0_6px_rgb(245_158_11/0.6)]',
neutral: '',
}

const toneOverlayClass: Record<StatTone, string> = {
brand: 'from-purple-500/5 to-transparent',
accent: 'from-yellow-500/5 to-transparent',
success: 'from-green-500/5 to-transparent',
warning: 'from-orange-500/5 to-transparent',
neutral: 'from-white/[0.02] to-transparent',
}

interface StatTileProps {
label: string
value: string | number
Expand All @@ -30,22 +46,53 @@ export default function StatTile({
className,
}: StatTileProps) {
return (
<article className={cn('surface-panel space-y-3 p-5', className)}>
<div className='flex items-center justify-between gap-2'>
<article
className={cn(
'surface-panel relative space-y-3 p-5 overflow-hidden',
className,
)}
>
<div
className={cn(
'pointer-events-none absolute inset-0 bg-gradient-to-br rounded-xl opacity-60',
toneOverlayClass[tone],
)}
aria-hidden='true'
/>
<div className='relative flex items-center justify-between gap-2'>
<p className='type-body-sm text-lucky-text-tertiary'>{label}</p>
{icon && <span className={cn('rounded-xl p-2', toneClass[tone])}>{icon}</span>}
{icon && (
<span
className={cn(
'rounded-xl p-2 transition-all duration-200',
toneClass[tone],
toneGlowClass[tone],
)}
>
{icon}
</span>
)}
</div>
<p className='type-h2 text-lucky-text-primary'>
<p
className='relative type-h2 text-lucky-text-primary animate-[fade-up_0.3s_ease-out]'
style={{ animationFillMode: 'both' }}
>
{typeof value === 'number' ? value.toLocaleString() : value}
</p>
{delta !== undefined && (
<p
className={cn(
'type-body-sm inline-flex items-center gap-1.5',
delta >= 0 ? 'text-lucky-success' : 'text-lucky-error',
'relative type-body-sm inline-flex items-center gap-1.5 rounded-full px-2 py-0.5',
delta >= 0
? 'bg-green-500/10 text-lucky-success'
: 'bg-red-500/10 text-lucky-error',
)}
>
{delta >= 0 ? <TrendingUp className='h-3.5 w-3.5' /> : <TrendingDown className='h-3.5 w-3.5' />}
{delta >= 0 ? (
<TrendingUp className='h-3.5 w-3.5' />
) : (
<TrendingDown className='h-3.5 w-3.5' />
)}
{Math.abs(delta)}%
</p>
)}
Expand Down
Loading
Loading