Modernize UI with improved animations and design system
This commit is contained in:
parent
0ed08e894d
commit
7044ffa751
@ -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'],
|
||||
|
||||
13
src/App.tsx
13
src/App.tsx
@ -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="w-3 h-3 rounded-full bg-emerald-500 animate-pulse"></div>
|
||||
<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>
|
||||
|
||||
@ -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>
|
||||
<h2 className="text-2xl font-bold text-white tracking-tight">{title}</h2>
|
||||
</div>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium bg-white/10 text-white/80 backdrop-blur-md`}>
|
||||
<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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user