mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
Merge pull request #67 from mzubair481/feat/ui-design-system-improvements
feat(ui): comprehensive design system improvements
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -128,6 +128,11 @@ pnpm-lock.yaml
|
||||
poetry.lock
|
||||
Pipfile.lock
|
||||
|
||||
# ===================
|
||||
# TypeScript
|
||||
# ===================
|
||||
*.tsbuildinfo
|
||||
|
||||
# ===================
|
||||
# Misc
|
||||
# ===================
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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`}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)]'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
Reference in New Issue
Block a user