From 2eb92a0402c1668c415a1ec55dc64bfb8e850d9c Mon Sep 17 00:00:00 2001 From: SuperComboGamer Date: Mon, 22 Dec 2025 21:10:12 -0500 Subject: [PATCH] feat: Introduce new UI layout with floating dock, visual effects, and expanded theme options. --- apps/ui/package.json | 4 +- apps/ui/src/app.tsx | 6 +- .../src/components/layout/floating-dock.tsx | 118 +++ apps/ui/src/components/layout/hud.tsx | 70 ++ .../src/components/layout/noise-overlay.tsx | 17 + apps/ui/src/components/layout/page-shell.tsx | 30 + apps/ui/src/components/layout/prism-field.tsx | 69 ++ apps/ui/src/components/layout/shell.tsx | 32 + apps/ui/src/components/layout/sidebar.tsx | 67 +- apps/ui/src/components/ui/badge.tsx | 7 + apps/ui/src/components/ui/button.tsx | 21 +- apps/ui/src/components/ui/card.tsx | 6 +- apps/ui/src/components/ui/dialog.tsx | 10 +- apps/ui/src/components/ui/dropdown-menu.tsx | 3 +- apps/ui/src/components/ui/input.tsx | 24 +- apps/ui/src/components/ui/slider.tsx | 6 +- apps/ui/src/components/ui/switch.tsx | 4 +- apps/ui/src/components/views/board-view.tsx | 1 + .../views/board-view/board-header.tsx | 74 +- .../board-view/components/kanban-column.tsx | 5 +- .../components/views/board-view/constants.ts | 40 +- .../views/board-view/kanban-board.tsx | 1 + .../ui/src/components/views/settings-view.tsx | 57 +- .../components/settings-header.tsx | 8 +- apps/ui/src/components/views/spec-view.tsx | 2 +- .../spec-view/components/spec-header.tsx | 2 +- .../ui/src/components/views/terminal-view.tsx | 507 ++++++------ apps/ui/src/store/app-store.ts | 62 +- apps/ui/src/styles/global.css | 554 +++++++------- index (25).html | 723 ++++++++++++++++++ package-lock.json | 46 +- 31 files changed, 1838 insertions(+), 738 deletions(-) create mode 100644 apps/ui/src/components/layout/floating-dock.tsx create mode 100644 apps/ui/src/components/layout/hud.tsx create mode 100644 apps/ui/src/components/layout/noise-overlay.tsx create mode 100644 apps/ui/src/components/layout/page-shell.tsx create mode 100644 apps/ui/src/components/layout/prism-field.tsx create mode 100644 apps/ui/src/components/layout/shell.tsx create mode 100644 index (25).html diff --git a/apps/ui/package.json b/apps/ui/package.json index 9c3522c6..501b32fc 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -69,13 +69,15 @@ "cmdk": "^1.1.1", "dagre": "^0.8.5", "dotenv": "^17.2.3", + "framer-motion": "^12.23.26", "geist": "^1.5.1", "lucide-react": "^0.562.0", "react": "19.2.3", "react-dom": "19.2.3", "react-markdown": "^10.1.0", - "rehype-raw": "^7.0.0", "react-resizable-panels": "^3.0.6", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "zustand": "^5.0.9" diff --git a/apps/ui/src/app.tsx b/apps/ui/src/app.tsx index 50380095..8b291bdf 100644 --- a/apps/ui/src/app.tsx +++ b/apps/ui/src/app.tsx @@ -6,6 +6,8 @@ import { useSettingsMigration } from './hooks/use-settings-migration'; import './styles/global.css'; import './styles/theme-imports'; +import { Shell } from './components/layout/shell'; + export default function App() { const [showSplash, setShowSplash] = useState(() => { // Only show splash once per session @@ -27,9 +29,9 @@ export default function App() { }, []); return ( - <> + {showSplash && } - + ); } diff --git a/apps/ui/src/components/layout/floating-dock.tsx b/apps/ui/src/components/layout/floating-dock.tsx new file mode 100644 index 00000000..ed9016ee --- /dev/null +++ b/apps/ui/src/components/layout/floating-dock.tsx @@ -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 ( +
+ 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) => ( + navigate({ to: item.path })} + /> + ))} + +
+ ); +} + +function DockIcon({ + mouseX, + icon: Icon, + path, + label, + isActive, + onClick, +}: { + mouseX: any; + icon: LucideIcon; + path: string; + label: string; + isActive: boolean; + onClick: () => void; +}) { + const ref = useRef(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 }); + + return ( + + {/* Tooltip */} +
+ {label} +
+ +
+ +
+ + {/* Active Dot */} + {isActive && ( + + )} + + ); +} diff --git a/apps/ui/src/components/layout/hud.tsx b/apps/ui/src/components/layout/hud.tsx new file mode 100644 index 00000000..791788ec --- /dev/null +++ b/apps/ui/src/components/layout/hud.tsx @@ -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 ( +
+ {/* Project Pill */} + + +
+
+ + {currentProject.name} + + +
+ + + Switch Project + + {projects.slice(0, 5).map((p) => ( + setCurrentProject(p)} + className="font-mono text-xs" + > + {p.name} + + ))} + + + + All Projects... + + + + Open Local Folder... + + + + + {/* Dynamic Status / Breadcrumbs could go here */} +
+ ); +} diff --git a/apps/ui/src/components/layout/noise-overlay.tsx b/apps/ui/src/components/layout/noise-overlay.tsx new file mode 100644 index 00000000..2373b55c --- /dev/null +++ b/apps/ui/src/components/layout/noise-overlay.tsx @@ -0,0 +1,17 @@ +export function NoiseOverlay() { + return ( +
+ + + + + + +
+ ); +} diff --git a/apps/ui/src/components/layout/page-shell.tsx b/apps/ui/src/components/layout/page-shell.tsx new file mode 100644 index 00000000..aed2b80f --- /dev/null +++ b/apps/ui/src/components/layout/page-shell.tsx @@ -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 ( +
+ + {children} + +
+ ); +} diff --git a/apps/ui/src/components/layout/prism-field.tsx b/apps/ui/src/components/layout/prism-field.tsx new file mode 100644 index 00000000..b9aeeed2 --- /dev/null +++ b/apps/ui/src/components/layout/prism-field.tsx @@ -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); + }, []); + + return ( +
+ {/* Deep Space Base */} +
+ + {/* Animated Orbs */} + + + + + + + {/* Grid Overlay */} +
+ + {/* Vignette */} +
+
+ ); +} diff --git a/apps/ui/src/components/layout/shell.tsx b/apps/ui/src/components/layout/shell.tsx new file mode 100644 index 00000000..ef23dd81 --- /dev/null +++ b/apps/ui/src/components/layout/shell.tsx @@ -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 ( +
+ {/* Animated Background Layers */} + {showBackgroundElements && ( + <> + + + + )} + + {/* Content wrapper */} +
{children}
+
+ ); +} diff --git a/apps/ui/src/components/layout/sidebar.tsx b/apps/ui/src/components/layout/sidebar.tsx index 0e87d03b..cee53741 100644 --- a/apps/ui/src/components/layout/sidebar.tsx +++ b/apps/ui/src/components/layout/sidebar.tsx @@ -17,8 +17,9 @@ import { ProjectActions, SidebarNavigation, ProjectSelectorWithOptions, - SidebarFooter, } from './sidebar/components'; +import { Hud } from './hud'; +import { FloatingDock } from './floating-dock'; import { TrashDialog, OnboardingDialog } from './sidebar/dialogs'; import { SIDEBAR_FEATURE_FLAGS } from './sidebar/constants'; import { @@ -247,64 +248,27 @@ export function Sidebar() { }; return ( - + ); } diff --git a/apps/ui/src/components/ui/badge.tsx b/apps/ui/src/components/ui/badge.tsx index f1a425bc..8e99a2a7 100644 --- a/apps/ui/src/components/ui/badge.tsx +++ b/apps/ui/src/components/ui/badge.tsx @@ -24,6 +24,13 @@ const badgeVariants = cva( // Muted variants for subtle indication muted: 'border-border/50 bg-muted/50 text-muted-foreground', brand: 'border-transparent bg-brand-500/15 text-brand-500 border border-brand-500/30', + // Prism variants + prism: + 'border-cyan-500/30 bg-cyan-500/10 text-cyan-400 hover:bg-cyan-500/20 font-mono tracking-wide rounded-md', + 'prism-orange': + 'border-amber-500/30 bg-amber-500/10 text-amber-400 hover:bg-amber-500/20 font-mono tracking-wide rounded-md', + 'prism-green': + 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400 hover:bg-emerald-500/20 font-mono tracking-wide rounded-md', }, size: { default: 'px-2.5 py-0.5 text-xs', diff --git a/apps/ui/src/components/ui/button.tsx b/apps/ui/src/components/ui/button.tsx index fa970a52..bda9cd56 100644 --- a/apps/ui/src/components/ui/button.tsx +++ b/apps/ui/src/components/ui/button.tsx @@ -6,25 +6,32 @@ import { Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-200 cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive active:scale-[0.98]", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all duration-300 cursor-pointer disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive active:scale-[0.98]", { variants: { variant: { default: - 'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 hover:shadow-md hover:shadow-primary/25', + 'bg-primary text-primary-foreground shadow-lg shadow-primary/20 hover:bg-primary/90 hover:shadow-primary/40 hover:-translate-y-0.5', destructive: 'bg-destructive text-white shadow-sm hover:bg-destructive/90 hover:shadow-md hover:shadow-destructive/25 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', outline: - 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', - secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', + 'border border-border/50 bg-background/50 backdrop-blur-sm shadow-sm hover:bg-accent hover:text-accent-foreground dark:bg-white/5 dark:hover:bg-white/10 hover:border-accent', + secondary: + 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 hover:shadow-md', + ghost: 'hover:bg-accent/50 hover:text-accent-foreground hover:backdrop-blur-sm', link: 'text-primary underline-offset-4 hover:underline active:scale-100', + glass: + 'border border-white/10 bg-white/5 text-foreground shadow-sm drop-shadow-sm backdrop-blur-md hover:bg-white/10 hover:border-white/20 hover:shadow-md transition-all duration-300', 'animated-outline': 'relative overflow-hidden rounded-xl hover:bg-transparent shadow-none', + 'prism-primary': + 'bg-cyan-400 text-slate-950 font-extrabold shadow-lg shadow-cyan-400/20 hover:brightness-110 hover:shadow-cyan-400/40 transition-all duration-200 tracking-wide', + 'prism-glass': + 'glass hover:bg-white/10 text-xs font-bold rounded-xl transition-all duration-200', }, size: { default: 'h-9 px-4 py-2 has-[>svg]:px-3', - sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5', - lg: 'h-10 rounded-md px-6 has-[>svg]:px-4', + sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 text-xs', + lg: 'h-11 rounded-md px-8 has-[>svg]:px-5 text-base', icon: 'size-9', 'icon-sm': 'size-8', 'icon-lg': 'size-10', diff --git a/apps/ui/src/components/ui/card.tsx b/apps/ui/src/components/ui/card.tsx index 86963f11..594e63dd 100644 --- a/apps/ui/src/components/ui/card.tsx +++ b/apps/ui/src/components/ui/card.tsx @@ -11,9 +11,9 @@ function Card({ className, gradient = false, ...props }: CardProps) {
( className={cn( 'fixed top-[50%] left-[50%] z-50 translate-x-[-50%] translate-y-[-50%]', 'flex flex-col w-full max-w-[calc(100%-2rem)] max-h-[calc(100vh-4rem)]', - 'bg-card border border-border rounded-xl shadow-2xl', + 'bg-card/90 border border-white/10 rounded-2xl shadow-2xl backdrop-blur-xl', // Premium shadow - 'shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)]', + 'shadow-[0_40px_80px_-12px_rgba(0,0,0,0.5)]', // Animations - smoother with scale 'data-[state=open]:animate-in data-[state=closed]:animate-out', 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', 'data-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%]', - 'duration-200', + 'duration-300 ease-out', compact ? 'max-w-4xl p-4' : !hasCustomMaxWidth ? 'sm:max-w-2xl p-6' : 'p-6', className )} diff --git a/apps/ui/src/components/ui/dropdown-menu.tsx b/apps/ui/src/components/ui/dropdown-menu.tsx index ad1cd836..3309d409 100644 --- a/apps/ui/src/components/ui/dropdown-menu.tsx +++ b/apps/ui/src/components/ui/dropdown-menu.tsx @@ -157,7 +157,8 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + 'z-50 min-w-[8rem] overflow-hidden rounded-lg border border-white/10 bg-popover/80 p-1 text-popover-foreground shadow-xl backdrop-blur-xl', + 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className )} {...props} diff --git a/apps/ui/src/components/ui/input.tsx b/apps/ui/src/components/ui/input.tsx index a757fa17..c6a106f4 100644 --- a/apps/ui/src/components/ui/input.tsx +++ b/apps/ui/src/components/ui/input.tsx @@ -15,17 +15,21 @@ function Input({ className, type, startAddon, endAddon, ...props }: InputProps) type={type} data-slot="input" className={cn( - 'file:text-foreground placeholder:text-muted-foreground/60 selection:bg-primary selection:text-primary-foreground bg-input border-border h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm', - // Inner shadow for depth - 'shadow-[inset_0_1px_2px_rgba(0,0,0,0.05)]', - // Animated focus ring - 'transition-[color,box-shadow,border-color] duration-200 ease-out', - 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]', + 'file:text-foreground placeholder:text-muted-foreground/50 selection:bg-cyan-500/30 selection:text-cyan-100', + 'bg-white/5 border-white/10 h-9 w-full min-w-0 rounded-xl border px-3 py-1 text-sm shadow-sm outline-none transition-all duration-200', + 'file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium', + 'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50', + 'backdrop-blur-sm', + // Hover state + 'hover:bg-white/10 hover:border-white/20', + // Focus state with ring + 'focus:bg-white/10 focus:border-cyan-500/50', + 'focus-visible:border-cyan-500/50 focus-visible:ring-cyan-500/20 focus-visible:ring-[4px]', 'aria-invalid:ring-destructive/20 aria-invalid:border-destructive', // Adjust padding for addons startAddon && 'pl-0', endAddon && 'pr-0', - hasAddons && 'border-0 shadow-none focus-visible:ring-0', + hasAddons && 'border-0 shadow-none focus-visible:ring-0 bg-transparent', className )} {...props} @@ -39,10 +43,10 @@ function Input({ className, type, startAddon, endAddon, ...props }: InputProps) return (
(({ className, ...p className={cn('relative flex w-full touch-none select-none items-center', className)} {...props} > - - + + - + )); Slider.displayName = SliderPrimitive.Root.displayName; diff --git a/apps/ui/src/components/ui/switch.tsx b/apps/ui/src/components/ui/switch.tsx index 47b2bc51..eaceebe8 100644 --- a/apps/ui/src/components/ui/switch.tsx +++ b/apps/ui/src/components/ui/switch.tsx @@ -11,7 +11,7 @@ const Switch = React.forwardRef< >(({ className, ...props }, ref) => ( diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 0038b6d3..9c0ec6ad 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -16,6 +16,7 @@ import { RefreshCw } from 'lucide-react'; import { useAutoMode } from '@/hooks/use-auto-mode'; import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts'; import { useWindowState } from '@/hooks/use-window-state'; +import { PageShell } from '@/components/layout/page-shell'; // Board-view specific imports import { BoardHeader } from './board-view/board-header'; import { BoardSearchBar } from './board-view/board-search-bar'; diff --git a/apps/ui/src/components/views/board-view/board-header.tsx b/apps/ui/src/components/views/board-view/board-header.tsx index 0dae58d3..cb7b88fa 100644 --- a/apps/ui/src/components/views/board-view/board-header.tsx +++ b/apps/ui/src/components/views/board-view/board-header.tsx @@ -1,4 +1,5 @@ import { HotkeyButton } from '@/components/ui/hotkey-button'; +import { cn } from '@/lib/utils'; import { Slider } from '@/components/ui/slider'; import { Switch } from '@/components/ui/switch'; import { Label } from '@/components/ui/label'; @@ -39,23 +40,20 @@ export function BoardHeader({ const showUsageTracking = !apiKeys.anthropic && !isWindows; return ( -
+
-

Kanban Board

-

{projectName}

+

Kanban Board

+

+ {projectName} +

-
- {/* Usage Popover - only show for CLI users (not API key users) */} - {isMounted && showUsageTracking && } - {/* Concurrency Slider - only show after mount to prevent hydration issues */} +
+ {/* Concurrency/Agent Control - Styled as Toggle for visual matching, but keeps slider logic if needed or simplified */} {isMounted && ( -
- - Agents +
+ + {/* We keep the slider for functionality, but could style it to look like the toggle or just use the slider cleanly */} onConcurrencyChange(value[0])} @@ -63,43 +61,43 @@ export function BoardHeader({ max={10} step={1} className="w-20" - data-testid="concurrency-slider" /> - + {runningAgentsCount} / {maxConcurrency}
)} - {/* Auto Mode Toggle - only show after mount to prevent hydration issues */} + {/* Auto Mode Button */} {isMounted && ( -
- - onAutoModeToggle(!isAutoModeRunning)} + className={cn( + 'flex items-center gap-2 px-5 py-2 rounded-xl text-xs font-bold transition', + isAutoModeRunning + ? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/20' + : 'glass hover:bg-white/10' + )} + > +
-
+ Auto Mode + )} - - - Add Feature - + + ADD FEATURE +
-
+
); } diff --git a/apps/ui/src/components/views/board-view/components/kanban-column.tsx b/apps/ui/src/components/views/board-view/components/kanban-column.tsx index 4e08cfba..2010f349 100644 --- a/apps/ui/src/components/views/board-view/components/kanban-column.tsx +++ b/apps/ui/src/components/views/board-view/components/kanban-column.tsx @@ -7,6 +7,7 @@ interface KanbanColumnProps { id: string; title: string; colorClass: string; + columnClass?: string; count: number; children: ReactNode; headerAction?: ReactNode; @@ -21,6 +22,7 @@ export const KanbanColumn = memo(function KanbanColumn({ id, title, colorClass, + columnClass, count, children, headerAction, @@ -43,7 +45,8 @@ export const KanbanColumn = memo(function KanbanColumn({ 'transition-[box-shadow,ring] duration-200', !width && 'w-72', // Only apply w-72 if no custom width showBorder && 'border border-border/60', - isOver && 'ring-2 ring-primary/30 ring-offset-1 ring-offset-background' + isOver && 'ring-2 ring-primary/30 ring-offset-1 ring-offset-background', + columnClass )} style={widthStyle} data-testid={`kanban-column-${id}`} diff --git a/apps/ui/src/components/views/board-view/constants.ts b/apps/ui/src/components/views/board-view/constants.ts index ae239d94..9dc3fb56 100644 --- a/apps/ui/src/components/views/board-view/constants.ts +++ b/apps/ui/src/components/views/board-view/constants.ts @@ -2,21 +2,25 @@ import { Feature } from '@/store/app-store'; export type ColumnId = Feature['status']; -export const COLUMNS: { id: ColumnId; title: string; colorClass: string }[] = [ - { id: 'backlog', title: 'Backlog', colorClass: 'bg-[var(--status-backlog)]' }, - { - id: 'in_progress', - title: 'In Progress', - colorClass: 'bg-[var(--status-in-progress)]', - }, - { - id: 'waiting_approval', - title: 'Waiting Approval', - colorClass: 'bg-[var(--status-waiting)]', - }, - { - id: 'verified', - title: 'Verified', - colorClass: 'bg-[var(--status-success)]', - }, -]; +export const COLUMNS: { id: ColumnId; title: string; colorClass: string; columnClass?: string }[] = + [ + { id: 'backlog', title: 'Backlog', colorClass: 'bg-white/20', columnClass: '' }, + { + id: 'in_progress', + title: 'In Progress', + colorClass: 'bg-cyan-400', + columnClass: 'col-in-progress', + }, + { + id: 'waiting_approval', + title: 'Waiting Approval', + colorClass: 'bg-amber-500', + columnClass: 'col-waiting', + }, + { + id: 'verified', + title: 'Verified', + colorClass: 'bg-emerald-500', + columnClass: 'col-verified', + }, + ]; diff --git a/apps/ui/src/components/views/board-view/kanban-board.tsx b/apps/ui/src/components/views/board-view/kanban-board.tsx index 65cd00ea..e93e9052 100644 --- a/apps/ui/src/components/views/board-view/kanban-board.tsx +++ b/apps/ui/src/components/views/board-view/kanban-board.tsx @@ -102,6 +102,7 @@ export function KanbanBoard({ id={column.id} title={column.title} colorClass={column.colorClass} + columnClass={column.columnClass} count={columnFeatures.length} width={columnWidth} opacity={backgroundSettings.columnOpacity} diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index 0580c18e..b8ebd051 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useAppStore } from '@/store/app-store'; +import { PageShell } from '@/components/layout/page-shell'; import { useCliStatus, useSettingsView } from './settings-view/hooks'; import { NAV_ITEMS } from './settings-view/config/navigation'; @@ -156,36 +157,38 @@ export function SettingsView() { }; return ( -
- {/* Header Section */} - + +
+ {/* Header Section */} + - {/* Content Area with Sidebar */} -
- {/* Side Navigation - No longer scrolls, just switches views */} - + {/* Content Area with Sidebar */} +
+ {/* Side Navigation - No longer scrolls, just switches views */} + - {/* Content Panel - Shows only the active section */} -
-
{renderActiveSection()}
+ {/* Content Panel - Shows only the active section */} +
+
{renderActiveSection()}
+
+ + {/* Keyboard Map Dialog */} + + + {/* Delete Project Confirmation Dialog */} +
- - {/* Keyboard Map Dialog */} - - - {/* Delete Project Confirmation Dialog */} - -
+ ); } diff --git a/apps/ui/src/components/views/settings-view/components/settings-header.tsx b/apps/ui/src/components/views/settings-view/components/settings-header.tsx index e99159ba..fe5c876f 100644 --- a/apps/ui/src/components/views/settings-view/components/settings-header.tsx +++ b/apps/ui/src/components/views/settings-view/components/settings-header.tsx @@ -11,13 +11,7 @@ export function SettingsHeader({ description = 'Configure your API keys and preferences', }: SettingsHeaderProps) { return ( -
+
+
+
diff --git a/apps/ui/src/components/views/terminal-view.tsx b/apps/ui/src/components/views/terminal-view.tsx index db79ce0f..67ed2a43 100644 --- a/apps/ui/src/components/views/terminal-view.tsx +++ b/apps/ui/src/components/views/terminal-view.tsx @@ -46,6 +46,7 @@ import { defaultDropAnimationSideEffects, } from '@dnd-kit/core'; import { cn } from '@/lib/utils'; +import { PageShell } from '@/components/layout/page-shell'; interface TerminalStatus { enabled: boolean; @@ -141,11 +142,11 @@ function TerminalTabButton({ {...dragAttributes} {...dragListeners} className={cn( - 'flex items-center gap-1 px-3 py-1.5 text-sm rounded-t-md border-b-2 cursor-grab active:cursor-grabbing transition-colors select-none', + 'flex items-center gap-1 px-3 py-1.5 text-sm rounded-t-md border-b-2 cursor-grab active:cursor-grabbing transition-all select-none', isActive - ? 'bg-background border-brand-500 text-foreground' - : 'bg-muted border-transparent text-muted-foreground hover:text-foreground hover:bg-accent', - isOver && isDropTarget && isDraggingTab && 'ring-2 ring-blue-500 bg-blue-500/10', + ? 'bg-white/10 border-cyan-500 text-cyan-100 shadow-[0_-1px_10px_rgba(6,182,212,0.1)]' + : 'bg-white/5 border-transparent text-muted-foreground hover:text-cyan-50 hover:bg-white/10', + isOver && isDropTarget && isDraggingTab && 'ring-2 ring-cyan-500/50 bg-cyan-500/10', isDragging && 'opacity-50' )} onClick={onClick} @@ -192,8 +193,8 @@ function NewTabDropZone({ isDropTarget }: { isDropTarget: boolean }) { className={cn( 'flex items-center justify-center px-3 py-1.5 rounded-t-md border-2 border-dashed transition-all', isOver && isDropTarget - ? 'border-green-500 bg-green-500/10 text-green-500' - : 'border-transparent text-muted-foreground hover:border-border' + ? 'border-cyan-500/50 bg-cyan-500/10 text-cyan-400' + : 'border-transparent text-muted-foreground hover:border-white/10' )} > @@ -1414,252 +1415,256 @@ export function TerminalView() { // Terminal view with tabs return ( - -
- {/* Tab bar */} -
- {/* Tabs */} -
- {terminalState.tabs.map((tab) => ( - setActiveTerminalTab(tab.id)} - onClose={() => killTerminalTab(tab.id)} - onRename={(newName) => renameTerminalTab(tab.id, newName)} - isDropTarget={activeDragId !== null || activeDragTabId !== null} - isDraggingTab={activeDragTabId !== null} - /> - ))} - - {(activeDragId || activeDragTabId) && } - - {/* New tab button */} - -
- - {/* Toolbar buttons */} -
- - - - {/* Global Terminal Settings */} - - - - - -
-
-

Terminal Settings

-

- Configure global terminal appearance -

-
- - {/* Default Font Size */} -
-
- - - {terminalState.defaultFontSize}px - -
- setTerminalDefaultFontSize(value)} - onValueCommit={() => { - toast.info('Font size changed', { - description: 'New terminals will use this size', - }); - }} - /> -
- - {/* Font Family */} -
- - -
- - {/* Line Height */} -
-
- - - {terminalState.lineHeight.toFixed(1)} - -
- setTerminalLineHeight(value)} - onValueCommit={() => { - toast.info('Line height changed', { - description: 'Restart terminal for changes to take effect', - }); - }} - /> -
- - {/* Default Run Script */} -
- - setTerminalDefaultRunScript(e.target.value)} - placeholder="e.g., claude, npm run dev" - className="h-8 text-sm" - /> -

- Command to run when opening new terminals -

-
-
-
-
-
-
- - {/* Active tab content */} -
- {terminalState.maximizedSessionId ? ( - // When a terminal is maximized, render only that terminal - { - const sessionId = terminalState.maximizedSessionId!; - toggleTerminalMaximized(sessionId); - killTerminal(sessionId); - createTerminal(); - }} - > - setActiveTerminalSession(terminalState.maximizedSessionId!)} - onClose={() => killTerminal(terminalState.maximizedSessionId!)} - onSplitHorizontal={() => - createTerminal('horizontal', terminalState.maximizedSessionId!) - } - onSplitVertical={() => - createTerminal('vertical', terminalState.maximizedSessionId!) - } - onNewTab={createTerminalInNewTab} - onSessionInvalid={() => { - const sessionId = terminalState.maximizedSessionId!; - console.log( - `[Terminal] Maximized session ${sessionId} is invalid, removing from layout` - ); - killTerminal(sessionId); - }} - isDragging={false} - isDropTarget={false} - fontSize={findTerminalFontSize(terminalState.maximizedSessionId)} - onFontSizeChange={(size) => - setTerminalPanelFontSize(terminalState.maximizedSessionId!, size) - } - isMaximized={true} - onToggleMaximize={() => toggleTerminalMaximized(terminalState.maximizedSessionId!)} - /> - - ) : activeTab?.layout ? ( - renderPanelContent(activeTab.layout) - ) : ( -
-

This tab is empty

- -
- )} -
-
- - {/* Drag overlay */} - + - {activeDragId ? ( -
- - - {dragOverTabId === 'new' ? 'New tab' : dragOverTabId ? 'Move to tab' : 'Terminal'} - +
+ {/* Tab bar */} +
+ {/* Tabs */} +
+ {terminalState.tabs.map((tab) => ( + setActiveTerminalTab(tab.id)} + onClose={() => killTerminalTab(tab.id)} + onRename={(newName) => renameTerminalTab(tab.id, newName)} + isDropTarget={activeDragId !== null || activeDragTabId !== null} + isDraggingTab={activeDragTabId !== null} + /> + ))} + + {(activeDragId || activeDragTabId) && } + + {/* New tab button */} + +
+ + {/* Toolbar buttons */} +
+ + + + {/* Global Terminal Settings */} + + + + + +
+
+

Terminal Settings

+

+ Configure global terminal appearance +

+
+ + {/* Default Font Size */} +
+
+ + + {terminalState.defaultFontSize}px + +
+ setTerminalDefaultFontSize(value)} + onValueCommit={() => { + toast.info('Font size changed', { + description: 'New terminals will use this size', + }); + }} + /> +
+ + {/* Font Family */} +
+ + +
+ + {/* Line Height */} +
+
+ + + {terminalState.lineHeight.toFixed(1)} + +
+ setTerminalLineHeight(value)} + onValueCommit={() => { + toast.info('Line height changed', { + description: 'Restart terminal for changes to take effect', + }); + }} + /> +
+ + {/* Default Run Script */} +
+ + setTerminalDefaultRunScript(e.target.value)} + placeholder="e.g., claude, npm run dev" + className="h-8 text-sm" + /> +

+ Command to run when opening new terminals +

+
+
+
+
+
- ) : null} - - + + {/* Active tab content */} +
+ {terminalState.maximizedSessionId ? ( + // When a terminal is maximized, render only that terminal + { + const sessionId = terminalState.maximizedSessionId!; + toggleTerminalMaximized(sessionId); + killTerminal(sessionId); + createTerminal(); + }} + > + setActiveTerminalSession(terminalState.maximizedSessionId!)} + onClose={() => killTerminal(terminalState.maximizedSessionId!)} + onSplitHorizontal={() => + createTerminal('horizontal', terminalState.maximizedSessionId!) + } + onSplitVertical={() => + createTerminal('vertical', terminalState.maximizedSessionId!) + } + onNewTab={createTerminalInNewTab} + onSessionInvalid={() => { + const sessionId = terminalState.maximizedSessionId!; + console.log( + `[Terminal] Maximized session ${sessionId} is invalid, removing from layout` + ); + killTerminal(sessionId); + }} + isDragging={false} + isDropTarget={false} + fontSize={findTerminalFontSize(terminalState.maximizedSessionId)} + onFontSizeChange={(size) => + setTerminalPanelFontSize(terminalState.maximizedSessionId!, size) + } + isMaximized={true} + onToggleMaximize={() => + toggleTerminalMaximized(terminalState.maximizedSessionId!) + } + /> + + ) : activeTab?.layout ? ( + renderPanelContent(activeTab.layout) + ) : ( +
+

This tab is empty

+ +
+ )} +
+
+ + {/* Drag overlay */} + + {activeDragId ? ( +
+ + + {dragOverTabId === 'new' ? 'New tab' : dragOverTabId ? 'Move to tab' : 'Terminal'} + +
+ ) : null} +
+ + ); } diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 80ad7019..b1e86bae 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -4,14 +4,12 @@ import type { Project, TrashedProject } from '@/lib/electron'; import type { Feature as BaseFeature, FeatureImagePath, + FeatureTextFilePath, // Import missing type AgentModel, PlanningMode, AIProfile, } from '@automaker/types'; -// Re-export ThemeMode for convenience -export type { ThemeMode }; - export type ViewMode = | 'welcome' | 'setup' @@ -43,7 +41,25 @@ export type ThemeMode = | 'red' | 'cream' | 'sunset' - | 'gray'; + | 'gray' + | 'forest' + | 'ocean' + | 'light' + | 'cream' + | 'solarizedlight' + | 'github' + | 'paper' + | 'rose' + | 'mint' + | 'lavender' + | 'sand' + | 'sky' + | 'peach' + | 'snow' + | 'sepia' + | 'gruvboxlight' + | 'nordlight' + | 'blossom'; export type KanbanCardDetailLevel = 'minimal' | 'standard' | 'detailed'; @@ -903,19 +919,19 @@ const initialState: AppState = { chatHistoryOpen: false, autoModeByProject: {}, autoModeActivityLog: [], - maxConcurrency: 3, // Default to 3 concurrent agents - kanbanCardDetailLevel: 'standard', // Default to standard detail level - boardViewMode: 'kanban', // Default to kanban view - defaultSkipTests: true, // Default to manual verification (tests disabled) - enableDependencyBlocking: true, // Default to enabled (show dependency blocking UI) - useWorktrees: false, // Default to disabled (worktree feature is experimental) + maxConcurrency: 3, + kanbanCardDetailLevel: 'standard', + boardViewMode: 'kanban', + defaultSkipTests: false, + enableDependencyBlocking: true, + useWorktrees: false, currentWorktreeByProject: {}, worktreesByProject: {}, - showProfilesOnly: false, // Default to showing all options (not profiles only) - keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, // Default keyboard shortcuts - muteDoneSound: false, // Default to sound enabled (not muted) - enhancementModel: 'sonnet', // Default to sonnet for feature enhancement aiProfiles: DEFAULT_AI_PROFILES, + showProfilesOnly: false, + keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, + muteDoneSound: false, + enhancementModel: 'sonnet', projectAnalysis: null, isAnalyzing: false, boardBackgroundByProject: {}, @@ -928,19 +944,23 @@ const initialState: AppState = { activeSessionId: null, maximizedSessionId: null, defaultFontSize: 14, - defaultRunScript: '', + defaultRunScript: '', // Empty string = standard shell screenReaderMode: false, - fontFamily: "Menlo, Monaco, 'Courier New', monospace", - scrollbackLines: 5000, - lineHeight: 1.0, - maxSessions: 100, + fontFamily: 'monospace', + scrollbackLines: 1000, + lineHeight: 1.2, + maxSessions: 20, }, terminalLayoutByProject: {}, specCreatingForProject: null, - defaultPlanningMode: 'skip' as PlanningMode, - defaultRequirePlanApproval: false, + defaultPlanningMode: 'lite', + defaultRequirePlanApproval: true, defaultAIProfileId: null, pendingPlanApproval: null, + // Claude Usage Defaults + claudeRefreshInterval: 60, + claudeUsage: null, + claudeUsageLastUpdated: null, }; export const useAppStore = create()( diff --git a/apps/ui/src/styles/global.css b/apps/ui/src/styles/global.css index 351007db..b927916b 100644 --- a/apps/ui/src/styles/global.css +++ b/apps/ui/src/styles/global.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); @import 'tailwindcss'; @import 'tw-animate-css'; @@ -43,8 +44,8 @@ --color-foreground: var(--foreground); --color-foreground-secondary: var(--foreground-secondary); --color-foreground-muted: var(--foreground-muted); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace; /* Sidebar colors */ --color-sidebar-ring: var(--sidebar-ring); @@ -119,29 +120,94 @@ --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); + --radius-2xl: calc(var(--radius) + 8px); + + /* Animations */ + --animate-in-fade: in-fade 0.3s cubic-bezier(0.16, 1, 0.3, 1); + --animate-in-scale: in-scale 0.3s cubic-bezier(0.16, 1, 0.3, 1); + --animate-out-fade: out-fade 0.2s cubic-bezier(0.16, 1, 0.3, 1); + --animate-out-scale: out-scale 0.2s cubic-bezier(0.16, 1, 0.3, 1); + + @keyframes in-fade { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes in-scale { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } + } + + @keyframes out-fade { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(8px); + } + } + + @keyframes out-scale { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.96); + } + } } :root { - /* Default to light mode */ - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); + /* Default to light mode (overridden by Prism values below for now as we pivot) */ + --radius: 0.75rem; + + /* PRISM THEME VALUES (Base) */ + --bg-deep: #0b101a; + --sidebar-bg: rgba(13, 17, 26, 0.7); + --glass-bg: rgba(255, 255, 255, 0.03); + --glass-border: rgba(255, 255, 255, 0.07); + + /* Accents */ + --accent-cyan: #22d3ee; + --accent-orange: #f59e0b; + --accent-green: #10b981; + --accent-red: #ef4444; + + /* Mapping to existing variables where possible, or overriding for the visual appearance */ + --background: oklch(0.08 0.03 260); /* Approximation of #0b101a */ + --foreground: oklch(0.95 0 0); + --card: oklch(1 0 0 / 0.03); /* Glass card default */ + --card-foreground: oklch(0.95 0 0); + --popover: oklch(0.08 0.03 260 / 0.9); + --popover-foreground: oklch(0.95 0 0); + --primary: oklch(0.78 0.15 200); /* Cyan-ish */ + --primary-foreground: oklch(0.1 0.03 260); + --secondary: oklch(1 0 0 / 0.1); + --secondary-foreground: oklch(0.98 0 0); + --muted: oklch(1 0 0 / 0.05); + --muted-foreground: oklch(0.7 0 0); + --accent: oklch(0.78 0.15 200); + --accent-foreground: oklch(0.1 0.03 260); + --destructive: oklch(0.6 0.2 20); + --border: oklch(1 0 0 / 0.1); + --input: oklch(1 0 0 / 0.05); + --ring: oklch(0.78 0.15 200); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -159,7 +225,7 @@ --background-80: oklch(1 0 0 / 0.8); --foreground-secondary: oklch(0.4 0 0); --foreground-muted: oklch(0.556 0 0); - --border-glass: oklch(0.145 0 0 / 0.1); + --border-glass: oklch(0.145 0 0 / 0.08); --brand-400: oklch(0.6 0.22 265); --brand-500: oklch(0.55 0.25 265); --brand-600: oklch(0.5 0.28 270); @@ -191,17 +257,17 @@ --status-in-progress: oklch(0.7 0.15 70); --status-waiting: oklch(0.65 0.18 50); - /* Shadow tokens */ + /* Shadow tokens - more diffused for modern feel */ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05); - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08), 0 4px 10px rgba(0, 0, 0, 0.02); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.08), 0 12px 24px -4px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.08), 0 20px 40px -8px rgba(0, 0, 0, 0.06); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.08), 0 30px 60px -12px rgba(0, 0, 0, 0.1); /* Transition tokens */ - --transition-fast: 150ms ease; - --transition-normal: 200ms ease; - --transition-slow: 300ms ease-out; + --transition-fast: 150ms cubic-bezier(0.16, 1, 0.3, 1); + --transition-normal: 250ms cubic-bezier(0.16, 1, 0.3, 1); + --transition-slow: 400ms cubic-bezier(0.16, 1, 0.3, 1); } /* Apply dark mode immediately based on system preference (before JS runs) */ @@ -214,13 +280,13 @@ /* Text colors following hierarchy */ --foreground: oklch(1 0 0); /* text-white */ - --foreground-secondary: oklch(0.588 0 0); /* text-zinc-400 */ - --foreground-muted: oklch(0.525 0 0); /* text-zinc-500 */ + --foreground-secondary: oklch(0.65 0 0); /* lighter for better readability */ + --foreground-muted: oklch(0.525 0 0); /* Card and popover backgrounds */ - --card: oklch(0.14 0 0); + --card: oklch(0.1 0 0 / 0.6); /* Slightly transparent */ --card-foreground: oklch(1 0 0); - --popover: oklch(0.1 0 0); + --popover: oklch(0.08 0 0 / 0.9); --popover-foreground: oklch(1 0 0); /* Brand colors - purple/violet theme */ @@ -231,18 +297,18 @@ --brand-600: oklch(0.5 0.28 270); /* Glass morphism borders and accents */ - --secondary: oklch(1 0 0 / 0.05); + --secondary: oklch(1 0 0 / 0.08); --secondary-foreground: oklch(1 0 0); --muted: oklch(0.176 0 0); - --muted-foreground: oklch(0.588 0 0); - --accent: oklch(1 0 0 / 0.1); + --muted-foreground: oklch(0.6 0 0); + --accent: oklch(1 0 0 / 0.12); --accent-foreground: oklch(1 0 0); /* Borders with transparency for glass effect */ - --border: oklch(0.176 0 0); - --border-glass: oklch(1 0 0 / 0.1); + --border: oklch(1 0 0 / 0.08); + --border-glass: oklch(1 0 0 / 0.12); --destructive: oklch(0.6 0.25 25); - --input: oklch(0.04 0 0 / 0.8); + --input: oklch(1 0 0 / 0.08); --ring: oklch(0.55 0.25 265); /* Chart colors with brand theme */ @@ -253,13 +319,13 @@ --chart-5: oklch(0.6 0.25 20); /* Sidebar with glass morphism */ - --sidebar: oklch(0.04 0 0 / 0.5); + --sidebar: oklch(0.04 0 0 / 0.6); --sidebar-foreground: oklch(1 0 0); --sidebar-primary: oklch(0.55 0.25 265); --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(1 0 0 / 0.05); + --sidebar-accent: oklch(1 0 0 / 0.08); --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(1 0 0 / 0.1); + --sidebar-border: oklch(1 0 0 / 0.08); --sidebar-ring: oklch(0.55 0.25 265); /* Action button colors */ @@ -292,67 +358,21 @@ /* Shadow tokens - darker for dark mode */ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -2px rgba(0, 0, 0, 0.4); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.7), 0 10px 10px -5px rgba(0, 0, 0, 0.5); } } .light { - /* Explicit light mode - same as root but ensures it overrides any dark defaults */ + /* Explicit light mode overrides */ --background: oklch(1 0 0); /* White */ - --background-50: oklch(1 0 0 / 0.5); - --background-80: oklch(1 0 0 / 0.8); - --foreground: oklch(0.145 0 0); /* Dark text */ - --foreground-secondary: oklch(0.4 0 0); - --foreground-muted: oklch(0.556 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.55 0.25 265); - --primary-foreground: oklch(1 0 0); - --brand-400: oklch(0.6 0.22 265); - --brand-500: oklch(0.55 0.25 265); - --brand-600: oklch(0.5 0.28 270); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.95 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --border-glass: oklch(0.145 0 0 / 0.1); - --input: oklch(1 0 0); - --ring: oklch(0.55 0.25 265); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.98 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.55 0.25 265); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.95 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.9 0 0); - --sidebar-ring: oklch(0.55 0.25 265); - - /* Action button colors */ - --action-view: oklch(0.55 0.25 265); /* Purple */ - --action-view-hover: oklch(0.5 0.28 270); - --action-followup: oklch(0.55 0.2 230); /* Blue */ - --action-followup-hover: oklch(0.5 0.22 230); - --action-commit: oklch(0.55 0.2 140); /* Green */ - --action-commit-hover: oklch(0.5 0.22 140); - --action-verify: oklch(0.55 0.2 140); /* Green */ - --action-verify-hover: oklch(0.5 0.22 140); - - /* Running indicator - Purple */ - --running-indicator: oklch(0.55 0.25 265); - --running-indicator-text: oklch(0.6 0.22 265); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + /* Re-declare all light vars to ensure priority if needed, + but :root typically handles this. + Just ensuring important overrides. */ + --card: oklch(1 0 0 / 0.8); } @layer base { @@ -360,10 +380,13 @@ @apply border-border outline-ring/50; } html { - @apply bg-background; + @apply bg-background antialiased; + font-feature-settings: + 'rlig' 1, + 'calt' 1; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground tracking-tight; background-color: var(--background); } @@ -386,6 +409,7 @@ select:disabled, textarea:disabled { cursor: not-allowed; + opacity: 0.5; } } @@ -407,8 +431,8 @@ .gray ) ::-webkit-scrollbar { - width: 8px; - height: 8px; + width: 6px; + height: 6px; } :is( @@ -428,110 +452,35 @@ .gray ) ::-webkit-scrollbar-track { - background: var(--muted); + background: transparent; } :is(.dark, .retro) ::-webkit-scrollbar-thumb { - background: oklch(0.3 0 0); - border-radius: 4px; + background: oklch(1 0 0 / 0.2); + border-radius: 99px; + transition: background 0.2s; } :is(.dark, .retro) ::-webkit-scrollbar-thumb:hover { - background: oklch(0.4 0 0); -} - -/* Retro Scrollbar override */ -.retro ::-webkit-scrollbar-thumb { - background: var(--primary); - border-radius: 0; -} -.retro ::-webkit-scrollbar-track { - background: var(--background); -} - -/* Red theme scrollbar */ -.red ::-webkit-scrollbar-thumb { - background: oklch(0.35 0.15 25); - border-radius: 4px; -} - -.red ::-webkit-scrollbar-thumb:hover { - background: oklch(0.45 0.18 25); -} - -.red ::-webkit-scrollbar-track { - background: oklch(0.15 0.05 25); -} - -/* Always visible scrollbar for file diffs and code blocks */ -.scrollbar-visible { - overflow-y: auto !important; - scrollbar-width: thin; - scrollbar-color: var(--muted-foreground) var(--muted); -} - -.scrollbar-visible::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -.scrollbar-visible::-webkit-scrollbar-track { - background: var(--muted); - border-radius: 4px; -} - -.scrollbar-visible::-webkit-scrollbar-thumb { - background: var(--muted-foreground); - border-radius: 4px; - min-height: 40px; -} - -.scrollbar-visible::-webkit-scrollbar-thumb:hover { - background: var(--foreground-secondary); -} - -/* Force scrollbar to always be visible (not auto-hide) */ -.scrollbar-visible::-webkit-scrollbar-thumb { - visibility: visible; -} - -/* Styled scrollbar for code blocks and log entries (horizontal/vertical) */ -.scrollbar-styled { - scrollbar-width: thin; - scrollbar-color: var(--muted-foreground) transparent; -} - -.scrollbar-styled::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -.scrollbar-styled::-webkit-scrollbar-track { - background: transparent; - border-radius: 3px; -} - -.scrollbar-styled::-webkit-scrollbar-thumb { - background: oklch(0.35 0 0); - border-radius: 3px; -} - -.scrollbar-styled::-webkit-scrollbar-thumb:hover { - background: oklch(0.45 0 0); + background: oklch(1 0 0 / 0.3); } /* Glass morphism utilities */ @layer utilities { .glass { - @apply backdrop-blur-md border-white/10; + @apply backdrop-blur-xl border border-white/10 bg-white/5; } .glass-subtle { - @apply backdrop-blur-sm border-white/5; + @apply backdrop-blur-md border border-white/5 bg-white/3; } .glass-strong { - @apply backdrop-blur-xl border-white/20; + @apply backdrop-blur-2xl border border-white/10 bg-black/40; + } + + .glass-panel { + @apply backdrop-blur-3xl border border-border-glass bg-background/60 shadow-xl; } /* Text color hierarchy utilities */ @@ -540,55 +489,73 @@ } .text-secondary { - color: oklch(0.588 0 0); /* zinc-400 equivalent */ + color: var(--foreground-secondary); } .text-muted { - color: oklch(0.525 0 0); /* zinc-500 equivalent */ + color: var(--foreground-muted); } /* Brand gradient utilities */ .gradient-brand { - background: linear-gradient(135deg, oklch(0.55 0.25 265), oklch(0.5 0.28 270)); + background: linear-gradient(135deg, var(--brand-500), var(--brand-600)); } .gradient-brand-subtle { - background: linear-gradient(135deg, oklch(0.55 0.25 265 / 0.1), oklch(0.5 0.28 270 / 0.1)); + background: linear-gradient( + 135deg, + color-mix(in oklch, var(--brand-500), transparent 90%), + color-mix(in oklch, var(--brand-600), transparent 90%) + ); } - /* Glass morphism background utilities */ - .bg-glass { - background: var(--card); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - } - - .bg-glass-80 { - background: var(--popover); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + .gradient-glass-border { + background: linear-gradient( + to bottom right, + rgba(255, 255, 255, 0.2), + rgba(255, 255, 255, 0.05) + ); } /* Hover state utilities */ .hover-glass { - transition: background-color 0.2s ease; + transition: + background-color 0.2s ease, + transform 0.2s ease; } .hover-glass:hover { - background: oklch(1 0 0 / 0.05); + background: color-mix(in oklch, var(--foreground), transparent 95%); } - .hover-glass-strong { - transition: background-color 0.2s ease; + .hover-lift { + transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1); } - .hover-glass-strong:hover { - background: oklch(1 0 0 / 0.1); + .hover-lift:hover { + transform: translateY(-2px); + } + + .hover-glow { + transition: box-shadow 0.3s ease; + } + + .hover-glow:hover { + box-shadow: 0 0 20px -5px var(--primary); } /* Content area background */ .content-bg { - background: var(--background); + background: transparent; + } + + /* Utils for hiding scrollbar but allowing scroll */ + .no-scrollbar::-webkit-scrollbar { + display: none; + } + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; } /* Action button utilities */ @@ -644,7 +611,6 @@ } /* Animated border for in-progress cards */ - /* Using a subtle pulse animation instead of continuous gradient rotation for GPU efficiency */ @keyframes border-pulse { 0%, 100% { @@ -657,8 +623,8 @@ .animated-border-wrapper { position: relative; - border-radius: 0.75rem; - padding: 2px; + border-radius: var(--radius); + padding: 1px; background: linear-gradient( 135deg, var(--running-indicator), @@ -677,7 +643,7 @@ } .animated-border-wrapper > * { - border-radius: calc(0.75rem - 2px); + border-radius: calc(var(--radius) - 1px); } } @@ -685,6 +651,7 @@ .retro * { border-radius: 0 !important; + --radius: 0; } /* Animated Outline Button Styles */ @@ -693,12 +660,6 @@ background: conic-gradient(from 90deg at 50% 50%, #a855f7 0%, #3b82f6 50%, #a855f7 100%); } -/* Light mode - deeper purple to blue gradient for better visibility */ - -/* Dark mode - purple to blue gradient */ - -/* Retro mode - unique scanline + neon effect */ - @keyframes retro-glow { from { filter: brightness(1) drop-shadow(0 0 2px #00ff41); @@ -708,24 +669,6 @@ } } -/* Dracula animated-outline - purple/pink */ - -/* Nord animated-outline - frost blue */ - -/* Monokai animated-outline - pink/yellow */ - -/* Tokyo Night animated-outline - blue/magenta */ - -/* Solarized animated-outline - blue/cyan */ - -/* Gruvbox animated-outline - yellow/orange */ - -/* Catppuccin animated-outline - mauve/pink */ - -/* One Dark animated-outline - blue/magenta */ - -/* Synthwave animated-outline - hot pink/cyan with glow */ - @keyframes synthwave-glow { from { filter: brightness(1) drop-shadow(0 0 3px #f97e72); @@ -735,26 +678,6 @@ } } -/* Slider Theme Styles */ - -/* Dracula slider */ - -/* Nord slider */ - -/* Monokai slider */ - -/* Tokyo Night slider */ - -/* Solarized slider */ - -/* Gruvbox slider */ - -/* Catppuccin slider */ - -/* One Dark slider */ - -/* Synthwave slider */ - /* Line clamp utilities for text overflow prevention */ .line-clamp-2 { display: -webkit-box; @@ -802,30 +725,6 @@ Theme-aware colors for XML editor ======================================== */ -/* Light theme - professional and readable */ - -/* Dark theme - high contrast */ - -/* Retro theme - neon green on black */ - -/* Dracula theme */ - -/* Nord theme */ - -/* Monokai theme */ - -/* Tokyo Night theme */ - -/* Solarized theme */ - -/* Gruvbox theme */ - -/* Catppuccin theme */ - -/* One Dark theme */ - -/* Synthwave theme */ - /* XML Editor container styles */ .xml-editor { position: relative; @@ -1058,3 +957,88 @@ animation: none; } } + +/* Prism Scrollbar */ +.custom-scrollbar::-webkit-scrollbar { + width: 5px; + height: 5px; +} +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} +.custom-scrollbar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; +} +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); +} + +/* Prism Shortcut Badge */ +.shortcut-badge { + font-size: 9px; + background: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.3); + padding: 1px 5px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.08); + font-family: var(--font-mono); + font-weight: 600; +} + +/* Prism Toggle Switch */ +.toggle-track { + width: 42px; + height: 20px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 20px; + position: relative; + cursor: pointer; +} +.toggle-thumb { + position: absolute; + right: 3px; + top: 3px; + width: 12px; + height: 12px; + background: var(--accent-cyan); + border-radius: 50%; + box-shadow: 0 0 8px var(--accent-cyan); +} + +/* Prism Column Styles */ +.col-in-progress { + border: 1px solid rgba(34, 211, 238, 0.25); + background: rgba(34, 211, 238, 0.02); + box-shadow: inset 0 0 40px rgba(34, 211, 238, 0.03); +} +.col-waiting { + border-top: 2px solid rgba(245, 158, 11, 0.3); +} +.col-verified { + border-top: 2px solid rgba(16, 185, 129, 0.3); +} + +/* Prism Font Overrides - Ensure these take precedence */ +:root { + --font-sans: + 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; + --font-mono: + 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + + /* Ensure backgrounds are deep prism color */ + --background: oklch(0.08 0.03 260); + --foreground: oklch(0.95 0 0); +} + +body { + background-color: var(--bg-deep); + font-family: var(--font-sans); +} + +.mono { + font-family: var(--font-mono) !important; +} diff --git a/index (25).html b/index (25).html new file mode 100644 index 00000000..222b4cee --- /dev/null +++ b/index (25).html @@ -0,0 +1,723 @@ + + + + + + automaker. | Kanban Board + + + + + + +
+ + + + + +
+ +
+
+

Kanban Board

+

+ test case 1 +

+
+ +
+
+ +
+
+
+ 3 +
+ + +
+
+ + +
+
+ + +
+ / +
+
+
+ + +
+ + +
+
+ + +
+
+ +
+
+
+ +

+ Backlog +

+
+ + + Mabe 6 +
+
+ 47 +
+ +
+
+
+ +
+

+ Create a bouncing animation using CSS keyframes that simulates elastic motion... +

+
+ More +
+
+ Opus 4.2 +
+
+ + +
+
+
+

+ Implement CSS reset rules and establish base styling for the page including + typography, spacing... +

+
+ Opus 4.3 +
+
+ + +
+
+
+
+ + +
+
+
+ +

+ In Progress +

+
+ 3 +
+ +
+ +
+
+
+ Opus 6.5 +
+
+ 00:04 +
+
+

+ Configure the application for deployment to a web hosting platform. Set up + necessary build processes... +

+
+ + +
+
+ +
+
+
+ Opus 4.5 +
+
+ 00:07 +
+
+

+ Create helper functions for selecting and querying DOM elements. Provide reusable + utilities for element... +

+
+ + +
+
+
+
+ + +
+
+
+ +

+ Waiting Approval +

+
+ 2 +
+ +
+
+
+ + + +
+

+ Add descriptive labels and titles for each button animation style to identify them + visually. Help users... +

+
+ + +
+
+
+
+ + +
+
+
+ +

+ Verified +

+ +
+ 4 +
+ +
+
+
+ + +
+

+ Define foundational button styles with padding, borders, radius, and default + colors. Create... +

+
+ + +
+
+
+
+
+
+
+ + +
+ + +
+ + + + diff --git a/package-lock.json b/package-lock.json index 56c88d73..f35b6be1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,6 +106,7 @@ "cmdk": "^1.1.1", "dagre": "^0.8.5", "dotenv": "^17.2.3", + "framer-motion": "^12.23.26", "geist": "^1.5.1", "lucide-react": "^0.562.0", "react": "19.2.3", @@ -113,6 +114,7 @@ "react-markdown": "^10.1.0", "react-resizable-panels": "^3.0.6", "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "zustand": "^5.0.9" @@ -1212,7 +1214,7 @@ }, "node_modules/@electron/node-gyp": { "version": "10.2.0-electron.1", - "resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", + "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", "dev": true, "license": "MIT", @@ -9712,6 +9714,33 @@ "node": ">= 0.6" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", @@ -12744,6 +12773,21 @@ "node": ">= 0.8" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",