Merge pull request #67 from mzubair481/feat/ui-design-system-improvements

feat(ui): comprehensive design system improvements
This commit is contained in:
Leon van Zyl
2026-01-15 12:50:33 +02:00
committed by GitHub
27 changed files with 914 additions and 303 deletions

5
.gitignore vendored
View File

@@ -128,6 +128,11 @@ pnpm-lock.yaml
poetry.lock
Pipfile.lock
# ===================
# TypeScript
# ===================
*.tsbuildinfo
# ===================
# Misc
# ===================

View File

@@ -7,7 +7,7 @@
<title>AutoCoder</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=Work+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>

View File

@@ -4,8 +4,6 @@ import { useProjects, useFeatures, useAgentStatus, useSettings } from './hooks/u
import { useProjectWebSocket } from './hooks/useWebSocket'
import { useFeatureSound } from './hooks/useFeatureSound'
import { useCelebration } from './hooks/useCelebration'
const STORAGE_KEY = 'autocoder-selected-project'
import { ProjectSelector } from './components/ProjectSelector'
import { KanbanBoard } from './components/KanbanBoard'
import { AgentControl } from './components/AgentControl'
@@ -20,9 +18,12 @@ import { AssistantPanel } from './components/AssistantPanel'
import { ExpandProjectModal } from './components/ExpandProjectModal'
import { SettingsModal } from './components/SettingsModal'
import { DevServerControl } from './components/DevServerControl'
import { Loader2, Settings } from 'lucide-react'
import { Loader2, Settings, Moon, Sun } from 'lucide-react'
import type { Feature } from './lib/types'
const STORAGE_KEY = 'autocoder-selected-project'
const DARK_MODE_KEY = 'autocoder-dark-mode'
function App() {
// Initialize selected project from localStorage
const [selectedProject, setSelectedProject] = useState<string | null>(() => {
@@ -42,6 +43,13 @@ function App() {
const [assistantOpen, setAssistantOpen] = useState(false)
const [showSettings, setShowSettings] = useState(false)
const [isSpecCreating, setIsSpecCreating] = useState(false)
const [darkMode, setDarkMode] = useState(() => {
try {
return localStorage.getItem(DARK_MODE_KEY) === 'true'
} catch {
return false
}
})
const queryClient = useQueryClient()
const { data: projects, isLoading: projectsLoading } = useProjects()
@@ -50,6 +58,20 @@ function App() {
useAgentStatus(selectedProject) // Keep polling for status updates
const wsState = useProjectWebSocket(selectedProject)
// Apply dark mode class to document
useEffect(() => {
if (darkMode) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
try {
localStorage.setItem(DARK_MODE_KEY, String(darkMode))
} catch {
// localStorage not available
}
}, [darkMode])
// Play sounds when features move between columns
useFeatureSound(features)
@@ -170,9 +192,9 @@ function App() {
}
return (
<div className="min-h-screen bg-[var(--color-neo-bg)]">
<div className="min-h-screen bg-neo-bg">
{/* Header */}
<header className="bg-[var(--color-neo-text)] text-white border-b-4 border-[var(--color-neo-border)]">
<header className="bg-neo-card text-neo-text border-b-4 border-neo-border">
<div className="max-w-7xl mx-auto px-4 py-4">
<div className="flex items-center justify-between">
{/* Logo and Title */}
@@ -215,7 +237,7 @@ function App() {
{/* GLM Mode Badge */}
{settings?.glm_mode && (
<span
className="px-2 py-1 text-xs font-bold bg-purple-500 text-white rounded border-2 border-black shadow-neo-sm"
className="px-2 py-1 text-xs font-bold bg-[var(--color-neo-glm)] text-white rounded border-2 border-neo-border shadow-neo-sm"
title="Using GLM API (configured via .env)"
>
GLM
@@ -223,6 +245,16 @@ function App() {
)}
</>
)}
{/* Dark mode toggle - always visible */}
<button
onClick={() => setDarkMode(!darkMode)}
className="neo-btn text-sm py-2 px-3"
title="Toggle dark mode"
aria-label="Toggle dark mode"
>
{darkMode ? <Sun size={18} /> : <Moon size={18} />}
</button>
</div>
</div>
</div>
@@ -238,7 +270,7 @@ function App() {
<h2 className="font-display text-2xl font-bold mb-2">
Welcome to AutoCoder
</h2>
<p className="text-[var(--color-neo-text-secondary)] mb-4">
<p className="text-neo-text-secondary mb-4">
Select a project from the dropdown above or create a new one to get started.
</p>
</div>
@@ -265,11 +297,11 @@ function App() {
features.done.length === 0 &&
wsState.agentStatus === 'running' && (
<div className="neo-card p-8 text-center">
<Loader2 size={32} className="animate-spin mx-auto mb-4 text-[var(--color-neo-progress)]" />
<Loader2 size={32} className="animate-spin mx-auto mb-4 text-neo-progress" />
<h3 className="font-display font-bold text-xl mb-2">
Initializing Features...
</h3>
<p className="text-[var(--color-neo-text-secondary)]">
<p className="text-neo-text-secondary">
The agent is reading your spec and creating features. This may take a moment.
</p>
</div>

View File

@@ -87,13 +87,13 @@ export function AddFeatureForm({ projectName, onClose }: AddFeatureFormProps) {
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Error Message */}
{error && (
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-danger)] text-white border-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-3 border-[var(--color-neo-error-border)]">
<AlertCircle size={20} />
<span>{error}</span>
<button
type="button"
onClick={() => setError(null)}
className="ml-auto"
className="ml-auto hover:opacity-70 transition-opacity"
>
<X size={16} />
</button>
@@ -166,8 +166,11 @@ export function AddFeatureForm({ projectName, onClose }: AddFeatureFormProps) {
</label>
<div className="space-y-2">
{steps.map((step, index) => (
<div key={step.id} className="flex gap-2">
<span className="neo-input w-12 text-center flex-shrink-0 flex items-center justify-center">
<div key={step.id} className="flex gap-2 items-center">
<span
className="w-10 h-10 flex-shrink-0 flex items-center justify-center font-mono font-bold text-sm border-3 border-[var(--color-neo-border)] bg-[var(--color-neo-bg)] text-[var(--color-neo-text-secondary)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
{index + 1}
</span>
<input

View File

@@ -131,7 +131,7 @@ export function AssistantChat({ projectName }: AssistantChatProps) {
)}
{/* Input area */}
<div className="border-t-3 border-[var(--color-neo-border)] p-4 bg-white">
<div className="border-t-3 border-[var(--color-neo-border)] p-4 bg-[var(--color-neo-card)]">
<div className="flex gap-2">
<textarea
ref={inputRef}

View File

@@ -17,9 +17,8 @@ export function AssistantFAB({ onClick, isOpen }: AssistantFABProps) {
fixed bottom-6 right-6 z-50
w-14 h-14
flex items-center justify-center
bg-[var(--color-neo-progress)] text-white
bg-[var(--color-neo-progress)] text-[var(--color-neo-text-on-bright)]
border-3 border-[var(--color-neo-border)]
rounded-full
shadow-neo-md
transition-all duration-200
hover:shadow-neo-lg hover:-translate-y-0.5

View File

@@ -31,26 +31,29 @@ export function AssistantPanel({ projectName, isOpen, onClose }: AssistantPanelP
className={`
fixed right-0 top-0 bottom-0 z-50
w-[400px] max-w-[90vw]
bg-white
bg-neo-card
border-l-4 border-[var(--color-neo-border)]
shadow-[-8px_0_0px_rgba(0,0,0,1)]
transform transition-transform duration-300 ease-out
flex flex-col
${isOpen ? 'translate-x-0' : 'translate-x-full'}
`}
style={{ boxShadow: 'var(--shadow-neo-left-lg)' }}
role="dialog"
aria-label="Project Assistant"
aria-hidden={!isOpen}
>
{/* Header */}
<div className="flex items-center justify-between px-4 py-3 border-b-3 border-[var(--color-neo-border)] bg-[var(--color-neo-progress)]">
<div className="flex items-center justify-between px-4 py-3 border-b-3 border-neo-border bg-neo-progress">
<div className="flex items-center gap-2">
<div className="bg-white border-2 border-[var(--color-neo-border)] p-1.5 shadow-[2px_2px_0px_rgba(0,0,0,1)]">
<div
className="bg-neo-card border-2 border-neo-border p-1.5"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<Bot size={18} />
</div>
<div>
<h2 className="font-display font-bold text-white">Project Assistant</h2>
<p className="text-xs text-white/80 font-mono">{projectName}</p>
<h2 className="font-display font-bold text-neo-text-on-bright">Project Assistant</h2>
<p className="text-xs text-neo-text-on-bright opacity-80 font-mono">{projectName}</p>
</div>
</div>
<button
@@ -58,9 +61,9 @@ export function AssistantPanel({ projectName, isOpen, onClose }: AssistantPanelP
className="
neo-btn neo-btn-ghost
p-2
bg-white/20 border-white/40
hover:bg-white/30
text-white
bg-[var(--color-neo-card)] border-[var(--color-neo-border)]
hover:bg-[var(--color-neo-bg)]
text-[var(--color-neo-text)]
"
title="Close Assistant (Press A)"
aria-label="Close Assistant"

View File

@@ -21,31 +21,37 @@ export function ChatMessage({ message }: ChatMessageProps) {
minute: '2-digit',
})
// Role-specific styling
// Role-specific styling using CSS variables for theme consistency
const roleConfig = {
user: {
icon: User,
bgColor: 'bg-[var(--color-neo-pending)]',
textColor: 'text-[var(--color-neo-text-on-bright)]',
borderColor: 'border-[var(--color-neo-border)]',
align: 'justify-end',
bubbleAlign: 'items-end',
iconBg: 'bg-[var(--color-neo-pending)]',
shadow: 'var(--shadow-neo-md)',
},
assistant: {
icon: Bot,
bgColor: 'bg-white',
bgColor: 'bg-[var(--color-neo-card)]',
textColor: 'text-[var(--color-neo-text)]',
borderColor: 'border-[var(--color-neo-border)]',
align: 'justify-start',
bubbleAlign: 'items-start',
iconBg: 'bg-[var(--color-neo-progress)]',
shadow: 'var(--shadow-neo-md)',
},
system: {
icon: Info,
bgColor: 'bg-[var(--color-neo-done)]',
textColor: 'text-[var(--color-neo-text-on-bright)]',
borderColor: 'border-[var(--color-neo-border)]',
align: 'justify-center',
bubbleAlign: 'items-center',
iconBg: 'bg-[var(--color-neo-done)]',
shadow: 'var(--shadow-neo-sm)',
},
}
@@ -61,9 +67,9 @@ export function ChatMessage({ message }: ChatMessageProps) {
${config.bgColor}
border-2 ${config.borderColor}
px-4 py-2
text-sm font-mono
shadow-[2px_2px_0px_rgba(0,0,0,1)]
text-sm font-mono text-[var(--color-neo-text-on-bright)]
`}
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<span className="flex items-center gap-2">
<Icon size={14} />
@@ -85,11 +91,11 @@ export function ChatMessage({ message }: ChatMessageProps) {
${config.iconBg}
border-2 border-[var(--color-neo-border)]
p-1.5
shadow-[2px_2px_0px_rgba(0,0,0,1)]
flex-shrink-0
`}
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<Icon size={16} className="text-white" />
<Icon size={16} className="text-[var(--color-neo-text-on-bright)]" />
</div>
)}
@@ -98,13 +104,13 @@ export function ChatMessage({ message }: ChatMessageProps) {
${config.bgColor}
border-3 ${config.borderColor}
px-4 py-3
shadow-[4px_4px_0px_rgba(0,0,0,1)]
${isStreaming ? 'animate-pulse-neo' : ''}
`}
style={{ boxShadow: config.shadow }}
>
{/* Parse content for basic markdown-like formatting */}
{content && (
<div className="whitespace-pre-wrap text-sm leading-relaxed text-[#1a1a1a]">
<div className={`whitespace-pre-wrap text-sm leading-relaxed ${config.textColor}`}>
{content.split('\n').map((line, i) => {
// Bold text
const boldRegex = /\*\*(.*?)\*\*/g
@@ -144,7 +150,8 @@ export function ChatMessage({ message }: ChatMessageProps) {
{attachments.map((attachment) => (
<div
key={attachment.id}
className="border-2 border-[var(--color-neo-border)] p-1 bg-white shadow-[2px_2px_0px_rgba(0,0,0,1)]"
className="border-2 border-[var(--color-neo-border)] p-1 bg-[var(--color-neo-card)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<img
src={attachment.previewUrl}
@@ -173,11 +180,11 @@ export function ChatMessage({ message }: ChatMessageProps) {
${config.iconBg}
border-2 border-[var(--color-neo-border)]
p-1.5
shadow-[2px_2px_0px_rgba(0,0,0,1)]
flex-shrink-0
`}
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<Icon size={16} />
<Icon size={16} className="text-[var(--color-neo-text-on-bright)]" />
</div>
)}
</div>

View File

@@ -55,12 +55,12 @@ export function ConfirmDialog({
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-3">
<div
className="p-2 border-2 border-[var(--color-neo-border)] shadow-[2px_2px_0px_rgba(0,0,0,1)]"
style={{ backgroundColor: colors.icon }}
className="p-2 border-2 border-[var(--color-neo-border)]"
style={{ boxShadow: 'var(--shadow-neo-sm)', backgroundColor: colors.icon }}
>
<AlertTriangle size={20} className="text-white" />
<AlertTriangle size={20} className="text-[var(--color-neo-text-on-bright)]" />
</div>
<h2 className="font-display font-bold text-lg text-[#1a1a1a]">
<h2 className="font-display font-bold text-lg text-[var(--color-neo-text)]">
{title}
</h2>
</div>

View File

@@ -273,18 +273,18 @@ export function DebugLogViewer({
return 'info'
}
// Get color class for log level
// Get color class for log level using theme CSS variables
const getLogColor = (level: LogLevel): string => {
switch (level) {
case 'error':
return 'text-red-400'
return 'text-[var(--color-neo-log-error)]'
case 'warn':
return 'text-yellow-400'
return 'text-[var(--color-neo-log-warning)]'
case 'debug':
return 'text-gray-400'
return 'text-[var(--color-neo-log-debug)]'
case 'info':
default:
return 'text-green-400'
return 'text-[var(--color-neo-log-info)]'
}
}
@@ -316,27 +316,27 @@ export function DebugLogViewer({
className="absolute top-0 left-0 right-0 h-2 cursor-ns-resize group flex items-center justify-center -translate-y-1/2 z-50"
onMouseDown={handleResizeStart}
>
<div className="w-16 h-1.5 bg-[#333] rounded-full group-hover:bg-[#555] transition-colors flex items-center justify-center">
<GripHorizontal size={12} className="text-gray-500 group-hover:text-gray-400" />
<div className="w-16 h-1.5 bg-[var(--color-neo-border)] rounded-full group-hover:bg-[var(--color-neo-text-secondary)] transition-colors flex items-center justify-center">
<GripHorizontal size={12} className="text-[var(--color-neo-text-muted)] group-hover:text-[var(--color-neo-text-secondary)]" />
</div>
</div>
)}
{/* Header bar */}
<div
className="flex items-center justify-between h-10 px-4 bg-[#1a1a1a] border-t-3 border-black"
className="flex items-center justify-between h-10 px-4 bg-[var(--color-neo-border)] border-t-3 border-[var(--color-neo-text)]"
>
<div className="flex items-center gap-2">
{/* Collapse/expand toggle */}
<button
onClick={onToggle}
className="flex items-center gap-2 hover:bg-[#333] px-2 py-1 rounded transition-colors cursor-pointer"
className="flex items-center gap-2 hover:bg-[var(--color-neo-hover-subtle)] px-2 py-1 rounded transition-colors cursor-pointer"
>
<TerminalIcon size={16} className="text-green-400" />
<span className="font-mono text-sm text-white font-bold">
<TerminalIcon size={16} className="text-[var(--color-neo-done)]" />
<span className="font-mono text-sm text-[var(--color-neo-bg)] font-bold">
Debug
</span>
<span className="px-1.5 py-0.5 text-xs font-mono bg-[#333] text-gray-500 rounded" title="Toggle debug panel">
<span className="px-1.5 py-0.5 text-xs font-mono bg-[var(--color-neo-card)] text-[var(--color-neo-text-muted)] rounded" title="Toggle debug panel">
D
</span>
</button>
@@ -351,14 +351,14 @@ export function DebugLogViewer({
}}
className={`flex items-center gap-1.5 px-3 py-1 text-xs font-mono rounded transition-colors ${
activeTab === 'agent'
? 'bg-[#333] text-white'
: 'text-gray-400 hover:text-white hover:bg-[#2a2a2a]'
? 'bg-[var(--color-neo-card)] text-[var(--color-neo-text)]'
: 'text-[var(--color-neo-text-muted)] hover:text-[var(--color-neo-text)] hover:bg-[var(--color-neo-hover-subtle)]'
}`}
>
<Cpu size={12} />
Agent
{logs.length > 0 && (
<span className="px-1.5 py-0.5 text-[10px] bg-[#444] rounded">
<span className="px-1.5 py-0.5 text-[10px] bg-[var(--color-neo-text-secondary)] text-[var(--color-neo-bg)] rounded">
{logs.length}
</span>
)}
@@ -370,14 +370,14 @@ export function DebugLogViewer({
}}
className={`flex items-center gap-1.5 px-3 py-1 text-xs font-mono rounded transition-colors ${
activeTab === 'devserver'
? 'bg-[#333] text-white'
: 'text-gray-400 hover:text-white hover:bg-[#2a2a2a]'
? 'bg-[var(--color-neo-card)] text-[var(--color-neo-text)]'
: 'text-[var(--color-neo-text-muted)] hover:text-[var(--color-neo-text)] hover:bg-[var(--color-neo-hover-subtle)]'
}`}
>
<Server size={12} />
Dev Server
{devLogs.length > 0 && (
<span className="px-1.5 py-0.5 text-[10px] bg-[#444] rounded">
<span className="px-1.5 py-0.5 text-[10px] bg-[var(--color-neo-text-secondary)] text-[var(--color-neo-bg)] rounded">
{devLogs.length}
</span>
)}
@@ -389,13 +389,13 @@ export function DebugLogViewer({
}}
className={`flex items-center gap-1.5 px-3 py-1 text-xs font-mono rounded transition-colors ${
activeTab === 'terminal'
? 'bg-[#333] text-white'
: 'text-gray-400 hover:text-white hover:bg-[#2a2a2a]'
? 'bg-[var(--color-neo-card)] text-[var(--color-neo-text)]'
: 'text-[var(--color-neo-text-muted)] hover:text-[var(--color-neo-text)] hover:bg-[var(--color-neo-hover-subtle)]'
}`}
>
<TerminalIcon size={12} />
Terminal
<span className="px-1.5 py-0.5 text-[10px] bg-[#444] text-gray-500 rounded" title="Toggle terminal">
<span className="px-1.5 py-0.5 text-[10px] bg-[var(--color-neo-text-secondary)] text-[var(--color-neo-text-muted)] rounded" title="Toggle terminal">
T
</span>
</button>
@@ -406,12 +406,12 @@ export function DebugLogViewer({
{isOpen && activeTab !== 'terminal' && (
<>
{getCurrentLogCount() > 0 && (
<span className="px-2 py-0.5 text-xs font-mono bg-[#333] text-gray-300 rounded ml-2">
<span className="px-2 py-0.5 text-xs font-mono bg-[var(--color-neo-card)] text-[var(--color-neo-text-secondary)] rounded ml-2">
{getCurrentLogCount()}
</span>
)}
{isAutoScrollPaused() && (
<span className="px-2 py-0.5 text-xs font-mono bg-yellow-600 text-white rounded">
<span className="px-2 py-0.5 text-xs font-mono bg-[var(--color-neo-pending)] text-[var(--color-neo-text-on-bright)] rounded">
Paused
</span>
)}
@@ -427,17 +427,17 @@ export function DebugLogViewer({
e.stopPropagation()
handleClear()
}}
className="p-1.5 hover:bg-[#333] rounded transition-colors"
className="p-1.5 hover:bg-[var(--color-neo-hover-subtle)] rounded transition-colors"
title="Clear logs"
>
<Trash2 size={14} className="text-gray-400" />
<Trash2 size={14} className="text-[var(--color-neo-text-muted)]" />
</button>
)}
<div className="p-1">
{isOpen ? (
<ChevronDown size={16} className="text-gray-400" />
<ChevronDown size={16} className="text-[var(--color-neo-text-muted)]" />
) : (
<ChevronUp size={16} className="text-gray-400" />
<ChevronUp size={16} className="text-[var(--color-neo-text-muted)]" />
)}
</div>
</div>
@@ -445,7 +445,7 @@ export function DebugLogViewer({
{/* Content area */}
{isOpen && (
<div className="h-[calc(100%-2.5rem)] bg-[#1a1a1a]">
<div className="h-[calc(100%-2.5rem)] bg-[var(--color-neo-border)]">
{/* Agent Logs Tab */}
{activeTab === 'agent' && (
<div
@@ -454,7 +454,7 @@ export function DebugLogViewer({
className="h-full overflow-y-auto p-2 font-mono text-sm"
>
{logs.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500">
<div className="flex items-center justify-center h-full text-[var(--color-neo-text-muted)]">
No logs yet. Start the agent to see output.
</div>
) : (
@@ -467,9 +467,9 @@ export function DebugLogViewer({
return (
<div
key={`${log.timestamp}-${index}`}
className="flex gap-2 hover:bg-[#2a2a2a] px-1 py-0.5 rounded"
className="flex gap-2 hover:bg-[var(--color-neo-hover-subtle)] px-1 py-0.5 rounded"
>
<span className="text-gray-500 select-none shrink-0">
<span className="text-[var(--color-neo-text-muted)] select-none shrink-0">
{timestamp}
</span>
<span className={`${colorClass} whitespace-pre-wrap break-all`}>
@@ -491,7 +491,7 @@ export function DebugLogViewer({
className="h-full overflow-y-auto p-2 font-mono text-sm"
>
{devLogs.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500">
<div className="flex items-center justify-center h-full text-[var(--color-neo-text-muted)]">
No dev server logs yet.
</div>
) : (
@@ -504,9 +504,9 @@ export function DebugLogViewer({
return (
<div
key={`${log.timestamp}-${index}`}
className="flex gap-2 hover:bg-[#2a2a2a] px-1 py-0.5 rounded"
className="flex gap-2 hover:bg-[var(--color-neo-hover-subtle)] px-1 py-0.5 rounded"
>
<span className="text-gray-500 select-none shrink-0">
<span className="text-[var(--color-neo-text-muted)] select-none shrink-0">
{timestamp}
</span>
<span className={`${colorClass} whitespace-pre-wrap break-all`}>
@@ -538,11 +538,11 @@ export function DebugLogViewer({
{/* Terminal content - render all terminals and show/hide to preserve buffers */}
<div className="flex-1 min-h-0 relative">
{isLoadingTerminals ? (
<div className="h-full flex items-center justify-center text-gray-500 font-mono text-sm">
<div className="h-full flex items-center justify-center text-[var(--color-neo-text-muted)] font-mono text-sm">
Loading terminals...
</div>
) : terminals.length === 0 ? (
<div className="h-full flex items-center justify-center text-gray-500 font-mono text-sm">
<div className="h-full flex items-center justify-center text-[var(--color-neo-text-muted)] font-mono text-sm">
No terminal available
</div>
) : (

View File

@@ -92,7 +92,7 @@ export function DevServerControl({ projectName, status, url }: DevServerControlP
className="neo-btn text-sm py-2 px-3"
style={isCrashed ? {
backgroundColor: 'var(--color-neo-danger)',
color: '#ffffff',
color: 'var(--color-neo-text-on-bright)',
} : undefined}
title={isCrashed ? "Dev Server Crashed - Click to Restart" : "Start Dev Server"}
aria-label={isCrashed ? "Restart Dev Server (crashed)" : "Start Dev Server"}
@@ -112,7 +112,7 @@ export function DevServerControl({ projectName, status, url }: DevServerControlP
className="neo-btn text-sm py-2 px-3"
style={{
backgroundColor: 'var(--color-neo-progress)',
color: '#ffffff',
color: 'var(--color-neo-text-on-bright)',
}}
title="Stop Dev Server"
aria-label="Stop Dev Server"
@@ -134,7 +134,7 @@ export function DevServerControl({ projectName, status, url }: DevServerControlP
className="neo-btn text-sm py-2 px-3 gap-1"
style={{
backgroundColor: 'var(--color-neo-progress)',
color: '#ffffff',
color: 'var(--color-neo-text-on-bright)',
textDecoration: 'none',
}}
title={`Open ${url} in new tab`}

View File

@@ -105,13 +105,13 @@ export function EditFeatureForm({ feature, projectName, onClose, onSaved }: Edit
<form onSubmit={handleSubmit} className="p-6 space-y-4">
{/* Error Message */}
{error && (
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-danger)] text-white border-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-3 border-[var(--color-neo-error-border)]">
<AlertCircle size={20} />
<span>{error}</span>
<button
type="button"
onClick={() => setError(null)}
className="ml-auto"
className="ml-auto hover:opacity-70 transition-opacity"
>
<X size={16} />
</button>
@@ -184,8 +184,11 @@ export function EditFeatureForm({ feature, projectName, onClose, onSaved }: Edit
</label>
<div className="space-y-2">
{steps.map((step, index) => (
<div key={step.id} className="flex gap-2">
<span className="neo-input w-12 text-center flex-shrink-0 flex items-center justify-center">
<div key={step.id} className="flex gap-2 items-center">
<span
className="w-10 h-10 flex-shrink-0 flex items-center justify-center font-mono font-bold text-sm border-3 border-[var(--color-neo-border)] bg-[var(--color-neo-bg)] text-[var(--color-neo-text-secondary)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
{index + 1}
</span>
<input

View File

@@ -152,28 +152,28 @@ export function ExpandProjectChat({
switch (connectionStatus) {
case 'connected':
return (
<span className="flex items-center gap-1 text-xs text-[var(--color-neo-done)]">
<span className="flex items-center gap-1 text-xs text-neo-done">
<Wifi size={12} />
Connected
</span>
)
case 'connecting':
return (
<span className="flex items-center gap-1 text-xs text-[var(--color-neo-pending)]">
<span className="flex items-center gap-1 text-xs text-neo-pending">
<Wifi size={12} className="animate-pulse" />
Connecting...
</span>
)
case 'error':
return (
<span className="flex items-center gap-1 text-xs text-[var(--color-neo-danger)]">
<span className="flex items-center gap-1 text-xs text-neo-danger">
<WifiOff size={12} />
Error
</span>
)
default:
return (
<span className="flex items-center gap-1 text-xs text-[var(--color-neo-text-secondary)]">
<span className="flex items-center gap-1 text-xs text-neo-text-secondary">
<WifiOff size={12} />
Disconnected
</span>
@@ -182,16 +182,16 @@ export function ExpandProjectChat({
}
return (
<div className="flex flex-col h-full bg-[var(--color-neo-bg)]">
<div className="flex flex-col h-full bg-neo-bg">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)] bg-white">
<div className="flex items-center justify-between p-4 border-b-3 border-neo-border bg-neo-card">
<div className="flex items-center gap-3">
<h2 className="font-display font-bold text-lg text-[#1a1a1a]">
<h2 className="font-display font-bold text-lg text-neo-text">
Expand Project: {projectName}
</h2>
<ConnectionIndicator />
{featuresCreated > 0 && (
<span className="flex items-center gap-1 text-sm text-[var(--color-neo-done)] font-bold">
<span className="flex items-center gap-1 text-sm text-neo-done font-bold">
<Plus size={14} />
{featuresCreated} added
</span>
@@ -200,7 +200,7 @@ export function ExpandProjectChat({
<div className="flex items-center gap-2">
{isComplete && (
<span className="flex items-center gap-1 text-sm text-[var(--color-neo-done)] font-bold">
<span className="flex items-center gap-1 text-sm text-neo-done font-bold">
<CheckCircle2 size={16} />
Complete
</span>
@@ -218,12 +218,12 @@ export function ExpandProjectChat({
{/* Error banner */}
{error && (
<div className="flex items-center gap-2 p-3 bg-[var(--color-neo-danger)] text-white border-b-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-2 p-3 bg-neo-error-bg text-neo-error-text border-b-3 border-neo-error-border">
<AlertCircle size={16} />
<span className="flex-1 text-sm">{error}</span>
<button
onClick={() => setError(null)}
className="p-1 hover:bg-white/20 rounded"
className="p-1 hover:opacity-70 transition-opacity rounded"
>
<X size={14} />
</button>
@@ -238,7 +238,7 @@ export function ExpandProjectChat({
<h3 className="font-display font-bold text-lg mb-2">
Starting Project Expansion
</h3>
<p className="text-sm text-[var(--color-neo-text-secondary)]">
<p className="text-sm text-neo-text-secondary">
Connecting to Claude to help you add new features to your project...
</p>
{connectionStatus === 'error' && (
@@ -268,7 +268,7 @@ export function ExpandProjectChat({
{/* Input area */}
{!isComplete && (
<div
className="p-4 border-t-3 border-[var(--color-neo-border)] bg-white"
className="p-4 border-t-3 border-neo-border bg-neo-card"
onDrop={handleDrop}
onDragOver={handleDragOver}
>
@@ -278,7 +278,8 @@ export function ExpandProjectChat({
{pendingAttachments.map((attachment) => (
<div
key={attachment.id}
className="relative group border-2 border-[var(--color-neo-border)] p-1 bg-white shadow-[2px_2px_0px_rgba(0,0,0,1)]"
className="relative group border-2 border-neo-border p-1 bg-neo-card"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<img
src={attachment.previewUrl}
@@ -287,7 +288,7 @@ export function ExpandProjectChat({
/>
<button
onClick={() => handleRemoveAttachment(attachment.id)}
className="absolute -top-2 -right-2 bg-[var(--color-neo-danger)] text-white rounded-full p-0.5 border-2 border-[var(--color-neo-border)] hover:scale-110 transition-transform"
className="absolute -top-2 -right-2 bg-neo-danger text-neo-text-on-bright rounded-full p-0.5 border-2 border-neo-border hover:scale-110 transition-transform"
title="Remove attachment"
>
<X size={12} />
@@ -351,7 +352,7 @@ export function ExpandProjectChat({
</div>
{/* Help text */}
<p className="text-xs text-[var(--color-neo-text-secondary)] mt-2">
<p className="text-xs text-neo-text-secondary mt-2">
Press Enter to send. Drag & drop or click <Paperclip size={12} className="inline" /> to attach images.
</p>
</div>
@@ -359,7 +360,7 @@ export function ExpandProjectChat({
{/* Completion footer */}
{isComplete && (
<div className="p-4 border-t-3 border-[var(--color-neo-border)] bg-[var(--color-neo-done)]">
<div className="p-4 border-t-3 border-neo-border bg-neo-done text-neo-text-on-bright">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CheckCircle2 size={20} />
@@ -369,7 +370,7 @@ export function ExpandProjectChat({
</div>
<button
onClick={() => onComplete(featuresCreated)}
className="neo-btn bg-white"
className="neo-btn bg-neo-card"
>
Close
</button>

View File

@@ -7,16 +7,17 @@ interface FeatureCardProps {
isInProgress?: boolean
}
// Generate consistent color for category
// Generate consistent color for category using CSS variable references
// These map to the --color-neo-category-* variables defined in globals.css
function getCategoryColor(category: string): string {
const colors = [
'#ff006e', // pink
'#00b4d8', // cyan
'#70e000', // green
'#ffd60a', // yellow
'#ff5400', // orange
'#8338ec', // purple
'#3a86ff', // blue
'var(--color-neo-category-pink)',
'var(--color-neo-category-cyan)',
'var(--color-neo-category-green)',
'var(--color-neo-category-yellow)',
'var(--color-neo-category-orange)',
'var(--color-neo-category-purple)',
'var(--color-neo-category-blue)',
]
let hash = 0
@@ -36,18 +37,18 @@ export function FeatureCard({ feature, onClick, isInProgress }: FeatureCardProps
className={`
w-full text-left neo-card p-4 cursor-pointer
${isInProgress ? 'animate-pulse-neo' : ''}
${feature.passes ? 'border-[var(--color-neo-done)]' : ''}
${feature.passes ? 'border-neo-done' : ''}
`}
>
{/* Header */}
<div className="flex items-start justify-between gap-2 mb-2">
<span
className="neo-badge"
style={{ backgroundColor: categoryColor, color: 'white' }}
style={{ backgroundColor: categoryColor, color: 'var(--color-neo-text-on-bright)' }}
>
{feature.category}
</span>
<span className="font-mono text-sm text-[var(--color-neo-text-secondary)]">
<span className="font-mono text-sm text-neo-text-secondary">
#{feature.priority}
</span>
</div>
@@ -58,7 +59,7 @@ export function FeatureCard({ feature, onClick, isInProgress }: FeatureCardProps
</h3>
{/* Description */}
<p className="text-sm text-[var(--color-neo-text-secondary)] line-clamp-2 mb-3">
<p className="text-sm text-neo-text-secondary line-clamp-2 mb-3">
{feature.description}
</p>
@@ -66,18 +67,18 @@ export function FeatureCard({ feature, onClick, isInProgress }: FeatureCardProps
<div className="flex items-center gap-2 text-sm">
{isInProgress ? (
<>
<Loader2 size={16} className="animate-spin text-[var(--color-neo-progress)]" />
<span className="text-[var(--color-neo-progress)] font-bold">Processing...</span>
<Loader2 size={16} className="animate-spin text-neo-progress" />
<span className="text-neo-progress font-bold">Processing...</span>
</>
) : feature.passes ? (
<>
<CheckCircle2 size={16} className="text-[var(--color-neo-done)]" />
<span className="text-[var(--color-neo-done)] font-bold">Complete</span>
<CheckCircle2 size={16} className="text-neo-done" />
<span className="text-neo-done font-bold">Complete</span>
</>
) : (
<>
<Circle size={16} className="text-[var(--color-neo-text-secondary)]" />
<span className="text-[var(--color-neo-text-secondary)]">Pending</span>
<Circle size={16} className="text-neo-text-secondary" />
<span className="text-neo-text-secondary">Pending</span>
</>
)}
</div>

View File

@@ -4,6 +4,26 @@ import { useSkipFeature, useDeleteFeature } from '../hooks/useProjects'
import { EditFeatureForm } from './EditFeatureForm'
import type { Feature } from '../lib/types'
// Generate consistent color for category (matches FeatureCard pattern)
function getCategoryColor(category: string): string {
const colors = [
'#ff006e', // pink (accent)
'#00b4d8', // cyan (progress)
'#70e000', // green (done)
'#ffd60a', // yellow (pending)
'#ff5400', // orange (danger)
'#8338ec', // purple
'#3a86ff', // blue
]
let hash = 0
for (let i = 0; i < category.length; i++) {
hash = category.charCodeAt(i) + ((hash << 5) - hash)
}
return colors[Math.abs(hash) % colors.length]
}
interface FeatureModalProps {
feature: Feature
projectName: string
@@ -59,7 +79,10 @@ export function FeatureModal({ feature, projectName, onClose }: FeatureModalProp
{/* Header */}
<div className="flex items-start justify-between p-6 border-b-3 border-[var(--color-neo-border)]">
<div>
<span className="neo-badge bg-[var(--color-neo-accent)] text-white mb-2">
<span
className="neo-badge mb-2"
style={{ backgroundColor: getCategoryColor(feature.category), color: 'var(--color-neo-text-on-bright)' }}
>
{feature.category}
</span>
<h2 className="font-display text-2xl font-bold">
@@ -78,12 +101,12 @@ export function FeatureModal({ feature, projectName, onClose }: FeatureModalProp
<div className="p-6 space-y-6">
{/* Error Message */}
{error && (
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-danger)] text-white border-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-3 p-4 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-3 border-[var(--color-neo-error-border)]">
<AlertCircle size={20} />
<span>{error}</span>
<button
onClick={() => setError(null)}
className="ml-auto"
className="ml-auto hover:opacity-70 transition-opacity"
>
<X size={16} />
</button>

View File

@@ -139,10 +139,10 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
return (
<div className="flex flex-col h-full max-h-[70vh]">
{/* Header with breadcrumb navigation */}
<div className="flex-shrink-0 p-4 border-b-3 border-[var(--color-neo-border)] bg-white">
<div className="flex-shrink-0 p-4 border-b-3 border-[var(--color-neo-border)] bg-[var(--color-neo-card)]">
<div className="flex items-center gap-2 mb-3">
<Folder size={20} className="text-[var(--color-neo-progress)]" />
<span className="font-bold text-[#1a1a1a]">Select Project Folder</span>
<span className="font-bold text-[var(--color-neo-text)]">Select Project Folder</span>
</div>
{/* Breadcrumb navigation */}
@@ -159,11 +159,11 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
{breadcrumbs.map((crumb, index) => (
<div key={crumb.path} className="flex items-center">
{index > 0 && <ChevronRight size={14} className="text-gray-400 mx-1" />}
{index > 0 && <ChevronRight size={14} className="text-[var(--color-neo-text-muted)] mx-1" />}
<button
onClick={() => handleNavigate(crumb.path)}
className={`
px-2 py-1 rounded text-[#1a1a1a]
px-2 py-1 rounded text-[var(--color-neo-text)]
hover:bg-[var(--color-neo-bg)]
${index === breadcrumbs.length - 1 ? 'font-bold' : ''}
`}
@@ -187,7 +187,7 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
className={`
neo-btn neo-btn-ghost py-1 px-2 text-sm
flex items-center gap-1
${currentPath?.startsWith(drive.letter) ? 'bg-[var(--color-neo-progress)] text-white' : ''}
${currentPath?.startsWith(drive.letter) ? 'bg-[var(--color-neo-progress)] text-[var(--color-neo-text-on-bright)]' : ''}
`}
>
<HardDrive size={14} />
@@ -199,7 +199,7 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
)}
{/* Directory listing */}
<div className="flex-1 overflow-y-auto p-2 bg-white">
<div className="flex-1 overflow-y-auto p-2 bg-[var(--color-neo-card)]">
{isLoading ? (
<div className="flex items-center justify-center p-8">
<Loader2 size={24} className="animate-spin text-[var(--color-neo-progress)]" />
@@ -238,9 +238,9 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
) : (
<Folder size={18} className="text-[var(--color-neo-pending)] flex-shrink-0" />
)}
<span className="truncate flex-1 text-[#1a1a1a]">{entry.name}</span>
<span className="truncate flex-1 text-[var(--color-neo-text)]">{entry.name}</span>
{entry.has_children && (
<ChevronRight size={14} className="ml-auto text-gray-400 flex-shrink-0" />
<ChevronRight size={14} className="ml-auto text-[var(--color-neo-text-muted)] flex-shrink-0" />
)}
</button>
))}
@@ -299,11 +299,11 @@ export function FolderBrowser({ onSelect, onCancel, initialPath }: FolderBrowser
</div>
{/* Footer with selected path and actions */}
<div className="flex-shrink-0 p-4 border-t-3 border-[var(--color-neo-border)] bg-white">
<div className="flex-shrink-0 p-4 border-t-3 border-[var(--color-neo-border)] bg-[var(--color-neo-card)]">
{/* Selected path display */}
<div className="mb-3 p-2 bg-[var(--color-neo-bg)] rounded border-2 border-[var(--color-neo-border)]">
<div className="text-xs text-[#4a4a4a] mb-1">Selected path:</div>
<div className="font-mono text-sm truncate text-[#1a1a1a]">{selectedPath || 'No folder selected'}</div>
<div className="text-xs text-[var(--color-neo-text-secondary)] mb-1">Selected path:</div>
<div className="font-mono text-sm truncate text-[var(--color-neo-text)]">{selectedPath || 'No folder selected'}</div>
{selectedPath && (
<div className="text-xs text-[var(--color-neo-text-secondary)] mt-2 italic">
This folder will contain all project files

View File

@@ -40,9 +40,9 @@ export function KanbanColumn({
style={{ backgroundColor: colorMap[color] }}
>
<div className="flex items-center justify-between">
<h2 className="font-display text-lg font-bold uppercase flex items-center gap-2 text-[var(--color-neo-text)]">
<h2 className="font-display text-lg font-bold uppercase flex items-center gap-2 text-[var(--color-neo-text-on-bright)]">
{title}
<span className="neo-badge bg-white text-[var(--color-neo-text)]">{count}</span>
<span className="neo-badge bg-[var(--color-neo-card)] text-[var(--color-neo-text)]">{count}</span>
</h2>
{(onAddFeature || onExpandProject) && (
<div className="flex items-center gap-2">
@@ -58,7 +58,7 @@ export function KanbanColumn({
{onExpandProject && showExpandButton && (
<button
onClick={onExpandProject}
className="neo-btn bg-[var(--color-neo-progress)] text-black text-sm py-1.5 px-2"
className="neo-btn bg-[var(--color-neo-progress)] text-[var(--color-neo-text-on-bright)] text-sm py-1.5 px-2"
title="Expand project with AI (E)"
>
<Sparkles size={16} />

View File

@@ -212,10 +212,10 @@ export function NewProjectModal({
<div className="flex items-center gap-3">
<Folder size={24} className="text-[var(--color-neo-progress)]" />
<div>
<h2 className="font-display font-bold text-xl text-[#1a1a1a]">
<h2 className="font-display font-bold text-xl text-[var(--color-neo-text)]">
Select Project Location
</h2>
<p className="text-sm text-[#4a4a4a]">
<p className="text-sm text-[var(--color-neo-text-secondary)]">
Select the folder to use for project <span className="font-bold font-mono">{projectName}</span>. Create a new folder or choose an existing one.
</p>
</div>
@@ -248,7 +248,7 @@ export function NewProjectModal({
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)]">
<h2 className="font-display font-bold text-xl text-[#1a1a1a]">
<h2 className="font-display font-bold text-xl text-[var(--color-neo-text)]">
{step === 'name' && 'Create New Project'}
{step === 'method' && 'Choose Setup Method'}
{step === 'complete' && 'Project Created!'}
@@ -267,7 +267,7 @@ export function NewProjectModal({
{step === 'name' && (
<form onSubmit={handleNameSubmit}>
<div className="mb-6">
<label className="block font-bold mb-2 text-[#1a1a1a]">
<label className="block font-bold mb-2 text-[var(--color-neo-text)]">
Project Name
</label>
<input
@@ -285,7 +285,7 @@ export function NewProjectModal({
</div>
{error && (
<div className="mb-4 p-3 bg-[var(--color-neo-danger)] text-white text-sm border-2 border-[var(--color-neo-border)]">
<div className="mb-4 p-3 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] text-sm border-3 border-[var(--color-neo-error-border)]">
{error}
</div>
)}
@@ -315,25 +315,25 @@ export function NewProjectModal({
<button
onClick={() => handleMethodSelect('claude')}
disabled={createProject.isPending}
className={`
className="
w-full text-left p-4
border-3 border-[var(--color-neo-border)]
bg-white
shadow-[4px_4px_0px_rgba(0,0,0,1)]
hover:translate-x-[-2px] hover:translate-y-[-2px]
hover:shadow-[6px_6px_0px_rgba(0,0,0,1)]
transition-all duration-150
disabled:opacity-50 disabled:cursor-not-allowed
`}
neo-card
"
>
<div className="flex items-start gap-4">
<div className="p-2 bg-[var(--color-neo-progress)] border-2 border-[var(--color-neo-border)] shadow-[2px_2px_0px_rgba(0,0,0,1)]">
<Bot size={24} className="text-white" />
<div
className="p-2 bg-[var(--color-neo-progress)] border-2 border-[var(--color-neo-border)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<Bot size={24} className="text-[var(--color-neo-text-on-bright)]" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-bold text-lg text-[#1a1a1a]">Create with Claude</span>
<span className="neo-badge bg-[var(--color-neo-done)] text-xs">
<span className="font-bold text-lg text-[var(--color-neo-text)]">Create with Claude</span>
<span className="neo-badge bg-[var(--color-neo-done)] text-[var(--color-neo-text-on-bright)] text-xs">
Recommended
</span>
</div>
@@ -348,23 +348,23 @@ export function NewProjectModal({
<button
onClick={() => handleMethodSelect('manual')}
disabled={createProject.isPending}
className={`
className="
w-full text-left p-4
border-3 border-[var(--color-neo-border)]
bg-white
shadow-[4px_4px_0px_rgba(0,0,0,1)]
hover:translate-x-[-2px] hover:translate-y-[-2px]
hover:shadow-[6px_6px_0px_rgba(0,0,0,1)]
transition-all duration-150
disabled:opacity-50 disabled:cursor-not-allowed
`}
neo-card
"
>
<div className="flex items-start gap-4">
<div className="p-2 bg-[var(--color-neo-pending)] border-2 border-[var(--color-neo-border)] shadow-[2px_2px_0px_rgba(0,0,0,1)]">
<FileEdit size={24} />
<div
className="p-2 bg-[var(--color-neo-pending)] border-2 border-[var(--color-neo-border)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<FileEdit size={24} className="text-[var(--color-neo-text-on-bright)]" />
</div>
<div className="flex-1">
<span className="font-bold text-lg text-[#1a1a1a]">Edit Templates Manually</span>
<span className="font-bold text-lg text-[var(--color-neo-text)]">Edit Templates Manually</span>
<p className="text-sm text-[var(--color-neo-text-secondary)] mt-1">
Edit the template files directly. Best for developers who want full control.
</p>
@@ -374,7 +374,7 @@ export function NewProjectModal({
</div>
{error && (
<div className="mt-4 p-3 bg-[var(--color-neo-danger)] text-white text-sm border-2 border-[var(--color-neo-border)]">
<div className="mt-4 p-3 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] text-sm border-3 border-[var(--color-neo-error-border)]">
{error}
</div>
)}
@@ -402,8 +402,11 @@ export function NewProjectModal({
{/* Step 3: Complete */}
{step === 'complete' && (
<div className="text-center py-8">
<div className="inline-flex items-center justify-center w-16 h-16 bg-[var(--color-neo-done)] border-3 border-[var(--color-neo-border)] shadow-[4px_4px_0px_rgba(0,0,0,1)] mb-4">
<CheckCircle2 size={32} />
<div
className="inline-flex items-center justify-center w-16 h-16 bg-[var(--color-neo-done)] border-3 border-[var(--color-neo-border)] mb-4"
style={{ boxShadow: 'var(--shadow-neo-md)' }}
>
<CheckCircle2 size={32} className="text-[var(--color-neo-text-on-bright)]" />
</div>
<h3 className="font-display font-bold text-xl mb-2">
{projectName}

View File

@@ -36,11 +36,13 @@ export function ProgressDashboard({
{/* Large Percentage */}
<div className="text-center mb-6">
<span className="font-display text-6xl font-bold">
{percentage.toFixed(1)}
</span>
<span className="font-display text-3xl font-bold text-[var(--color-neo-text-secondary)]">
%
<span className="inline-flex items-baseline">
<span className="font-display text-6xl font-bold">
{percentage.toFixed(1)}
</span>
<span className="font-display text-3xl font-bold text-[var(--color-neo-text-secondary)]">
%
</span>
</span>
</div>

View File

@@ -65,7 +65,7 @@ export function ProjectSelector({
{/* Dropdown Trigger */}
<button
onClick={() => setIsOpen(!isOpen)}
className="neo-btn bg-white text-[var(--color-neo-text)] min-w-[200px] justify-between"
className="neo-btn bg-[var(--color-neo-card)] text-[var(--color-neo-text)] min-w-[200px] justify-between"
disabled={isLoading}
>
{isLoading ? (
@@ -108,7 +108,7 @@ export function ProjectSelector({
key={project.name}
className={`flex items-center ${
project.name === selectedProject
? 'bg-[var(--color-neo-pending)]'
? 'bg-[var(--color-neo-pending)] text-[var(--color-neo-text-on-bright)]'
: ''
}`}
>

View File

@@ -93,11 +93,11 @@ export function QuestionOptions({
{questions.map((q, questionIdx) => (
<div
key={questionIdx}
className="neo-card p-4 bg-white"
className="neo-card p-4 bg-[var(--color-neo-card)]"
>
{/* Question header */}
<div className="flex items-center gap-3 mb-4">
<span className="neo-badge bg-[var(--color-neo-accent)] text-white">
<span className="neo-badge bg-[var(--color-neo-accent)] text-[var(--color-neo-text-on-bright)]">
{q.header}
</span>
<span className="font-bold text-[var(--color-neo-text)]">
@@ -126,11 +126,24 @@ export function QuestionOptions({
transition-all duration-150
${
isSelected
? 'bg-[var(--color-neo-pending)] shadow-[2px_2px_0px_rgba(0,0,0,1)] translate-x-[1px] translate-y-[1px]'
: 'bg-white shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]'
? 'bg-[var(--color-neo-pending)] translate-x-[1px] translate-y-[1px]'
: 'bg-[var(--color-neo-card)] hover:translate-x-[-1px] hover:translate-y-[-1px]'
}
disabled:opacity-50 disabled:cursor-not-allowed
`}
style={{
boxShadow: isSelected ? 'var(--shadow-neo-sm)' : 'var(--shadow-neo-md)',
}}
onMouseEnter={(e) => {
if (!isSelected && !disabled) {
e.currentTarget.style.boxShadow = 'var(--shadow-neo-lg)'
}
}}
onMouseLeave={(e) => {
if (!isSelected && !disabled) {
e.currentTarget.style.boxShadow = 'var(--shadow-neo-md)'
}
}}
>
<div className="flex items-start gap-2">
{/* Checkbox/Radio indicator */}
@@ -140,7 +153,7 @@ export function QuestionOptions({
border-2 border-[var(--color-neo-border)]
flex items-center justify-center
${q.multiSelect ? '' : 'rounded-full'}
${isSelected ? 'bg-[var(--color-neo-done)]' : 'bg-white'}
${isSelected ? 'bg-[var(--color-neo-done)]' : 'bg-[var(--color-neo-card)]'}
`}
>
{isSelected && <Check size={12} strokeWidth={3} />}
@@ -169,11 +182,24 @@ export function QuestionOptions({
transition-all duration-150
${
showCustomInput[String(questionIdx)]
? 'bg-[var(--color-neo-pending)] shadow-[2px_2px_0px_rgba(0,0,0,1)] translate-x-[1px] translate-y-[1px]'
: 'bg-white shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]'
? 'bg-[var(--color-neo-pending)] translate-x-[1px] translate-y-[1px]'
: 'bg-[var(--color-neo-card)] hover:translate-x-[-1px] hover:translate-y-[-1px]'
}
disabled:opacity-50 disabled:cursor-not-allowed
`}
style={{
boxShadow: showCustomInput[String(questionIdx)] ? 'var(--shadow-neo-sm)' : 'var(--shadow-neo-md)',
}}
onMouseEnter={(e) => {
if (!showCustomInput[String(questionIdx)] && !disabled) {
e.currentTarget.style.boxShadow = 'var(--shadow-neo-lg)'
}
}}
onMouseLeave={(e) => {
if (!showCustomInput[String(questionIdx)] && !disabled) {
e.currentTarget.style.boxShadow = 'var(--shadow-neo-md)'
}
}}
>
<div className="flex items-start gap-2">
<div
@@ -182,7 +208,7 @@ export function QuestionOptions({
border-2 border-[var(--color-neo-border)]
flex items-center justify-center
${q.multiSelect ? '' : 'rounded-full'}
${showCustomInput[String(questionIdx)] ? 'bg-[var(--color-neo-done)]' : 'bg-white'}
${showCustomInput[String(questionIdx)] ? 'bg-[var(--color-neo-done)]' : 'bg-[var(--color-neo-card)]'}
`}
>
{showCustomInput[String(questionIdx)] && <Check size={12} strokeWidth={3} />}

View File

@@ -115,14 +115,14 @@ export function SettingsModal({ onClose }: SettingsModalProps) {
{/* Error State */}
{isError && (
<div className="p-4 bg-[var(--color-neo-danger)] text-white border-3 border-[var(--color-neo-border)] mb-4">
<div className="p-4 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-3 border-[var(--color-neo-error-border)] mb-4">
<div className="flex items-center gap-2">
<AlertCircle size={18} />
<span>Failed to load settings</span>
</div>
<button
onClick={() => refetch()}
className="mt-2 underline text-sm"
className="mt-2 underline text-sm hover:opacity-70 transition-opacity"
>
Retry
</button>
@@ -152,7 +152,7 @@ export function SettingsModal({ onClose }: SettingsModalProps) {
className={`relative w-14 h-8 rounded-none border-3 border-[var(--color-neo-border)] transition-colors ${
settings.yolo_mode
? 'bg-[var(--color-neo-pending)]'
: 'bg-white'
: 'bg-[var(--color-neo-card)]'
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
role="switch"
aria-checked={settings.yolo_mode}
@@ -189,8 +189,8 @@ export function SettingsModal({ onClose }: SettingsModalProps) {
aria-checked={settings.model === model.id}
className={`flex-1 py-3 px-4 font-display font-bold text-sm transition-colors ${
settings.model === model.id
? 'bg-[var(--color-neo-accent)] text-white'
: 'bg-white text-[var(--color-neo-text)] hover:bg-gray-100'
? 'bg-[var(--color-neo-accent)] text-[var(--color-neo-text-on-bright)]'
: 'bg-[var(--color-neo-card)] text-[var(--color-neo-text)] hover:bg-[var(--color-neo-hover-subtle)]'
} ${isSaving ? 'opacity-50 cursor-not-allowed' : ''}`}
>
{model.name}
@@ -201,7 +201,7 @@ export function SettingsModal({ onClose }: SettingsModalProps) {
{/* Update Error */}
{updateSettings.isError && (
<div className="p-3 bg-red-50 border-3 border-red-200 text-red-700 text-sm">
<div className="p-3 bg-[var(--color-neo-error-bg)] border-3 border-[var(--color-neo-error-border)] text-[var(--color-neo-error-text)] text-sm">
Failed to save settings. Please try again.
</div>
)}

View File

@@ -108,7 +108,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
{/* Error Message */}
{(healthError || setupError) && (
<div className="mt-6 p-4 bg-[var(--color-neo-danger)] text-white border-3 border-[var(--color-neo-border)]">
<div className="mt-6 p-4 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-3 border-[var(--color-neo-error-border)]">
<p className="font-bold mb-2">Setup Error</p>
<p className="text-sm">
{healthError

View File

@@ -207,9 +207,9 @@ export function SpecCreationChat({
return (
<div className="flex flex-col h-full bg-[var(--color-neo-bg)]">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)] bg-white">
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)] bg-[var(--color-neo-card)]">
<div className="flex items-center gap-3">
<h2 className="font-display font-bold text-lg text-[#1a1a1a]">
<h2 className="font-display font-bold text-lg text-[var(--color-neo-text)]">
Create Spec: {projectName}
</h2>
<ConnectionIndicator />
@@ -245,12 +245,12 @@ export function SpecCreationChat({
{/* Error banner */}
{error && (
<div className="flex items-center gap-2 p-3 bg-[var(--color-neo-danger)] text-white border-b-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-2 p-3 bg-[var(--color-neo-error-bg)] text-[var(--color-neo-error-text)] border-b-3 border-[var(--color-neo-error-border)]">
<AlertCircle size={16} />
<span className="flex-1 text-sm">{error}</span>
<button
onClick={() => setError(null)}
className="p-1 hover:bg-white/20 rounded"
className="p-1 hover:opacity-70 transition-opacity rounded"
>
<X size={14} />
</button>
@@ -304,7 +304,7 @@ export function SpecCreationChat({
{/* Input area */}
{!isComplete && (
<div
className="p-4 border-t-3 border-[var(--color-neo-border)] bg-white"
className="p-4 border-t-3 border-[var(--color-neo-border)] bg-[var(--color-neo-card)]"
onDrop={handleDrop}
onDragOver={handleDragOver}
>
@@ -314,7 +314,8 @@ export function SpecCreationChat({
{pendingAttachments.map((attachment) => (
<div
key={attachment.id}
className="relative group border-2 border-[var(--color-neo-border)] p-1 bg-white shadow-[2px_2px_0px_rgba(0,0,0,1)]"
className="relative group border-2 border-[var(--color-neo-border)] p-1 bg-[var(--color-neo-card)]"
style={{ boxShadow: 'var(--shadow-neo-sm)' }}
>
<img
src={attachment.previewUrl}
@@ -323,7 +324,7 @@ export function SpecCreationChat({
/>
<button
onClick={() => handleRemoveAttachment(attachment.id)}
className="absolute -top-2 -right-2 bg-[var(--color-neo-danger)] text-white rounded-full p-0.5 border-2 border-[var(--color-neo-border)] hover:scale-110 transition-transform"
className="absolute -top-2 -right-2 bg-[var(--color-neo-danger)] text-[var(--color-neo-text-on-bright)] rounded-full p-0.5 border-2 border-[var(--color-neo-border)] hover:scale-110 transition-transform"
title="Remove attachment"
>
<X size={12} />
@@ -409,22 +410,22 @@ export function SpecCreationChat({
<div className="flex items-center gap-2">
{initializerStatus === 'starting' ? (
<>
<Loader2 size={20} className="animate-spin" />
<span className="font-bold">
<Loader2 size={20} className="animate-spin text-[var(--color-neo-text-on-bright)]" />
<span className="font-bold text-[var(--color-neo-text-on-bright)]">
Starting agent{yoloEnabled ? ' (YOLO mode)' : ''}...
</span>
</>
) : initializerStatus === 'error' ? (
<>
<AlertCircle size={20} className="text-white" />
<span className="font-bold text-white">
<AlertCircle size={20} className="text-[var(--color-neo-text-on-bright)]" />
<span className="font-bold text-[var(--color-neo-text-on-bright)]">
{initializerError || 'Failed to start agent'}
</span>
</>
) : (
<>
<CheckCircle2 size={20} />
<span className="font-bold">Specification created successfully!</span>
<CheckCircle2 size={20} className="text-[var(--color-neo-text-on-bright)]" />
<span className="font-bold text-[var(--color-neo-text-on-bright)]">Specification created successfully!</span>
</>
)}
</div>
@@ -432,7 +433,7 @@ export function SpecCreationChat({
{initializerStatus === 'error' && onRetryInitializer && (
<button
onClick={onRetryInitializer}
className="neo-btn bg-white"
className="neo-btn bg-[var(--color-neo-card)]"
>
<RotateCcw size={14} />
Retry
@@ -444,7 +445,7 @@ export function SpecCreationChat({
<button
onClick={() => setYoloEnabled(!yoloEnabled)}
className={`neo-btn text-sm py-2 px-3 ${
yoloEnabled ? 'neo-btn-warning' : 'bg-white'
yoloEnabled ? 'neo-btn-warning' : 'bg-[var(--color-neo-card)]'
}`}
title="YOLO Mode: Skip testing for rapid prototyping"
>

View File

@@ -165,7 +165,7 @@ export function TerminalTabs({
${
activeTerminalId === terminal.id
? 'bg-neo-progress text-black'
: 'bg-[#3a3a3a] text-white hover:bg-[#4a4a4a]'
: 'bg-[#3a3a3a] text-white hover:bg-[var(--color-neo-hover-subtle)]'
}
`}
onClick={() => onSelect(terminal.id)}
@@ -180,7 +180,7 @@ export function TerminalTabs({
onChange={(e) => setEditValue(e.target.value)}
onBlur={submitEdit}
onKeyDown={handleKeyDown}
className="bg-white text-black px-1 py-0 text-sm font-mono border-2 border-black w-24 outline-none"
className="bg-neo-card text-neo-text px-1 py-0 text-sm font-mono border-2 border-black w-24 outline-none"
onClick={(e) => e.stopPropagation()}
/>
) : (
@@ -212,7 +212,7 @@ export function TerminalTabs({
{/* Add new terminal button */}
<button
onClick={onCreate}
className="flex items-center justify-center w-8 h-8 border-2 border-black bg-[#3a3a3a] text-white hover:bg-[#4a4a4a] transition-colors"
className="flex items-center justify-center w-8 h-8 border-2 border-black bg-[#3a3a3a] text-white hover:bg-[var(--color-neo-hover-subtle)] transition-colors"
title="New terminal"
>
<Plus className="w-4 h-4" />
@@ -222,8 +222,8 @@ export function TerminalTabs({
{contextMenu.visible && (
<div
ref={contextMenuRef}
className="fixed z-50 bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] py-1 min-w-[120px]"
style={{ left: contextMenu.x, top: contextMenu.y }}
className="fixed z-50 bg-neo-card border-2 border-[var(--color-neo-border)] py-1 min-w-[120px]"
style={{ left: contextMenu.x, top: contextMenu.y, boxShadow: 'var(--shadow-neo-md)' }}
>
<button
onClick={handleContextMenuRename}

View File

@@ -1,11 +1,16 @@
@import "tailwindcss";
/* Enable class-based dark mode in Tailwind v4 */
@custom-variant dark (&:where(.dark, .dark *));
/* ============================================================================
Neobrutalism Design System
============================================================================ */
@theme {
/* Colors */
/* -------------------------------------------------------------------------
Colors - Primary palette
------------------------------------------------------------------------- */
--color-neo-bg: #fffef5;
--color-neo-card: #ffffff;
--color-neo-pending: #ffd60a;
@@ -16,53 +21,195 @@
--color-neo-border: #1a1a1a;
--color-neo-text: #1a1a1a;
--color-neo-text-secondary: #4a4a4a;
--color-neo-text-muted: #6b6b6b;
/* Fonts */
--font-neo-display: 'Space Grotesk', sans-serif;
--font-neo-sans: 'DM Sans', sans-serif;
--font-neo-mono: 'JetBrains Mono', monospace;
/* Dark text for bright backgrounds (always dark regardless of theme) */
--color-neo-text-on-bright: #1a1a1a;
/* Shadows */
--shadow-neo-sm: 2px 2px 0px rgba(0, 0, 0, 1);
--shadow-neo-md: 4px 4px 0px rgba(0, 0, 0, 1);
--shadow-neo-lg: 6px 6px 0px rgba(0, 0, 0, 1);
--shadow-neo-xl: 8px 8px 0px rgba(0, 0, 0, 1);
/* -------------------------------------------------------------------------
Colors - Neutral scale
------------------------------------------------------------------------- */
--color-neo-neutral-50: #fafafa;
--color-neo-neutral-100: #f5f5f5;
--color-neo-neutral-200: #e5e5e5;
--color-neo-neutral-300: #d4d4d4;
--color-neo-neutral-400: #a3a3a3;
--color-neo-neutral-500: #737373;
--color-neo-neutral-600: #525252;
--color-neo-neutral-700: #404040;
--color-neo-neutral-800: #262626;
--color-neo-neutral-900: #171717;
/* Transitions */
/* -------------------------------------------------------------------------
Colors - Semantic states
------------------------------------------------------------------------- */
--color-neo-error-bg: #fef2f2;
--color-neo-error-border: #fecaca;
--color-neo-error-text: #dc2626;
--color-neo-error: #dc2626;
--color-neo-surface: #ffffff;
/* Hover states */
--color-neo-hover-subtle: rgba(0, 0, 0, 0.05);
/* -------------------------------------------------------------------------
Colors - Log levels
------------------------------------------------------------------------- */
--color-neo-log-error: #ef4444;
--color-neo-log-warning: #f59e0b;
--color-neo-log-info: #3b82f6;
--color-neo-log-debug: #6b7280;
--color-neo-log-success: #22c55e;
/* GLM/Alternative API badge */
--color-neo-glm: #8b5cf6;
/* Additional semantic colors */
--color-neo-category-pink: #ff006e;
--color-neo-category-cyan: #00b4d8;
--color-neo-category-green: #70e000;
--color-neo-category-yellow: #ffd60a;
--color-neo-category-orange: #ff5400;
--color-neo-category-purple: #8338ec;
--color-neo-category-blue: #3a86ff;
/* -------------------------------------------------------------------------
Typography - Font stacks with proper fallbacks
------------------------------------------------------------------------- */
--font-neo-display: 'Archivo Black', 'Bebas Neue', 'Impact', system-ui, sans-serif;
--font-neo-sans: 'Work Sans', 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
--font-neo-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
/* -------------------------------------------------------------------------
Shadows - Solid black for light mode
------------------------------------------------------------------------- */
--shadow-neo-sm: 2px 2px 0px #1a1a1a;
--shadow-neo-md: 4px 4px 0px #1a1a1a;
--shadow-neo-lg: 6px 6px 0px #1a1a1a;
--shadow-neo-xl: 8px 8px 0px #1a1a1a;
--shadow-neo-left: -4px 0px 0px #1a1a1a;
--shadow-neo-left-lg: -8px 0px 0px #1a1a1a;
--shadow-neo-inset: inset 2px 2px 0 var(--color-neo-border);
--shadow-neo-inset-lg: inset 4px 4px 0 var(--color-neo-border);
/* -------------------------------------------------------------------------
Transitions
------------------------------------------------------------------------- */
--transition-neo-fast: 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
--transition-neo-normal: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
/* Transition timing */
--transition-fast: 150ms;
--transition-normal: 250ms;
--transition-slow: 400ms;
--transition-slower: 600ms;
/* Easing functions */
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
/* -------------------------------------------------------------------------
Spacing scale
------------------------------------------------------------------------- */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
/* -------------------------------------------------------------------------
Z-index scale
------------------------------------------------------------------------- */
--z-dropdown: 100;
--z-sticky: 200;
--z-modal-backdrop: 300;
--z-modal: 400;
--z-tooltip: 500;
--z-toast: 600;
/* -------------------------------------------------------------------------
Border radius tokens
------------------------------------------------------------------------- */
--radius-sm: 0.125rem;
--radius-md: 0.25rem;
--radius-lg: 0.5rem;
--radius-full: 9999px;
}
/* ============================================================================
Base Layer - Foundation styles
============================================================================ */
@layer base {
/* Ensure CSS variables are available globally */
:root {
--color-neo-bg: #fffef5;
--color-neo-card: #ffffff;
--color-neo-pending: #ffd60a;
--color-neo-progress: #00b4d8;
--color-neo-done: #70e000;
--color-neo-accent: #ff006e;
--color-neo-danger: #ff5400;
--color-neo-border: #1a1a1a;
--color-neo-text: #1a1a1a;
--color-neo-text-secondary: #4a4a4a;
--font-neo-display: 'Space Grotesk', sans-serif;
--font-neo-sans: 'DM Sans', sans-serif;
--font-neo-mono: 'JetBrains Mono', monospace;
--shadow-neo-sm: 2px 2px 0px rgba(0, 0, 0, 1);
--shadow-neo-md: 4px 4px 0px rgba(0, 0, 0, 1);
--shadow-neo-lg: 6px 6px 0px rgba(0, 0, 0, 1);
--shadow-neo-xl: 8px 8px 0px rgba(0, 0, 0, 1);
}
/* Dark mode - must be outside @layer to override @theme values */
.dark {
--color-neo-bg: #121212;
--color-neo-card: #1e1e1e;
--color-neo-pending: #ffca28;
--color-neo-progress: #26c6da;
--color-neo-done: #66bb6a;
--color-neo-accent: #f50057;
--color-neo-danger: #ff6e40;
--color-neo-border: #e0e0e0;
--color-neo-text: #f5f5f5;
--color-neo-text-secondary: #b0b0b0;
--color-neo-text-muted: #757575;
/* Dark text for bright backgrounds (unchanged in dark mode) */
--color-neo-text-on-bright: #1a1a1a;
/* Neutral scale for dark mode (inverted) */
--color-neo-neutral-50: #171717;
--color-neo-neutral-100: #262626;
--color-neo-neutral-200: #404040;
--color-neo-neutral-300: #525252;
--color-neo-neutral-400: #737373;
--color-neo-neutral-500: #a3a3a3;
--color-neo-neutral-600: #d4d4d4;
--color-neo-neutral-700: #e5e5e5;
--color-neo-neutral-800: #f5f5f5;
--color-neo-neutral-900: #fafafa;
/* Semantic error states for dark mode */
--color-neo-error-bg: rgba(244, 67, 54, 0.15);
--color-neo-error-border: rgba(244, 67, 54, 0.4);
--color-neo-error-text: #ef5350;
--color-neo-error: #ef5350;
--color-neo-surface: #1e1e1e;
/* Hover states */
--color-neo-hover-subtle: rgba(255, 255, 255, 0.08);
/* Log level colors for dark mode - adjusted for dark backgrounds */
--color-neo-log-error: #f87171;
--color-neo-log-warning: #fbbf24;
--color-neo-log-info: #60a5fa;
--color-neo-log-debug: #9ca3af;
--color-neo-log-success: #4ade80;
/* GLM/Alternative API badge for dark mode */
--color-neo-glm: #a78bfa;
/* Shadows - lighter for dark mode with subtle glow */
--shadow-neo-sm: 2px 2px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-md: 4px 4px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-lg: 6px 6px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-xl: 8px 8px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-left: -4px 0px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-left-lg: -8px 0px 0px rgba(224, 224, 224, 0.7);
--shadow-neo-inset: inset 2px 2px 0 var(--color-neo-border);
--shadow-neo-inset-lg: inset 4px 4px 0 var(--color-neo-border);
}
@layer base {
body {
font-family: var(--font-neo-sans);
background-color: var(--color-neo-bg);
color: var(--color-neo-text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* Force form elements to inherit colors properly */
@@ -93,6 +240,20 @@
box-shadow: var(--shadow-neo-lg);
}
/* Card Elevation Variants */
.neo-card-elevated {
box-shadow: var(--shadow-neo-lg);
}
.neo-card-flat {
box-shadow: none;
border-width: 2px;
}
.neo-card-sunken {
box-shadow: var(--shadow-neo-inset);
}
/* Buttons */
.neo-btn {
display: inline-flex;
@@ -105,9 +266,9 @@
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.025em;
color: #1a1a1a;
background-color: #ffffff;
border: 3px solid #1a1a1a;
color: var(--color-neo-text);
background-color: var(--color-neo-card);
border: 3px solid var(--color-neo-border);
box-shadow: var(--shadow-neo-md);
transition: transform var(--transition-neo-fast), box-shadow var(--transition-neo-fast);
cursor: pointer;
@@ -126,8 +287,43 @@
.neo-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: var(--shadow-neo-sm);
transform: none !important;
box-shadow: var(--shadow-neo-sm) !important;
}
/* Button Size Variants */
.neo-btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
}
.neo-btn-lg {
padding: 0.875rem 1.75rem;
font-size: 1.125rem;
}
.neo-btn-icon {
padding: 0.5rem;
aspect-ratio: 1;
}
/* Button Loading State */
.neo-btn-loading {
position: relative;
color: transparent;
}
.neo-btn-loading::after {
content: '';
position: absolute;
inset: 0;
margin: auto;
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--color-neo-border);
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
.neo-btn-primary {
@@ -152,40 +348,100 @@
.neo-btn-ghost {
background-color: transparent;
color: #1a1a1a;
color: var(--color-neo-text);
box-shadow: none;
}
.neo-btn-ghost:hover {
background-color: rgba(0, 0, 0, 0.05);
color: #1a1a1a;
background-color: rgba(128, 128, 128, 0.1);
color: var(--color-neo-text);
box-shadow: none;
transform: none;
}
/* YOLO Mode Button - Animated fire effect for when YOLO mode is enabled */
.neo-btn-yolo {
background: linear-gradient(
0deg,
#8b0000 0%,
#d64500 30%,
#ff6a00 60%,
#ffa500 100%
);
background-size: 100% 200%;
position: relative;
will-change: transform, filter;
background:
radial-gradient(ellipse at 20% 80%, rgba(255, 200, 0, 0.4) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(255, 150, 0, 0.3) 0%, transparent 50%),
radial-gradient(ellipse at 50% 90%, rgba(255, 100, 0, 0.5) 0%, transparent 40%),
linear-gradient(
0deg,
#8b0000 0%,
#c43500 20%,
#d64500 35%,
#ff6a00 55%,
#ff8c00 75%,
#ffa500 90%,
#ffcc00 100%
);
background-size: 100% 100%, 100% 100%, 100% 100%, 100% 300%;
color: #ffffff;
animation: fireGlow 0.8s ease-in-out infinite, fireGradient 1.5s ease-in-out infinite;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 0 10px rgba(255, 200, 0, 0.5);
animation:
fireGlow 0.8s ease-in-out infinite,
fireGradient 2s ease-in-out infinite,
fireFlicker 0.3s ease-in-out infinite;
overflow: visible;
}
.neo-btn-yolo::before {
content: '';
position: absolute;
inset: -2px;
background:
radial-gradient(circle at 30% 100%, rgba(255, 200, 0, 0.6) 0%, transparent 30%),
radial-gradient(circle at 70% 100%, rgba(255, 150, 0, 0.5) 0%, transparent 25%),
radial-gradient(circle at 50% 100%, rgba(255, 100, 0, 0.7) 0%, transparent 35%);
background-size: 100% 200%;
z-index: -1;
animation: fireParticles 1.5s ease-in-out infinite;
filter: blur(4px);
opacity: 0.8;
}
.neo-btn-yolo::after {
content: '';
position: absolute;
top: -4px;
left: 10%;
right: 10%;
height: 8px;
background: linear-gradient(90deg,
transparent 0%,
rgba(255, 220, 0, 0.6) 20%,
rgba(255, 255, 200, 0.8) 50%,
rgba(255, 220, 0, 0.6) 80%,
transparent 100%
);
filter: blur(3px);
animation: fireFlicker 0.4s ease-in-out infinite reverse;
opacity: 0.9;
}
.neo-btn-yolo:hover {
background: linear-gradient(
0deg,
#a00000 0%,
#e65c00 30%,
#ff7800 60%,
#ffb700 100%
);
background-size: 100% 200%;
background:
radial-gradient(ellipse at 20% 80%, rgba(255, 220, 0, 0.5) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(255, 180, 0, 0.4) 0%, transparent 50%),
radial-gradient(ellipse at 50% 90%, rgba(255, 120, 0, 0.6) 0%, transparent 40%),
linear-gradient(
0deg,
#a00000 0%,
#d64500 20%,
#e65c00 35%,
#ff7800 55%,
#ff9c00 75%,
#ffb700 90%,
#ffdd00 100%
);
background-size: 100% 100%, 100% 100%, 100% 100%, 100% 300%;
}
.neo-btn-yolo:hover::before {
opacity: 1;
filter: blur(6px);
}
/* Inputs */
@@ -194,15 +450,15 @@
padding: 0.75rem 1rem;
font-family: var(--font-neo-sans);
font-size: 1rem;
color: #1a1a1a;
background-color: #ffffff;
border: 3px solid #1a1a1a;
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.1);
color: var(--color-neo-text);
background-color: var(--color-neo-card);
border: 3px solid var(--color-neo-border);
box-shadow: 3px 3px 0px rgba(128, 128, 128, 0.2);
transition: transform var(--transition-neo-fast), box-shadow var(--transition-neo-fast);
}
.neo-input::placeholder {
color: #4a4a4a;
color: var(--color-neo-text-secondary);
opacity: 0.7;
}
@@ -213,6 +469,23 @@
border-color: #ff006e;
}
/* Input Variants */
.neo-input-error {
border-color: var(--color-neo-error);
background-color: color-mix(in srgb, var(--color-neo-error) 5%, var(--color-neo-surface));
}
.neo-input:disabled {
opacity: 0.6;
cursor: not-allowed;
background-color: var(--color-neo-neutral-200);
}
.neo-textarea {
min-height: 6rem;
resize: vertical;
}
/* Badge */
.neo-badge {
display: inline-flex;
@@ -222,53 +495,105 @@
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
color: #1a1a1a;
border: 2px solid #1a1a1a;
color: var(--color-neo-text);
border: 2px solid var(--color-neo-border);
}
/* Badge Color Variants */
.neo-badge-success {
background-color: var(--color-neo-done);
color: var(--color-neo-text-on-bright);
}
.neo-badge-warning {
background-color: var(--color-neo-pending);
color: var(--color-neo-text-on-bright);
}
.neo-badge-error {
background-color: var(--color-neo-error);
color: white;
}
.neo-badge-info {
background-color: var(--color-neo-progress);
color: var(--color-neo-text-on-bright);
}
/* Badge Size Variants */
.neo-badge-sm {
padding: 0.125rem 0.375rem;
font-size: 0.625rem;
}
.neo-badge-lg {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
/* Progress Bar */
.neo-progress {
width: 100%;
height: 2rem;
background-color: #ffffff;
border: 3px solid #1a1a1a;
background-color: var(--color-neo-card);
border: 3px solid var(--color-neo-border);
box-shadow: var(--shadow-neo-sm);
overflow: hidden;
}
.neo-progress-fill {
height: 100%;
background-color: #70e000;
background-color: var(--color-neo-done);
transition: width 0.5s ease-out;
position: relative;
}
/* Progress Bar Shimmer Effect */
.neo-progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255,255,255,0.2) 50%,
transparent 100%
);
animation: shimmer 2s infinite;
}
/* Modal */
.neo-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(2px);
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 50;
z-index: var(--z-modal-backdrop);
}
.neo-modal {
background-color: #ffffff;
border: 4px solid #1a1a1a;
background-color: var(--color-neo-card);
border: 4px solid var(--color-neo-border);
box-shadow: var(--shadow-neo-xl);
animation: popIn 0.3s var(--transition-neo-fast);
max-width: 90vw;
max-height: 90vh;
overflow: auto;
z-index: var(--z-modal);
}
/* Dropdown */
.neo-dropdown {
background-color: #ffffff;
border: 3px solid #1a1a1a;
background-color: var(--color-neo-card);
border: 3px solid var(--color-neo-border);
box-shadow: var(--shadow-neo-lg);
z-index: var(--z-dropdown);
}
.neo-dropdown-item {
@@ -276,7 +601,7 @@
width: 100%;
padding: 0.75rem 1rem;
cursor: pointer;
color: #1a1a1a;
color: var(--color-neo-text);
background-color: transparent;
text-align: left;
border: none;
@@ -285,26 +610,27 @@
}
.neo-dropdown-item:hover {
background-color: #ffd60a;
background-color: var(--color-neo-pending);
color: #1a1a1a;
}
/* Tooltip */
/* Tooltip - Neobrutalism style with light bg and dark text */
.neo-tooltip {
background-color: #1a1a1a;
color: #ffffff;
background: var(--color-neo-surface);
color: var(--color-neo-text);
border: 2px solid var(--color-neo-border);
box-shadow: var(--shadow-neo-sm);
padding: 0.5rem 0.75rem;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
border: 2px solid #1a1a1a;
box-shadow: var(--shadow-neo-sm);
z-index: var(--z-tooltip);
}
/* Empty State */
.neo-empty-state {
background-color: var(--color-neo-bg);
border: 4px dashed #1a1a1a;
border: 4px dashed var(--color-neo-border);
padding: 2rem;
text-align: center;
}
@@ -383,36 +709,165 @@
0%, 100% {
box-shadow:
4px 4px 0 var(--color-neo-border),
0 0 10px rgba(255, 100, 0, 0.5),
0 0 20px rgba(255, 60, 0, 0.3);
0 0 10px rgba(255, 100, 0, 0.6),
0 0 20px rgba(255, 60, 0, 0.4),
0 -5px 15px rgba(255, 200, 0, 0.3),
inset 0 -2px 8px rgba(255, 200, 0, 0.2);
}
25% {
box-shadow:
4px 4px 0 var(--color-neo-border),
0 0 15px rgba(255, 80, 0, 0.6),
0 0 30px rgba(255, 40, 0, 0.4);
0 0 18px rgba(255, 80, 0, 0.7),
0 0 35px rgba(255, 40, 0, 0.5),
0 -8px 20px rgba(255, 180, 0, 0.4),
inset 0 -3px 12px rgba(255, 180, 0, 0.25);
}
50% {
box-shadow:
4px 4px 0 var(--color-neo-border),
0 0 12px rgba(255, 120, 0, 0.7),
0 0 25px rgba(255, 50, 0, 0.5);
0 0 14px rgba(255, 120, 0, 0.8),
0 0 28px rgba(255, 50, 0, 0.6),
0 -6px 18px rgba(255, 220, 0, 0.35),
inset 0 -2px 10px rgba(255, 220, 0, 0.3);
}
75% {
box-shadow:
4px 4px 0 var(--color-neo-border),
0 0 18px rgba(255, 70, 0, 0.55),
0 0 35px rgba(255, 30, 0, 0.35);
0 0 22px rgba(255, 70, 0, 0.65),
0 0 40px rgba(255, 30, 0, 0.45),
0 -10px 25px rgba(255, 160, 0, 0.45),
inset 0 -4px 14px rgba(255, 160, 0, 0.2);
}
}
@keyframes fireGradient {
0%, 100% {
0% {
background-position: 0% 100%;
}
25% {
background-position: 50% 50%;
}
50% {
background-position: 100% 0%;
}
75% {
background-position: 50% 50%;
}
100% {
background-position: 0% 100%;
}
}
@keyframes fireFlicker {
0%, 100% {
filter: brightness(1) saturate(1);
}
25% {
filter: brightness(1.1) saturate(1.2);
}
50% {
filter: brightness(0.95) saturate(1.1);
}
75% {
filter: brightness(1.15) saturate(1.15);
}
}
@keyframes fireParticles {
0% {
background-position: 0% 100%, 50% 100%, 100% 100%;
}
50% {
background-position: 25% 0%, 50% 25%, 75% 0%;
}
100% {
background-position: 0% 100%, 50% 100%, 100% 100%;
}
}
@keyframes slide-in-top {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-in-bottom {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-in-left {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes bounce-in {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes shake {
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-4px);
}
20%, 40%, 60%, 80% {
transform: translateX(4px);
}
}
@keyframes scale-pop {
0% {
transform: scale(0.95);
opacity: 0;
}
40% {
transform: scale(1.02);
}
100% {
transform: scale(1);
opacity: 1;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* ============================================================================
@@ -456,6 +911,54 @@
animation: shimmer 2s ease-in-out infinite;
}
/* Slide animations */
.animate-slide-in-top {
animation: slide-in-top var(--transition-normal) var(--ease-out-back);
}
.animate-slide-in-bottom {
animation: slide-in-bottom var(--transition-normal) var(--ease-out-back);
}
.animate-slide-in-left {
animation: slide-in-left var(--transition-normal) var(--ease-out-back);
}
/* Bounce and pop animations */
.animate-bounce-in {
animation: bounce-in var(--transition-slow) var(--ease-bounce);
}
.animate-scale-pop {
animation: scale-pop var(--transition-normal) var(--ease-out-back);
}
/* Error shake animation */
.animate-shake {
animation: shake 0.5s ease-in-out;
}
/* Stagger delay utilities for sequential animations */
.stagger-1 {
animation-delay: 50ms;
}
.stagger-2 {
animation-delay: 100ms;
}
.stagger-3 {
animation-delay: 150ms;
}
.stagger-4 {
animation-delay: 200ms;
}
.stagger-5 {
animation-delay: 250ms;
}
.font-display {
font-family: var(--font-neo-display);
}
@@ -479,15 +982,15 @@
}
::-webkit-scrollbar-track {
background: #fffef5;
border: 2px solid #1a1a1a;
background: var(--color-neo-bg);
border: 2px solid var(--color-neo-border);
}
::-webkit-scrollbar-thumb {
background: #1a1a1a;
border: 2px solid #1a1a1a;
background: var(--color-neo-border);
border: 2px solid var(--color-neo-border);
}
::-webkit-scrollbar-thumb:hover {
background: #4a4a4a;
background: var(--color-neo-text-secondary);
}

View File

@@ -1 +0,0 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/confirmdialog.tsx","./src/components/debuglogviewer.tsx","./src/components/devservercontrol.tsx","./src/components/editfeatureform.tsx","./src/components/expandprojectchat.tsx","./src/components/expandprojectmodal.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/settingsmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/terminal.tsx","./src/components/terminaltabs.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/useexpandchat.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}