Modernize UI with improved animations and design system

This commit is contained in:
Jarian Cottingham 2026-02-24 22:27:24 -06:00
parent 0ed08e894d
commit 7044ffa751
5 changed files with 137 additions and 63 deletions

View File

@ -7,7 +7,7 @@ app.use(cors())
app.use(express.json())
app.post('/api/execute', (req, res) => {
const { description, validation } = req.body
const { description } = req.body
const child = spawn('opencode', ['run', description], {
stdio: ['pipe', 'pipe', 'pipe'],

View File

@ -4,15 +4,18 @@ function App() {
return (
<div className="min-h-screen text-white p-6">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-between mb-8">
<div className="flex items-center justify-between mb-8 animate-fade-in">
<div>
<h1 className="text-5xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
<h1 className="text-5xl font-bold bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent bg-gradient-to-r bg-[length:200%_auto] animate-gradient-text">
WorkBoard
</h1>
<p className="text-slate-400 mt-2">AI-powered task management</p>
<p className="text-slate-400 mt-2 text-lg">AI-powered task management</p>
</div>
<div className="flex items-center gap-2">
<div className="flex items-center gap-3 animate-slide-in">
<div className="relative">
<div className="w-3 h-3 rounded-full bg-emerald-500 animate-pulse"></div>
<span className="absolute inset-0 rounded-full bg-emerald-500 animate-ping opacity-75"></span>
</div>
<span className="text-sm font-medium text-slate-300">AI Agent Ready</span>
</div>
</div>

View File

@ -41,38 +41,43 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
const colorStyles = {
blue: {
header: 'bg-gradient-to-r from-blue-500 to-cyan-500',
bg: 'bg-gradient-to-br from-blue-500/10 to-cyan-500/5',
border: 'border-blue-500/20',
button: 'bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700',
pulse: 'from-blue-500/20 to-cyan-500/20',
header: 'bg-gradient-to-r from-blue-600 via-blue-500 to-cyan-500',
bg: 'from-blue-500/10 via-cyan-500/5 to-blue-500/10',
border: 'border-blue-500/30',
button: 'bg-gradient-to-r from-blue-600 via-blue-500 to-cyan-500 hover:from-blue-700 hover:via-blue-600 hover:to-cyan-600',
pulse: 'bg-gradient-to-r from-blue-500/20 via-cyan-500/20 to-blue-500/20',
glow: 'shadow-blue-500/30',
},
purple: {
header: 'bg-gradient-to-r from-purple-500 to-pink-500',
bg: 'bg-gradient-to-br from-purple-500/10 to-pink-500/5',
border: 'border-purple-500/20',
button: 'bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700',
pulse: 'from-purple-500/20 to-pink-500/20',
header: 'bg-gradient-to-r from-purple-600 via-purple-500 to-pink-500',
bg: 'from-purple-500/10 via-pink-500/5 to-purple-500/10',
border: 'border-purple-500/30',
button: 'bg-gradient-to-r from-purple-600 via-purple-500 to-pink-500 hover:from-purple-700 hover:via-purple-600 hover:to-pink-600',
pulse: 'bg-gradient-to-r from-purple-500/20 via-pink-500/20 to-purple-500/20',
glow: 'shadow-purple-500/30',
},
green: {
header: 'bg-gradient-to-r from-emerald-500 to-teal-500',
bg: 'bg-gradient-to-br from-emerald-500/10 to-teal-500/5',
border: 'border-emerald-500/20',
button: 'bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700',
pulse: 'from-emerald-500/20 to-teal-500/20',
header: 'bg-gradient-to-r from-emerald-600 via-emerald-500 to-teal-500',
bg: 'from-emerald-500/10 via-teal-500/5 to-emerald-500/10',
border: 'border-emerald-500/30',
button: 'bg-gradient-to-r from-emerald-600 via-emerald-500 to-teal-500 hover:from-emerald-700 hover:via-emerald-600 hover:to-teal-600',
pulse: 'bg-gradient-to-r from-emerald-500/20 via-teal-500/20 to-emerald-500/20',
glow: 'shadow-emerald-500/30',
},
}
const styles = colorStyles[color]
return (
<div className={`flex-1 rounded-2xl p-5 ${styles.bg} border ${styles.border} backdrop-blur-sm transition-all duration-300 hover:shadow-2xl hover:shadow-${color}-500/10`}>
<div className="flex items-center justify-between mb-6">
<div className={`flex-1 rounded-2xl p-5 ${styles.bg} border ${styles.border} backdrop-blur-sm transition-all duration-300 hover:shadow-2xl hover:${styles.glow}`}>
<div className="flex items-center justify-between mb-6 animate-slide-in">
<div className="flex items-center gap-3">
<div className={`w-3 h-3 rounded-full ${color === 'blue' ? 'bg-blue-400' : color === 'purple' ? 'bg-purple-400' : 'bg-emerald-400'} animate-pulse`}></div>
<h2 className="text-2xl font-bold text-white">{title}</h2>
<div className={`relative w-3 h-3 rounded-full ${color === 'blue' ? 'bg-blue-400' : color === 'purple' ? 'bg-purple-400' : 'bg-emerald-400'} animate-pulse`}>
<span className={`absolute inset-0 rounded-full animate-ping opacity-75 ${color === 'blue' ? 'bg-blue-400' : color === 'purple' ? 'bg-purple-400' : 'bg-emerald-400'}`}></span>
</div>
<span className={`px-3 py-1 rounded-full text-sm font-medium bg-white/10 text-white/80 backdrop-blur-md`}>
<h2 className="text-2xl font-bold text-white tracking-tight">{title}</h2>
</div>
<span className="px-3 py-1.5 rounded-full text-sm font-medium bg-white/10 text-white/90 backdrop-blur-md border border-white/5 animate-fade-in">
{items.length}
</span>
</div>
@ -86,9 +91,9 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
/>
))}
{status === 'active' && items.some(i => i.status === 'active') && (
<div className={`p-4 rounded-xl bg-gradient-to-r ${styles.pulse} border border-white/5 animate-pulse`}>
<div className="p-4 rounded-xl animate-fade-in border border-white/5">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-white"></div>
<div className="w-2 h-2 rounded-full bg-white animate-pulse"></div>
<p className="text-sm font-medium text-white/90">AI agent is working on this task...</p>
</div>
</div>
@ -97,16 +102,16 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
{status === 'queued' && (
<button
onClick={() => setIsModalOpen(true)}
className="w-full py-4 border-2 border-dashed border-white/20 rounded-xl text-white/60 hover:border-white/40 hover:text-white/90 hover:bg-white/5 transition-all duration-200 flex items-center justify-center gap-2 group"
className="w-full py-4 border-2 border-dashed border-white/20 rounded-xl text-white/60 hover:border-white/40 hover:text-white/90 hover:bg-white/5 transition-all duration-200 flex items-center justify-center gap-2 group animate-fade-in"
>
<span className="group-hover:scale-110 transition-transform">+</span>
<span className="group-hover:scale-110 group-hover:rotate-90 transition-all duration-300 transform">+</span>
<span className="font-medium">Add Work Item</span>
</button>
)}
{isModalOpen && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-gradient-to-br from-slate-800 to-slate-900 rounded-2xl p-6 w-full max-w-md border border-white/10 shadow-2xl">
<h3 className="text-2xl font-bold text-white mb-6">New Work Item</h3>
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4 animate-fade-in">
<div className="bg-gradient-to-br from-slate-800 to-slate-900 rounded-2xl p-6 w-full max-w-md border border-white/10 shadow-2xl animate-slide-in">
<h3 className="text-2xl font-bold text-white mb-6 tracking-tight">New Work Item</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
@ -118,7 +123,8 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
onChange={(e) =>
setFormData({ ...formData, title: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
placeholder="Enter task title"
required
/>
</div>
@ -131,8 +137,9 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
rows={3}
placeholder="Describe the task in detail"
required
/>
</div>
@ -145,15 +152,16 @@ const Column = ({ title, status, items, onUpdate, onDelete, onAdd, color }: Colu
onChange={(e) =>
setFormData({ ...formData, validation: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
rows={3}
placeholder="Define validation conditions"
required
/>
</div>
<div className="flex gap-3">
<button
type="submit"
className={`flex-1 px-6 py-3 rounded-xl ${styles.button} text-white transition-all duration-200 shadow-lg shadow-blue-500/20`}
className={`flex-1 px-6 py-3 rounded-xl ${styles.button} text-white transition-all duration-200 shadow-lg ${styles.glow}`}
>
Create
</button>

View File

@ -37,23 +37,23 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
}
return (
<div className={`glass-card p-5 transition-all duration-300 hover:shadow-xl hover:shadow-white/5 ${workItem.failedAttempts >= 3 ? 'border-red-500/50' : ''}`}>
<div className={`glass-card p-5 transition-all duration-300 ${isExpanded ? 'ring-2 ring-blue-500/50' : ''} ${workItem.failedAttempts >= 3 ? 'border-red-500/50' : ''}`}>
{!isExpanded ? (
<>
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex-1 min-w-0 animate-slide-in">
<div className="flex items-center gap-2 mb-1">
<span className={`w-2 h-2 rounded-full ${getStatusColor(workItem.status)}`}></span>
<h3 className="text-lg font-semibold text-white truncate">{workItem.title}</h3>
<span className={`w-2.5 h-2.5 rounded-full ${getStatusColor(workItem.status)} transition-transform duration-300`}></span>
<h3 className="text-lg font-semibold text-white truncate transition-colors duration-200">{workItem.title}</h3>
</div>
<p className="text-sm text-slate-400 line-clamp-2 mb-2">{workItem.description}</p>
<p className="text-sm text-slate-400 line-clamp-2 mb-2 transition-colors duration-200">{workItem.description}</p>
{workItem.failedAttempts > 0 && (
<div className="flex items-center gap-2">
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${getFailedColor()} text-white`}>
<div className="flex items-center gap-2 animate-fade-in">
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${getFailedColor()} text-white transition-all duration-300`}>
{workItem.failedAttempts}/3 attempts
</span>
{workItem.errorMessage && (
<span className="text-xs text-red-400 truncate max-w-xs">
<span className="text-xs text-red-400 truncate max-w-xs transition-colors duration-200">
{workItem.errorMessage}
</span>
)}
@ -63,11 +63,11 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
<button
onClick={() => onUpdate(workItem.id, { status: 'queued' })}
disabled={workItem.status === 'complete' || workItem.failedAttempts >= 3}
className="ml-4 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white rounded-full w-10 h-10 flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed transition-all duration-200 shadow-lg shadow-blue-500/20"
className="ml-4 bg-gradient-to-r from-blue-600 via-blue-500 to-cyan-500 hover:from-blue-700 hover:via-blue-600 hover:to-cyan-600 text-white rounded-full w-10 h-10 flex items-center justify-center disabled:opacity-30 disabled:cursor-not-allowed transition-all duration-200 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/40 hover:scale-110 active:scale-95"
title="Queue for AI execution"
>
<svg
className="w-5 h-5 fill-current"
className="w-5 h-5 fill-current transition-transform duration-300 group-hover:scale-110"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
@ -77,7 +77,7 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
</div>
</>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4 animate-fade-in">
<div>
<label className="block text-sm font-medium text-slate-300 mb-2">
Title
@ -88,7 +88,7 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
onChange={(e) =>
setFormData({ ...formData, title: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
required
/>
</div>
@ -101,7 +101,7 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
onChange={(e) =>
setFormData({ ...formData, description: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
rows={3}
required
/>
@ -115,7 +115,7 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
onChange={(e) =>
setFormData({ ...formData, validation: e.target.value })
}
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white"
className="w-full px-4 py-3 bg-slate-950 border border-slate-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-white placeholder-slate-600"
rows={3}
required
/>
@ -138,7 +138,7 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
</p>
</div>
{workItem.errorMessage && (
<div className="bg-red-900/20 border border-red-500/30 rounded-xl p-4">
<div className="bg-red-900/20 border border-red-500/30 rounded-xl p-4 animate-fade-in">
<p className="text-red-400 text-sm">
<span className="font-semibold">Error:</span> {workItem.errorMessage}
</p>
@ -147,21 +147,21 @@ const WorkItemComponent = ({ workItem, onUpdate, onDelete }: Props) => {
<div className="flex gap-3 pt-2">
<button
type="submit"
className="flex-1 px-6 py-3 bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white rounded-xl transition-all duration-200 shadow-lg shadow-blue-500/20 font-medium"
className="flex-1 px-6 py-3 bg-gradient-to-r from-blue-600 via-blue-500 to-cyan-500 hover:from-blue-700 hover:via-blue-600 hover:to-cyan-600 text-white rounded-xl transition-all duration-200 shadow-lg shadow-blue-500/20 font-medium hover:shadow-blue-500/40 active:scale-98"
>
Save Changes
</button>
<button
type="button"
onClick={() => setIsExpanded(false)}
className="flex-1 px-6 py-3 bg-slate-700/50 hover:bg-slate-700 text-white rounded-xl transition-colors duration-200 font-medium"
className="flex-1 px-6 py-3 bg-slate-700/50 hover:bg-slate-700 text-white rounded-xl transition-colors duration-200 font-medium hover:scale-105 active:scale-98"
>
Collapse
</button>
<button
type="button"
onClick={() => onDelete(workItem.id)}
className="flex-1 px-6 py-3 bg-red-600/10 hover:bg-red-600/20 text-red-400 hover:text-red-300 rounded-xl transition-colors duration-200 font-medium"
className="flex-1 px-6 py-3 bg-red-600/10 hover:bg-red-600/20 text-red-400 hover:text-red-300 rounded-xl transition-colors duration-200 font-medium hover:scale-105 active:scale-98"
>
Delete
</button>

View File

@ -9,31 +9,94 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
background-size: 200% 200%;
animation: gradientShift 15s ease infinite;
min-height: 100vh;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass-card:hover {
background: rgba(255, 255, 255, 0.05);
transform: translateY(-2px);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 255, 255, 0.12);
}
.glass-btn {
transition: all 0.2s ease;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.glass-btn:hover {
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: 0 10px 20px -5px rgba(0, 0, 0, 0.3);
}
.glass-btn:active {
transform: scale(0.95);
transform: scale(0.98);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes gradientText {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.animate-fade-in {
animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.animate-slide-in {
animation: slideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.animate-gradient-text {
animation: gradientText 3s ease infinite;
}