-
Notifications
You must be signed in to change notification settings - Fork 489
feat: Introduce new UI layout with floating dock, visual effects, and… #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,118 @@ | ||||||||||||||||||||||||||||||||||||||
| import { useRef } from 'react'; | ||||||||||||||||||||||||||||||||||||||
| import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'; | ||||||||||||||||||||||||||||||||||||||
| import { useNavigate, useLocation } from '@tanstack/react-router'; | ||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||
| LayoutDashboard, | ||||||||||||||||||||||||||||||||||||||
| Bot, | ||||||||||||||||||||||||||||||||||||||
| FileText, | ||||||||||||||||||||||||||||||||||||||
| Database, | ||||||||||||||||||||||||||||||||||||||
| Terminal, | ||||||||||||||||||||||||||||||||||||||
| Settings, | ||||||||||||||||||||||||||||||||||||||
| Users, | ||||||||||||||||||||||||||||||||||||||
| type LucideIcon, | ||||||||||||||||||||||||||||||||||||||
| } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||
| import { cn } from '@/lib/utils'; | ||||||||||||||||||||||||||||||||||||||
| import { useAppStore } from '@/store/app-store'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export function FloatingDock() { | ||||||||||||||||||||||||||||||||||||||
| const mouseX = useMotionValue(Infinity); | ||||||||||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||
| const location = useLocation(); | ||||||||||||||||||||||||||||||||||||||
| const { currentProject } = useAppStore(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const navItems = [ | ||||||||||||||||||||||||||||||||||||||
| { id: 'board', icon: LayoutDashboard, label: 'Board', path: '/board' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'agent', icon: Bot, label: 'Agent', path: '/agent' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'spec', icon: FileText, label: 'Spec', path: '/spec' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'context', icon: Database, label: 'Context', path: '/context' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'profiles', icon: Users, label: 'Profiles', path: '/profiles' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'terminal', icon: Terminal, label: 'Terminal', path: '/terminal' }, | ||||||||||||||||||||||||||||||||||||||
| { id: 'settings', icon: Settings, label: 'Settings', path: '/settings' }, | ||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (!currentProject) return null; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||
| <div className="fixed bottom-8 left-1/2 -translate-x-1/2 z-50"> | ||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||
| onMouseMove={(e) => mouseX.set(e.pageX)} | ||||||||||||||||||||||||||||||||||||||
| onMouseLeave={() => mouseX.set(Infinity)} | ||||||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||||||
| 'flex h-16 items-end gap-4 rounded-2xl px-4 pb-3', | ||||||||||||||||||||||||||||||||||||||
| 'bg-white/5 backdrop-blur-2xl border border-white/10 shadow-2xl' | ||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| {navItems.map((item) => ( | ||||||||||||||||||||||||||||||||||||||
| <DockIcon | ||||||||||||||||||||||||||||||||||||||
| key={item.id} | ||||||||||||||||||||||||||||||||||||||
| mouseX={mouseX} | ||||||||||||||||||||||||||||||||||||||
| icon={item.icon} | ||||||||||||||||||||||||||||||||||||||
| path={item.path} | ||||||||||||||||||||||||||||||||||||||
| label={item.label} | ||||||||||||||||||||||||||||||||||||||
| isActive={location.pathname.startsWith(item.path)} | ||||||||||||||||||||||||||||||||||||||
| onClick={() => navigate({ to: item.path })} | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| function DockIcon({ | ||||||||||||||||||||||||||||||||||||||
| mouseX, | ||||||||||||||||||||||||||||||||||||||
| icon: Icon, | ||||||||||||||||||||||||||||||||||||||
| path, | ||||||||||||||||||||||||||||||||||||||
| label, | ||||||||||||||||||||||||||||||||||||||
| isActive, | ||||||||||||||||||||||||||||||||||||||
| onClick, | ||||||||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||||||||
| mouseX: any; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the The 🔎 Type-safe fix+import { motion, useMotionValue, useSpring, useTransform, MotionValue } from 'framer-motion';
function DockIcon({
mouseX,
icon: Icon,
path,
label,
isActive,
onClick,
}: {
- mouseX: any;
+ mouseX: MotionValue<number>;
icon: LucideIcon;
path: string;
label: string;
isActive: boolean;
onClick: () => void;
}) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
| icon: LucideIcon; | ||||||||||||||||||||||||||||||||||||||
| path: string; | ||||||||||||||||||||||||||||||||||||||
| label: string; | ||||||||||||||||||||||||||||||||||||||
| isActive: boolean; | ||||||||||||||||||||||||||||||||||||||
| onClick: () => void; | ||||||||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||||||||
| const ref = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const distance = useTransform(mouseX, (val: number) => { | ||||||||||||||||||||||||||||||||||||||
| const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; | ||||||||||||||||||||||||||||||||||||||
| return val - bounds.x - bounds.width / 2; | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]); | ||||||||||||||||||||||||||||||||||||||
| const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 }); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+83
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add reduced motion support for dock animations. The spring-based width animation does not respect 🔎 Recommended fix+import { motion, useMotionValue, useSpring, useTransform, useReducedMotion } from 'framer-motion';
function DockIcon({
mouseX,
icon: Icon,
path,
label,
isActive,
onClick,
}: {
mouseX: MotionValue<number>;
icon: LucideIcon;
path: string;
label: string;
isActive: boolean;
onClick: () => void;
}) {
const ref = useRef<HTMLDivElement>(null);
+ const shouldReduceMotion = useReducedMotion();
const distance = useTransform(mouseX, (val: number) => {
const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
return val - bounds.x - bounds.width / 2;
});
- const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
- const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 });
+ const widthSync = useTransform(
+ distance,
+ [-150, 0, 150],
+ shouldReduceMotion ? [60, 60, 60] : [40, 80, 40]
+ );
+ const width = useSpring(widthSync, {
+ mass: 0.1,
+ stiffness: shouldReduceMotion ? 300 : 150,
+ damping: shouldReduceMotion ? 30 : 12
+ });Also applies to: 89-89 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||
| ref={ref} | ||||||||||||||||||||||||||||||||||||||
| style={{ width }} | ||||||||||||||||||||||||||||||||||||||
| className="aspect-square cursor-pointer group relative" | ||||||||||||||||||||||||||||||||||||||
| onClick={onClick} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| {/* Tooltip */} | ||||||||||||||||||||||||||||||||||||||
| <div className="absolute -top-10 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity text-xs font-mono bg-black/80 text-white px-2 py-1 rounded backdrop-blur-md border border-white/10 pointer-events-none whitespace-nowrap"> | ||||||||||||||||||||||||||||||||||||||
| {label} | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||||||
| 'flex h-full w-full items-center justify-center rounded-full transition-colors', | ||||||||||||||||||||||||||||||||||||||
| isActive | ||||||||||||||||||||||||||||||||||||||
| ? 'bg-primary text-primary-foreground shadow-[0_0_20px_rgba(34,211,238,0.3)]' | ||||||||||||||||||||||||||||||||||||||
| : 'bg-white/5 text-muted-foreground hover:bg-white/10' | ||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| <Icon className="h-[40%] w-[40%]" /> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {/* Active Dot */} | ||||||||||||||||||||||||||||||||||||||
| {isActive && ( | ||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||
| layoutId="activeDockDot" | ||||||||||||||||||||||||||||||||||||||
| className="absolute -bottom-2 left-1/2 w-1 h-1 bg-primary rounded-full -translate-x-1/2" | ||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { ChevronDown, Command, Folder } from 'lucide-react'; | ||
| import { cn } from '@/lib/utils'; | ||
| import { useAppStore } from '@/store/app-store'; | ||
| import { Button } from '@/components/ui/button'; | ||
| import { | ||
| DropdownMenu, | ||
| DropdownMenuContent, | ||
| DropdownMenuItem, | ||
| DropdownMenuLabel, | ||
| DropdownMenuSeparator, | ||
| DropdownMenuTrigger, | ||
| } from '@/components/ui/dropdown-menu'; | ||
|
|
||
| interface HudProps { | ||
| onOpenProjectPicker: () => void; | ||
| onOpenFolder: () => void; | ||
| } | ||
|
|
||
| export function Hud({ onOpenProjectPicker, onOpenFolder }: HudProps) { | ||
| const { currentProject, projects, setCurrentProject } = useAppStore(); | ||
|
|
||
| if (!currentProject) return null; | ||
|
|
||
| return ( | ||
| <div className="fixed top-4 left-4 z-50 flex items-center gap-3"> | ||
| {/* Project Pill */} | ||
| <DropdownMenu> | ||
| <DropdownMenuTrigger asChild> | ||
| <div | ||
| className={cn( | ||
| 'group flex items-center gap-3 px-4 py-2 rounded-full cursor-pointer', | ||
| 'bg-white/5 backdrop-blur-md border border-white/10', | ||
| 'hover:bg-white/10 transition-colors' | ||
| )} | ||
| > | ||
| <div className="w-2 h-2 rounded-full bg-emerald-500 shadow-[0_0_10px_rgba(16,185,129,0.4)] animate-pulse" /> | ||
| <span className="font-mono text-sm font-medium tracking-tight"> | ||
| {currentProject.name} | ||
| </span> | ||
| <ChevronDown className="w-3 h-3 text-muted-foreground group-hover:text-foreground transition-colors" /> | ||
| </div> | ||
| </DropdownMenuTrigger> | ||
| <DropdownMenuContent className="w-56 glass border-white/10" align="start"> | ||
| <DropdownMenuLabel>Switch Project</DropdownMenuLabel> | ||
| <DropdownMenuSeparator /> | ||
| {projects.slice(0, 5).map((p) => ( | ||
| <DropdownMenuItem | ||
| key={p.id} | ||
| onClick={() => setCurrentProject(p)} | ||
| className="font-mono text-xs" | ||
| > | ||
| {p.name} | ||
| </DropdownMenuItem> | ||
| ))} | ||
| <DropdownMenuSeparator /> | ||
| <DropdownMenuItem onClick={onOpenProjectPicker}> | ||
| <Command className="mr-2 w-3 h-3" /> | ||
| All Projects... | ||
| </DropdownMenuItem> | ||
| <DropdownMenuItem onClick={onOpenFolder}> | ||
| <Folder className="mr-2 w-3 h-3" /> | ||
| Open Local Folder... | ||
| </DropdownMenuItem> | ||
| </DropdownMenuContent> | ||
| </DropdownMenu> | ||
|
|
||
| {/* Dynamic Status / Breadcrumbs could go here */} | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| export function NoiseOverlay() { | ||
| return ( | ||
| <div className="fixed inset-0 z-50 pointer-events-none opacity-[0.015] mix-blend-overlay"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check z-index usage in the codebase, especially for modals, dialogs, dropdowns, toasts
rg -n "z-\[?[0-9]+" apps/ui/src --type=tsx --type=css -A 2 -B 2 | head -100Repository: AutoMaker-Org/automaker Length of output: 94 🏁 Script executed: #!/bin/bash
# Check z-index usage in the codebase
rg "z-\[?[0-9]+" apps/ui/src -g "*.tsx" -g "*.ts" -g "*.css" -A 1 -B 1 | head -150Repository: AutoMaker-Org/automaker Length of output: 13647 Lower z-index recommended for decorative overlay. The noise overlay uses z-50, which is the same level as modals, dialogs, dropdowns, tooltips, and other interactive components throughout the codebase. While the overlay's 🤖 Prompt for AI Agents |
||
| <svg className="w-full h-full"> | ||
| <filter id="noiseFilter"> | ||
| <feTurbulence | ||
| type="fractalNoise" | ||
| baseFrequency="0.80" | ||
| numOctaves="3" | ||
| stitchTiles="stitch" | ||
| /> | ||
| </filter> | ||
| <rect width="100%" height="100%" filter="url(#noiseFilter)" /> | ||
| </svg> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { ReactNode } from 'react'; | ||
| import { cn } from '@/lib/utils'; | ||
| import { motion } from 'framer-motion'; | ||
|
|
||
| interface PageShellProps { | ||
| children: ReactNode; | ||
| className?: string; | ||
| fullWidth?: boolean; | ||
| } | ||
|
|
||
| export function PageShell({ children, className, fullWidth = false }: PageShellProps) { | ||
| return ( | ||
| <div className="relative w-full h-full pt-16 pb-24 px-6 overflow-hidden"> | ||
| <motion.div | ||
| initial={{ opacity: 0, scale: 0.98, y: 10 }} | ||
| animate={{ opacity: 1, scale: 1, y: 0 }} | ||
| transition={{ duration: 0.4, ease: [0.2, 0, 0, 1] }} | ||
| className={cn( | ||
| 'w-full h-full rounded-3xl overflow-hidden', | ||
| 'bg-black/20 backdrop-blur-2xl border border-white/5 shadow-2xl', | ||
| 'flex flex-col', | ||
| !fullWidth && 'max-w-7xl mx-auto', | ||
| className | ||
| )} | ||
| > | ||
| {children} | ||
| </motion.div> | ||
|
Comment on lines
+14
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add reduced motion support for accessibility compliance. The entrance animation does not respect the 🔎 Recommended fix using Framer Motion's useReducedMotion import { ReactNode } from 'react';
import { cn } from '@/lib/utils';
-import { motion } from 'framer-motion';
+import { motion, useReducedMotion } from 'framer-motion';
interface PageShellProps {
children: ReactNode;
className?: string;
fullWidth?: boolean;
}
export function PageShell({ children, className, fullWidth = false }: PageShellProps) {
+ const shouldReduceMotion = useReducedMotion();
+
return (
<div className="relative w-full h-full pt-16 pb-24 px-6 overflow-hidden">
<motion.div
- initial={{ opacity: 0, scale: 0.98, y: 10 }}
- animate={{ opacity: 1, scale: 1, y: 0 }}
- transition={{ duration: 0.4, ease: [0.2, 0, 0, 1] }}
+ initial={shouldReduceMotion ? false : { opacity: 0, scale: 0.98, y: 10 }}
+ animate={{ opacity: 1, scale: 1, y: 0 }}
+ transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.4, ease: [0.2, 0, 0, 1] }}
className={cn(🤖 Prompt for AI Agents |
||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { motion } from 'framer-motion'; | ||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| export function PrismField() { | ||
| const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); | ||
|
|
||
| useEffect(() => { | ||
| const handleMouseMove = (e: MouseEvent) => { | ||
| setMousePosition({ | ||
| x: e.clientX, | ||
| y: e.clientY, | ||
| }); | ||
| }; | ||
|
|
||
| window.addEventListener('mousemove', handleMouseMove); | ||
| return () => window.removeEventListener('mousemove', handleMouseMove); | ||
| }, []); | ||
|
Comment on lines
+5
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating React state on every You'll need to update your import: Then, you can use these motion values in |
||
|
|
||
| return ( | ||
| <div className="fixed inset-0 z-0 overflow-hidden pointer-events-none bg-[#0b101a]"> | ||
| {/* Deep Space Base */} | ||
| <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_50%,rgba(17,24,39,1)_0%,rgba(11,16,26,1)_100%)]" /> | ||
|
|
||
| {/* Animated Orbs */} | ||
| <motion.div | ||
| animate={{ | ||
| x: mousePosition.x * 0.02, | ||
| y: mousePosition.y * 0.02, | ||
| }} | ||
| transition={{ type: 'spring', damping: 50, stiffness: 400 }} | ||
| className="absolute top-[-20%] left-[-10%] w-[70vw] h-[70vw] rounded-full bg-cyan-500/5 blur-[120px] mix-blend-screen" | ||
| /> | ||
|
|
||
| <motion.div | ||
| animate={{ | ||
| x: mousePosition.x * -0.03, | ||
| y: mousePosition.y * -0.03, | ||
| }} | ||
| transition={{ type: 'spring', damping: 50, stiffness: 400 }} | ||
| className="absolute bottom-[-20%] right-[-10%] w-[60vw] h-[60vw] rounded-full bg-violet-600/5 blur-[120px] mix-blend-screen" | ||
| /> | ||
|
|
||
| <motion.div | ||
| animate={{ | ||
| scale: [1, 1.1, 1], | ||
| opacity: [0.3, 0.5, 0.3], | ||
| }} | ||
| transition={{ | ||
| duration: 8, | ||
| repeat: Infinity, | ||
| ease: 'easeInOut', | ||
| }} | ||
| className="absolute top-[30%] left-[50%] transform -translate-x-1/2 -translate-y-1/2 w-[40vw] h-[40vw] rounded-full bg-blue-500/5 blur-[100px] mix-blend-screen" | ||
| /> | ||
|
|
||
| {/* Grid Overlay */} | ||
| <div | ||
| className="absolute inset-0 z-10 opacity-[0.03]" | ||
| style={{ | ||
| backgroundImage: `linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)`, | ||
| backgroundSize: '50px 50px', | ||
| }} | ||
| /> | ||
|
|
||
| {/* Vignette */} | ||
| <div className="absolute inset-0 z-20 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(11,16,26,0.8)_100%)]" /> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { ReactNode } from 'react'; | ||
| import { cn } from '../../lib/utils'; | ||
| import { PrismField } from './prism-field'; | ||
| import { NoiseOverlay } from './noise-overlay'; | ||
|
|
||
| interface ShellProps { | ||
| children: ReactNode; | ||
| className?: string; | ||
| showBackgroundElements?: boolean; | ||
| } | ||
|
|
||
| export function Shell({ children, className, showBackgroundElements = true }: ShellProps) { | ||
| return ( | ||
| <div | ||
| className={cn( | ||
| 'relative min-h-screen w-full overflow-hidden bg-background text-foreground transition-colors duration-500', | ||
| className | ||
| )} | ||
| > | ||
| {/* Animated Background Layers */} | ||
| {showBackgroundElements && ( | ||
| <> | ||
| <PrismField /> | ||
| <NoiseOverlay /> | ||
| </> | ||
| )} | ||
|
|
||
| {/* Content wrapper */} | ||
| <div className="relative z-10 flex h-screen flex-col">{children}</div> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better type safety, the
mouseXprop is typed asany. You should use the specific type fromframer-motion, which isMotionValue<number>. You'll need to importMotionValuefromframer-motionat the top of the file:import { ..., type MotionValue } from 'framer-motion';