style(frontend):优化前端效果

1.对于UI的配色和布局进行了优化
This commit is contained in:
2026-06-01 03:30:30 +00:00
parent 99520c69d7
commit f04fef916f
11 changed files with 452 additions and 416 deletions
+5 -10
View File
@@ -28,7 +28,6 @@ function App() {
const { loadSessions } = useChatStore();
// Initialize theme on mount
useEffect(() => {
applyTheme();
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
@@ -37,14 +36,12 @@ function App() {
return () => mediaQuery.removeEventListener('change', handler);
}, [applyTheme]);
// Sync persisted locale to i18next on mount
useEffect(() => {
if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale);
}
}, [locale]);
// Check auth and load sessions
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
@@ -75,12 +72,10 @@ function App() {
<div className="flex-1 flex overflow-hidden">
{mode === 'work' && workTab === 'chat' && (
<div className="flex-1 p-4 flex overflow-hidden gap-4">
<div className="flex-1 flex bg-bg-card rounded-2xl shadow-sm border border-border-primary overflow-hidden relative">
<div className="flex-1 flex overflow-hidden">
<LeftPanel activeTab="chats" />
<ChatPanel />
</div>
</div>
)}
{mode === 'work' && workTab === 'workflow' && <WorkflowShell />}
@@ -101,22 +96,22 @@ function WorkflowShell() {
if (selectedWorkflow === 'new') {
return (
<>
<div className="flex-1 flex overflow-hidden">
<LeftPanel activeTab="workflows" />
<NewWorkflowDialog
onClose={() => setSelectedWorkflow(null)}
onSuccess={(traceId: string) => setSelectedWorkflow(traceId)}
/>
</>
</div>
);
}
if (selectedWorkflow) {
return (
<>
<div className="flex-1 flex overflow-hidden">
<LeftPanel activeTab="workflows" />
<RightPanel selectedWorkflow={selectedWorkflow} />
</>
</div>
);
}
+50 -37
View File
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import apiClient from '../../api/client';
import { Zap, ArrowRight, ShieldCheck } from 'lucide-react';
import { ArrowRight } from 'lucide-react';
interface AuthPageProps {
onLoginSuccess: () => void;
@@ -49,58 +49,72 @@ export function AuthPage({ onLoginSuccess }: AuthPageProps) {
};
return (
<div className="flex min-h-screen w-full relative overflow-hidden">
{/* Background decoration */}
<div className="absolute inset-0 bg-bg-primary">
<div className="absolute top-0 left-1/4 w-96 h-96 bg-accent/5 rounded-full blur-3xl" />
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-glow-purple/5 rounded-full blur-3xl" />
<div className="min-h-screen w-full bg-bg-primary flex items-center justify-center">
<div className="flex items-center gap-[70px] w-[900px]">
{/* Left: Brand */}
<div className="flex-1">
<div className="w-10 h-[3px] rounded-sm bg-gradient-to-r from-accent to-clay mb-6" />
<h1 className="text-[40px] font-bold tracking-tight leading-tight mb-3 text-text-primary">
Kilo<span className="not-italic text-accent">Star</span>
</h1>
<p className="text-[15px] text-text-muted leading-relaxed mb-8">
{t('app.tagline')}
</p>
<div className="flex flex-col gap-2.5">
<div className="flex items-center gap-2.5 text-[13px] text-text-muted">
<div className="w-[5px] h-[5px] rounded-full bg-clay" />
<span>Multi-Agent Orchestration</span>
</div>
<div className="flex items-center gap-2.5 text-[13px] text-text-muted">
<div className="w-[5px] h-[5px] rounded-full bg-clay" />
<span>Visual Pipeline Builder</span>
</div>
<div className="flex items-center gap-2.5 text-[13px] text-text-muted">
<div className="w-[5px] h-[5px] rounded-full bg-clay" />
<span>Intelligent Monitoring</span>
</div>
</div>
</div>
<div className="relative z-10 flex w-full items-center justify-center p-6">
<div className="w-full max-w-md animate-fade-in-scale">
{/* Logo */}
<div className="flex flex-col items-center mb-8">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-accent to-glow-purple flex items-center justify-center text-white shadow-xl shadow-accent/20 mb-5">
<Zap size={28} fill="currentColor" />
</div>
<h1 className="text-2xl font-bold text-text-primary tracking-tight">{t('app.name')}</h1>
<p className="text-sm text-text-muted mt-1.5">{t('app.tagline')}</p>
</div>
<div className="bg-bg-card/80 backdrop-blur-xl rounded-2xl border border-border-primary shadow-xl shadow-black/5 p-8">
<div className="flex items-center gap-2 mb-6">
<ShieldCheck size={18} className="text-accent" />
<h2 className="text-lg font-semibold text-text-primary">
{/* Right: Card */}
<div className="w-[380px] bg-bg-card rounded-[20px] p-10 shadow-[0_1px_3px_rgba(0,0,0,0.04),0_8px_24px_rgba(0,0,0,0.03)] border border-border-primary">
<h2 className="text-xl font-semibold text-text-primary mb-1">
{isLogin ? t('auth.welcomeBack') : t('auth.createAccount')}
</h2>
</div>
<p className="text-[13px] text-text-muted mb-7">
{isLogin ? t('auth.enterCredentials') : t('auth.signUpToStart')}
</p>
{error && (
<div className={`mb-5 p-3 rounded-xl text-sm border ${error.includes('success') || error.includes('成功') ? 'bg-success-bg/50 text-success border-success/20' : 'bg-danger-bg/50 text-danger border-danger/20'}`}>
<div className={`mb-5 p-3 rounded-[10px] text-sm border ${error.includes('success') || error.includes('成功') ? 'bg-success-bg/50 text-success border-success/20' : 'bg-danger-bg/50 text-danger border-danger/20'}`}>
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-[18px]">
<div>
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('auth.username')}</label>
<label className="block text-xs font-medium text-text-muted mb-1.5">
{t('auth.username')}
</label>
<input
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
className="w-full px-4 py-2.5 bg-bg-input border border-border-primary rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent transition-all text-text-primary placeholder:text-text-muted/60 text-sm"
className="w-full px-3.5 py-3 rounded-[10px] border border-border-primary bg-bg-primary text-sm text-text-primary placeholder:text-[#bbb5ae] outline-none transition-all focus:border-accent focus:shadow-[0_0_0_3px_rgba(156,175,136,0.1)]"
placeholder={t('auth.usernamePlaceholder')}
required
/>
</div>
<div>
<label className="block text-xs font-semibold text-text-secondary mb-1.5 uppercase tracking-wider">{t('auth.password')}</label>
<label className="block text-xs font-medium text-text-muted mb-1.5">
{t('auth.password')}
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-4 py-2.5 bg-bg-input border border-border-primary rounded-xl focus:outline-none focus:ring-2 focus:ring-accent/20 focus:border-accent transition-all text-text-primary placeholder:text-text-muted/60 text-sm"
className="w-full px-3.5 py-3 rounded-[10px] border border-border-primary bg-bg-primary text-sm text-text-primary placeholder:text-[#bbb5ae] outline-none transition-all focus:border-accent focus:shadow-[0_0_0_3px_rgba(156,175,136,0.1)]"
placeholder={t('auth.passwordPlaceholder')}
required
/>
@@ -109,7 +123,7 @@ export function AuthPage({ onLoginSuccess }: AuthPageProps) {
<button
type="submit"
disabled={loading}
className="w-full py-2.5 bg-accent text-white rounded-xl font-semibold hover:bg-accent-hover focus:outline-none focus:ring-2 focus:ring-accent/30 transition-all disabled:opacity-50 cursor-pointer text-sm flex items-center justify-center gap-2 group"
className="w-full py-3 rounded-[10px] bg-accent text-white text-sm font-semibold hover:bg-accent-hover hover:-translate-y-px hover:shadow-[0_6px_20px_rgba(156,175,136,0.3)] transition-all disabled:opacity-50 cursor-pointer flex items-center justify-center gap-2"
>
{loading ? (
<span className="flex items-center gap-2">
@@ -119,25 +133,24 @@ export function AuthPage({ onLoginSuccess }: AuthPageProps) {
) : (
<>
{isLogin ? t('auth.signIn') : t('auth.signUp')}
<ArrowRight size={16} className="transition-transform group-hover:translate-x-0.5" />
<ArrowRight size={16} />
</>
)}
</button>
</form>
<div className="mt-6 text-center text-sm text-text-muted">
<div className="flex items-center gap-3.5 my-5 text-[#bbb5ae] text-xs before:flex-1 before:h-px before:bg-border-primary after:flex-1 after:h-px after:bg-border-primary">
or
</div>
<p className="text-center text-[13px] text-text-muted">
{isLogin ? t('auth.noAccount') : t('auth.hasAccount')}{' '}
<button
onClick={() => { setIsLogin(!isLogin); setError(''); }}
className="text-accent font-semibold hover:text-accent-hover transition-colors"
className="text-accent font-medium hover:text-accent-hover transition-colors"
>
{isLogin ? t('auth.signUp') : t('auth.signIn')}
</button>
</div>
</div>
<p className="text-center text-xs text-text-muted/60 mt-6">
{t('app.tagline')}
</p>
</div>
</div>
+46 -47
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { MessageSquare, Activity, ArrowUp, Plus, Sparkles, Code, FileText, Search } from 'lucide-react';
import { MessageSquare, Activity, ArrowUp, Plus, Sparkles, Code, FileText, Search, User } from 'lucide-react';
import { useChatStore } from '../../store/useChatStore';
export function ChatPanel() {
@@ -64,36 +64,32 @@ export function ChatPanel() {
if (!activeSessionId) {
return (
<div className="flex-1 flex flex-col items-center justify-center p-8 relative overflow-hidden">
{/* Decorative background */}
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-accent/5 rounded-full blur-3xl" />
<div className="flex-1 flex flex-col items-center justify-center p-10 relative overflow-hidden">
<div className="relative z-10 flex flex-col items-center animate-fade-in-scale">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-accent to-glow-purple flex items-center justify-center text-white shadow-xl shadow-accent/20 mb-6">
<Activity size={32} />
<div className="w-14 h-14 rounded-2xl bg-accent-light flex items-center justify-center text-accent mb-5">
<Activity size={24} />
</div>
<h2 className="text-2xl font-bold text-text-primary mb-2">{t('chat.assistantName')}</h2>
<p className="text-text-muted text-sm mb-8 text-center max-w-sm">
<h2 className="text-lg font-semibold text-text-primary mb-1.5">{t('chat.assistantName')}</h2>
<p className="text-text-secondary text-sm mb-7 text-center max-w-sm">
{t('chat.selectChat')}
</p>
{/* Quick Actions */}
<div className="grid grid-cols-2 gap-3 mb-8 w-full max-w-md">
<div className="grid grid-cols-2 gap-2.5 mb-7 w-full max-w-[380px]">
{quickActions.map((action) => (
<button
key={action.label}
onClick={() => handleQuickAction(action.prompt)}
className="flex items-center gap-2.5 px-4 py-3 bg-bg-card border border-border-primary rounded-xl text-left hover:border-accent hover:bg-bg-hover transition-all group"
className="flex items-center gap-2.5 px-3.5 py-3 bg-bg-card border border-border-primary rounded-[10px] text-left hover:border-border-secondary hover:bg-bg-hover transition-all group shadow-[0_1px_2px_rgba(0,0,0,0.02)]"
>
<action.icon size={16} className="text-text-muted group-hover:text-accent transition-colors" />
<span className="text-sm text-text-secondary group-hover:text-text-primary transition-colors">{action.label}</span>
<action.icon size={14} className="text-accent flex-shrink-0" />
<span className="text-xs text-text-secondary group-hover:text-text-primary transition-colors">{action.label}</span>
</button>
))}
</div>
<button
onClick={handleNewChat}
className="px-6 py-2.5 bg-accent text-white rounded-xl font-medium hover:bg-accent-hover transition-all shadow-lg shadow-accent/20 text-sm flex items-center gap-2"
className="px-5 py-2.5 bg-accent text-white rounded-xl font-medium hover:bg-accent-hover transition-all text-sm flex items-center gap-2"
>
<Plus size={16} />
{t('chat.newChat')}
@@ -104,19 +100,17 @@ export function ChatPanel() {
}
return (
<div className="flex-1 flex flex-col bg-bg-card overflow-hidden relative">
<div className="flex-1 flex flex-col bg-bg-primary overflow-hidden relative">
{/* Header */}
<div className="h-12 border-b border-border-primary/60 bg-bg-card/80 backdrop-blur flex items-center justify-between px-5 z-10 shrink-0">
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-lg bg-accent-light flex items-center justify-center">
<MessageSquare size={14} className="text-accent" />
</div>
<h1 className="font-semibold text-sm text-text-primary">{activeSession?.title || t('chat.defaultTitle')}</h1>
<div className="h-12 border-b border-border-primary flex items-center px-6 gap-2.5 bg-bg-primary/60 backdrop-blur-[8px] shrink-0">
<div className="w-[26px] h-[26px] rounded-[7px] flex items-center justify-center bg-accent-light">
<MessageSquare size={12} className="text-accent" />
</div>
<span className="text-[13px] font-semibold text-text-primary">{activeSession?.title || t('chat.defaultTitle')}</span>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-5">
<div className="flex-1 overflow-y-auto px-8 py-6 flex flex-col gap-4.5">
{loadingMessages ? (
<div className="flex justify-center items-center h-full">
<div className="flex items-center gap-1.5">
@@ -129,24 +123,29 @@ export function ChatPanel() {
messages.map((msg, idx) => (
<div
key={msg.id}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'} animate-fade-in`}
className={`flex gap-2.5 max-w-[78%] ${msg.role === 'user' ? 'self-end flex-row-reverse' : ''} animate-fade-in`}
style={{ animationDelay: `${idx * 30}ms` }}
>
{msg.role === 'assistant' && (
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-accent to-glow-purple flex items-center justify-center mr-2.5 mt-0.5 shadow-sm flex-shrink-0">
<Activity size={13} className="text-white" />
</div>
)}
<div className={`max-w-[85%] ${msg.role === 'user' ? 'mr-1' : ''}`}>
<div
className={`px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap ${
msg.role === 'user'
? 'bg-text-primary text-bg-primary rounded-2xl rounded-tr-sm'
: 'bg-bg-secondary border border-border-primary rounded-2xl rounded-tl-sm'
}`}
className="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0"
style={{
background: msg.role === 'assistant'
? 'var(--accent)'
: 'linear-gradient(135deg, #6b6860, #8a8578)',
}}
>
{msg.content}
{msg.role === 'assistant' ? (
<Activity size={12} className="text-white" />
) : (
<User size={12} className="text-white" />
)}
</div>
<div className={`px-4 py-3 text-[13px] leading-[1.7] whitespace-pre-wrap ${
msg.role === 'user'
? 'bg-text-primary text-white rounded-[14px] rounded-br-[3px]'
: 'bg-bg-card border border-border-primary rounded-[14px] rounded-bl-[3px] shadow-[0_1px_3px_rgba(0,0,0,0.02)]'
}`}>
{msg.content}
</div>
</div>
))
@@ -154,11 +153,11 @@ export function ChatPanel() {
{/* Typing indicator */}
{activeSession && activeSession.messages.length > 0 && activeSession.messages[activeSession.messages.length - 1].role === 'user' && (
<div className="flex justify-start animate-fade-in">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-accent to-glow-purple flex items-center justify-center mr-2.5 mt-0.5 shadow-sm flex-shrink-0">
<Activity size={13} className="text-white animate-pulse" />
<div className="flex gap-2.5 max-w-[78%] animate-fade-in">
<div className="w-7 h-7 rounded-full bg-accent flex items-center justify-center flex-shrink-0">
<Activity size={12} className="text-white animate-pulse" />
</div>
<div className="bg-bg-secondary border border-border-primary rounded-2xl rounded-tl-sm px-4 py-3">
<div className="bg-bg-card border border-border-primary rounded-[14px] rounded-bl-[3px] px-4 py-3 shadow-[0_1px_3px_rgba(0,0,0,0.02)]">
<div className="flex items-center gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-accent animate-typing-dot" />
<span className="w-1.5 h-1.5 rounded-full bg-accent animate-typing-dot" />
@@ -171,12 +170,12 @@ export function ChatPanel() {
</div>
{/* Input */}
<div className="p-4 bg-bg-card border-t border-border-primary/60 shrink-0">
<div className="relative flex items-end gap-2 max-w-3xl mx-auto">
<div className="px-6 pt-3.5 pb-4.5 border-t border-border-primary bg-bg-primary/60 backdrop-blur-[8px] shrink-0">
<div className="flex items-end gap-2 max-w-[720px] mx-auto">
<input type="file" ref={fileInputRef} className="hidden" />
<button
onClick={() => fileInputRef.current?.click()}
className="p-2.5 text-text-muted hover:text-accent hover:bg-accent-light rounded-xl transition-all flex-shrink-0 mb-0.5"
className="w-10 h-10 rounded-xl text-text-muted hover:text-accent hover:bg-bg-hover transition-all flex items-center justify-center flex-shrink-0"
title={t('chat.addAttachment')}
>
<Plus size={18} />
@@ -193,19 +192,19 @@ export function ChatPanel() {
}}
placeholder={t('chat.placeholder')}
rows={1}
className="w-full bg-bg-input border border-border-primary rounded-xl pl-4 pr-12 py-3 focus:outline-none focus:ring-2 focus:ring-accent/15 focus:border-accent/40 transition-all text-text-primary placeholder:text-text-muted/50 text-sm resize-none min-h-[44px] max-h-[120px]"
className="w-full bg-bg-card border border-border-primary rounded-xl pl-4 pr-4 py-3 focus:outline-none focus:border-accent focus:shadow-[0_0_0_3px_var(--accent-light),0_1px_3px_rgba(0,0,0,0.02)] transition-all text-text-primary placeholder:text-[#bbb5ae] text-[13px] resize-none min-h-[44px] max-h-[100px]"
style={{ height: 'auto' }}
/>
</div>
<button
onClick={handleSendMessage}
disabled={!input.trim()}
className="p-2.5 bg-accent text-white rounded-xl hover:bg-accent-hover transition-all shadow-lg shadow-accent/15 disabled:opacity-30 disabled:shadow-none disabled:hover:bg-accent flex-shrink-0 mb-0.5"
className="w-10 h-10 rounded-[10px] bg-accent text-white hover:bg-accent-hover hover:-translate-y-px hover:shadow-[0_4px_12px_rgba(156,175,136,0.25)] transition-all disabled:opacity-30 flex items-center justify-center flex-shrink-0"
>
<ArrowUp size={18} />
<ArrowUp size={16} />
</button>
</div>
<p className="text-center text-[10px] text-text-muted/50 mt-2">
<p className="text-center text-[10px] text-text-muted mt-2">
{t('chat.mistakeWarning')}
</p>
</div>
+34 -35
View File
@@ -69,9 +69,9 @@ export function LeftPanel({ activeTab }: LeftPanelProps) {
const isChats = activeTab === 'chats';
return (
<div className="w-64 bg-bg-sidebar border-r border-border-primary flex flex-col shrink-0">
<div className="flex items-center justify-between px-4 py-3 border-b border-border-primary">
<span className="text-[11px] font-bold text-text-muted uppercase tracking-widest">
<div className="w-[260px] bg-bg-sidebar border-r border-border-primary flex flex-col shrink-0">
<div className="flex items-center justify-between px-4 py-4">
<span className="text-[11px] font-semibold text-text-muted uppercase tracking-[1.5px]">
{isChats ? t('chat.chatHistory') : t('nav.workflow')}
</span>
<button
@@ -79,47 +79,49 @@ export function LeftPanel({ activeTab }: LeftPanelProps) {
if (isChats) handleNewChat();
else setSelectedWorkflow('new');
}}
className="p-1.5 rounded-lg bg-bg-hover text-text-muted hover:text-accent hover:bg-accent-light transition-all"
className="w-[26px] h-[26px] rounded-md bg-bg-card text-text-secondary hover:bg-accent hover:text-white transition-all flex items-center justify-center shadow-[0_1px_2px_rgba(0,0,0,0.04)]"
title={isChats ? t('chat.newChat') : t('workflow.createWorkflow')}
>
<Plus size={14} />
<Plus size={12} />
</button>
</div>
<div className="flex-1 overflow-y-auto py-2 px-2 space-y-0.5">
<div className="flex-1 overflow-y-auto px-2 pb-2">
{isChats ? (
sessions.length === 0 ? (
<div className="px-3 py-8 text-center text-text-muted text-xs">
{t('chat.noHistory')}
</div>
) : (
sessions.map((session) => (
sessions.map((session) => {
const isActive = activeSessionId === session.id;
return (
<div
key={session.id}
onClick={() => setActiveSessionId(session.id)}
className={`group flex items-center gap-2.5 px-3 py-2.5 rounded-lg cursor-pointer transition-all ${
activeSessionId === session.id
? 'bg-accent-light text-accent'
: 'hover:bg-bg-hover text-text-secondary'
className={`group flex items-center gap-2.5 px-2.5 py-2 rounded-lg cursor-pointer transition-all mb-px ${
isActive
? 'bg-bg-card shadow-[0_1px_3px_rgba(0,0,0,0.04)]'
: 'hover:bg-white/60 dark:hover:bg-white/[0.04]'
}`}
>
<MessageSquare size={14} className={`flex-shrink-0 ${activeSessionId === session.id ? 'text-accent' : 'text-text-muted'}`} />
<div className={`w-7 h-7 rounded-[7px] flex items-center justify-center flex-shrink-0 ${isActive ? 'bg-accent-light' : 'bg-bg-primary'}`}>
<MessageSquare size={12} className={isActive ? 'text-accent' : 'text-text-muted'} />
</div>
<div className="flex-1 min-w-0">
<h3 className={`text-xs font-medium truncate ${activeSessionId === session.id ? 'text-accent' : 'text-text-secondary'}`}>
<h3 className={`text-xs truncate ${isActive ? 'text-text-primary font-medium' : 'text-text-secondary'}`}>
{session.title}
</h3>
<p className="text-[10px] text-text-muted mt-0.5">
{new Date(session.updatedAt).toLocaleDateString()}
</p>
</div>
<button
onClick={(e) => handleDeleteChat(e, session.id)}
className="opacity-0 group-hover:opacity-100 p-1 rounded text-text-muted hover:text-danger hover:bg-danger-bg transition-all"
className="opacity-0 group-hover:opacity-100 p-1 rounded text-text-muted hover:text-danger transition-all"
>
<Trash2 size={12} />
<Trash2 size={11} />
</button>
</div>
))
);
})
)
) : (
loadingWorkflows ? (
@@ -129,32 +131,29 @@ export function LeftPanel({ activeTab }: LeftPanelProps) {
{t('workflow.noWorkflows')}
</div>
) : (
workflows.map((wf) => (
workflows.map((wf) => {
const isActive = selectedWorkflow === wf.trace_id;
return (
<div
key={wf.trace_id}
onClick={() => setSelectedWorkflow(wf.trace_id)}
className={`group flex items-center gap-2.5 px-3 py-2.5 rounded-lg cursor-pointer transition-all ${
selectedWorkflow === wf.trace_id
? 'bg-accent-light'
: 'hover:bg-bg-hover'
className={`group flex items-center gap-2.5 px-2.5 py-2 rounded-lg cursor-pointer transition-all mb-px ${
isActive
? 'bg-bg-card shadow-[0_1px_3px_rgba(0,0,0,0.04)]'
: 'hover:bg-white/60 dark:hover:bg-white/[0.04]'
}`}
>
<WorkflowIcon size={14} className={`flex-shrink-0 ${selectedWorkflow === wf.trace_id ? 'text-accent' : 'text-text-muted'}`} />
<div className={`w-7 h-7 rounded-[7px] flex items-center justify-center flex-shrink-0 ${isActive ? 'bg-accent-light' : 'bg-bg-primary'}`}>
<WorkflowIcon size={12} className={isActive ? 'text-accent' : 'text-text-muted'} />
</div>
<div className="flex-1 min-w-0">
<h3 className={`text-xs font-medium truncate ${selectedWorkflow === wf.trace_id ? 'text-accent' : 'text-text-secondary'}`}>
<h3 className={`text-xs truncate ${isActive ? 'text-text-primary font-medium' : 'text-text-secondary'}`}>
{wf.title || t('common.unnamed')}
</h3>
<div className="flex items-center gap-1.5 mt-0.5">
<span className={`w-1.5 h-1.5 rounded-full ${
wf.status?.includes('working') ? 'bg-accent animate-pulse' :
wf.status === 'failed' ? 'bg-danger' :
wf.status === 'completed' ? 'bg-success' : 'bg-text-muted'
}`} />
<span className="text-[10px] text-text-muted font-mono truncate">{wf.trace_id.slice(-8)}</span>
</div>
</div>
</div>
))
);
})
)
)}
</div>
@@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import apiClient from '../../api/client';
import type { Workflow } from '../../types';
import { PlayCircle, CheckCircle, XCircle, Clock, ArrowRight, Zap } from 'lucide-react';
interface WorkflowListViewProps {
onSelectWorkflow: (id: string) => void;
@@ -37,11 +36,24 @@ export function WorkflowListView({ onSelectWorkflow }: WorkflowListViewProps) {
return () => clearInterval(intervalId);
}, []);
const getStatusMeta = (status?: string) => {
if (status === 'completed') return { icon: CheckCircle, color: 'text-success', bg: 'bg-success-bg', border: 'border-success/20', glow: 'group-hover:shadow-success/20' };
if (status === 'failed') return { icon: XCircle, color: 'text-danger', bg: 'bg-danger-bg', border: 'border-danger/20', glow: 'group-hover:shadow-danger/20' };
if (status && status.includes('working')) return { icon: PlayCircle, color: 'text-accent', bg: 'bg-accent-light', border: 'border-accent/20', glow: 'group-hover:shadow-accent/20' };
return { icon: Clock, color: 'text-text-muted', bg: 'bg-bg-secondary', border: 'border-border-primary', glow: 'group-hover:shadow-border-primary' };
const getStatusStyle = (status?: string) => {
if (status && status.includes('working')) {
return { label: t('workflow.status.running'), bg: 'bg-[rgba(156,175,136,0.12)]', text: 'text-[#7a8e6a]' };
}
if (status === 'completed') {
return { label: t('workflow.status.completed'), bg: 'bg-[rgba(196,168,130,0.12)]', text: 'text-[#9a7d5e]' };
}
if (status === 'failed') {
return { label: t('workflow.status.failed'), bg: 'bg-[rgba(196,145,122,0.1)]', text: 'text-[#a0705a]' };
}
return { label: t('workflow.status.waiting'), bg: 'bg-[rgba(138,154,170,0.1)]', text: 'text-[#6e7d8d]' };
};
const stats = {
total: workflows.length,
running: workflows.filter((w) => w.status?.includes('working')).length,
completed: workflows.filter((w) => w.status === 'completed').length,
queued: workflows.filter((w) => !w.status || (!w.status.includes('working') && w.status !== 'completed' && w.status !== 'failed')).length,
};
if (loading) {
@@ -56,80 +68,85 @@ export function WorkflowListView({ onSelectWorkflow }: WorkflowListViewProps) {
}
return (
<div className="flex-1 flex flex-col p-8 overflow-auto">
<div className="max-w-6xl mx-auto w-full">
<div className="flex justify-between items-end mb-8">
<div className="flex-1 overflow-y-auto px-7 py-6">
{/* Header */}
<div className="flex justify-between items-end mb-6">
<div>
<div className="flex items-center gap-2 mb-1">
<Zap size={18} className="text-accent" />
<h1 className="text-2xl font-bold text-text-primary tracking-tight">{t('workflow.workflows')}</h1>
</div>
<p className="text-sm text-text-muted">{t('workflow.manageWorkflows')}</p>
<h1 className="text-xl font-semibold text-text-primary">{t('workflow.workflows')}</h1>
<p className="text-[13px] text-text-muted mt-[3px]">{t('workflow.manageWorkflows')}</p>
</div>
<button
onClick={() => onSelectWorkflow('new')}
className="flex items-center gap-2 px-5 py-2.5 bg-accent text-white font-medium rounded-xl hover:bg-accent-hover transition-all shadow-lg shadow-accent/15 text-sm"
className="px-4 py-2 rounded-[10px] bg-accent text-white text-xs font-semibold hover:bg-accent-hover hover:-translate-y-px hover:shadow-[0_4px_16px_rgba(156,175,136,0.3)] transition-all"
>
<span className="text-base leading-none">+</span>
{t('workflow.createWorkflow')}
+ {t('workflow.createWorkflow')}
</button>
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-3 mb-6">
<div className="p-4 bg-bg-card rounded-xl border border-border-primary">
<div className="text-[10px] font-semibold uppercase tracking-[1px] text-text-muted mb-1.5">{t('workflow.total')}</div>
<div className="text-[22px] font-bold text-text-primary">{stats.total}</div>
</div>
<div className="p-4 bg-bg-card rounded-xl border border-border-primary">
<div className="text-[10px] font-semibold uppercase tracking-[1px] text-text-muted mb-1.5">{t('workflow.status.running')}</div>
<div className="text-[22px] font-bold text-[#7a8e6a]">{stats.running}</div>
</div>
<div className="p-4 bg-bg-card rounded-xl border border-border-primary">
<div className="text-[10px] font-semibold uppercase tracking-[1px] text-text-muted mb-1.5">{t('workflow.status.completed')}</div>
<div className="text-[22px] font-bold text-[#9a7d5e]">{stats.completed}</div>
</div>
<div className="p-4 bg-bg-card rounded-xl border border-border-primary">
<div className="text-[10px] font-semibold uppercase tracking-[1px] text-text-muted mb-1.5">{t('workflow.queued')}</div>
<div className="text-[22px] font-bold text-[#6e7d8d]">{stats.queued}</div>
</div>
</div>
{workflows.length === 0 ? (
<div className="flex flex-col items-center justify-center border border-dashed border-border-primary rounded-2xl bg-bg-card/50 p-16 text-center">
<div className="w-14 h-14 bg-bg-secondary rounded-2xl flex items-center justify-center mb-4">
<PlayCircle size={28} className="text-text-muted" />
<div className="flex flex-col items-center justify-center border border-dashed border-border-primary rounded-xl bg-bg-card/50 p-16 text-center">
<div className="w-14 h-14 bg-bg-secondary rounded-xl flex items-center justify-center mb-4">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="w-7 h-7 text-text-muted"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
</div>
<h3 className="text-base font-semibold text-text-primary mb-1">{t('workflow.noWorkflows')}</h3>
<p className="text-sm text-text-muted max-w-xs">{t('workflow.workflowsAppearHere')}</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<div className="grid grid-cols-[repeat(auto-fill,minmax(280px,1fr))] gap-3">
{workflows.map((wf) => {
const meta = getStatusMeta(wf.status);
const Icon = meta.icon;
const style = getStatusStyle(wf.status);
return (
<div
key={wf.trace_id}
onClick={() => onSelectWorkflow(wf.trace_id)}
className={`group relative bg-bg-card rounded-2xl p-5 border border-border-primary card-hover cursor-pointer overflow-hidden ${meta.glow}`}
className="p-4 bg-bg-card rounded-xl border border-border-primary cursor-pointer transition-all hover:shadow-[0_4px_16px_rgba(0,0,0,0.05)] hover:-translate-y-0.5 hover:border-[#d5d0ca] dark:hover:border-white/10"
>
{/* Status glow on hover */}
<div className={`absolute top-0 right-0 w-24 h-24 ${meta.bg} rounded-full blur-2xl opacity-0 group-hover:opacity-40 transition-opacity -translate-y-1/2 translate-x-1/2`} />
<div className="relative">
<div className="flex justify-between items-start mb-4">
<div className={`p-2 rounded-xl ${meta.bg} ${meta.border} border`}>
<Icon size={18} className={meta.color} />
<div className="flex justify-between items-center mb-3">
<span className={`text-[10px] font-semibold px-2.5 py-[3px] rounded-[20px] tracking-[0.3px] ${style.bg} ${style.text}`}>
{style.label}
</span>
</div>
<ArrowRight size={16} className="text-text-muted opacity-0 group-hover:opacity-100 transition-all group-hover:translate-x-0.5" />
</div>
<h3 className="text-base font-semibold text-text-primary mb-1 line-clamp-1" title={wf.title || t('common.unnamed')}>
<h3 className="text-[13px] font-semibold text-text-primary mb-[5px] line-clamp-1" title={wf.title || t('common.unnamed')}>
{wf.title || t('common.unnamed')}
</h3>
{wf.command && (
<p className="text-xs text-text-muted line-clamp-2 mb-4">
<p className="text-xs text-text-muted leading-relaxed mb-3.5 line-clamp-2">
{wf.command}
</p>
)}
<div className="flex items-center justify-between pt-3 border-t border-border-secondary">
<span className="text-[10px] font-mono text-text-muted bg-bg-secondary px-2 py-0.5 rounded">
{wf.trace_id.slice(0, 8)}...
<div className="flex justify-between items-center pt-3 border-t border-border-primary">
<span className="text-[10px] text-[#b5afa8] font-mono bg-bg-primary px-2 py-0.5 rounded">
{wf.trace_id.slice(-8)}
</span>
{wf.created_at && (
<span className="text-[10px] text-text-muted">{new Date(wf.created_at).toLocaleDateString()}</span>
<span className="text-[11px] text-text-muted">{new Date(wf.created_at).toLocaleDateString()}</span>
)}
</div>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
);
}
@@ -33,7 +33,7 @@ export function CollapsibleSidebar() {
isSidebarOpen ? 'w-56' : 'w-14'
}`}
>
<div className="flex-1 py-3 space-y-1">
<div className="flex-1 py-3 space-y-0.5">
{navItems.map((item) => {
const isActive = activeTab === item.key;
return (
@@ -42,18 +42,15 @@ export function CollapsibleSidebar() {
onClick={() => setTab(item.key as any)}
className={`w-full flex items-center mx-1.5 rounded-lg transition-all duration-200 group ${
isActive
? 'bg-accent-light text-accent'
: 'text-text-muted hover:text-text-secondary hover:bg-bg-hover'
? 'bg-white/50 dark:bg-white/[0.06] text-text-primary shadow-[0_1px_3px_rgba(0,0,0,0.04)]'
: 'text-text-muted hover:text-text-secondary hover:bg-white/40 dark:hover:bg-white/[0.04]'
} ${isSidebarOpen ? 'px-3 py-2.5 gap-3' : 'px-0 py-2.5 justify-center'}`}
style={{ width: isSidebarOpen ? 'calc(100% - 12px)' : 'calc(100% - 12px)' }}
>
<item.icon size={18} className={`flex-shrink-0 transition-transform group-hover:scale-110 ${isActive ? 'text-accent' : ''}`} />
<item.icon size={16} className={`flex-shrink-0 transition-transform group-hover:scale-110 ${isActive ? 'text-accent' : ''}`} />
{isSidebarOpen && (
<span className="text-xs font-medium">{item.label}</span>
)}
{isActive && isSidebarOpen && (
<div className="ml-auto w-1 h-1 rounded-full bg-accent" />
)}
</button>
);
})}
+23 -29
View File
@@ -36,28 +36,23 @@ export function TopBar() {
};
return (
<div className="h-14 glass text-text-primary flex items-center justify-between px-5 shrink-0 z-50 relative">
{/* Left: Logo with status */}
<div className="flex items-center gap-3">
<div className="relative">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-accent to-glow-purple flex items-center justify-center text-white shadow-lg">
<Zap size={18} fill="currentColor" />
</div>
<span className="status-dot absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full bg-success border-2 border-bg-card" />
</div>
<div className="flex flex-col leading-none">
<span className="font-bold text-sm tracking-tight text-text-primary">{t('app.name')}</span>
<span className="text-[10px] text-text-muted font-medium tracking-wide">{t('app.tagline')}</span>
<div className="h-[52px] glass text-text-primary flex items-center justify-between px-5 shrink-0 z-50 relative">
{/* Left: Logo + Nav */}
<div className="flex items-center">
<div className="flex items-center gap-2.5">
<div className="w-7 h-7 rounded-[7px] bg-accent flex items-center justify-center">
<Zap size={14} className="text-white" />
</div>
<span className="text-[15px] font-bold tracking-[-0.3px] text-text-primary">{t('app.name')}</span>
</div>
{/* Center: Mode Switch */}
<div className="hidden md:flex items-center bg-bg-secondary/80 rounded-full p-0.5 border border-border-primary">
{/* Center: Pill Nav */}
<div className="hidden md:flex items-center gap-px p-0.5 bg-bg-input rounded-lg ml-5">
<button
onClick={() => { setMode('work'); setShowSettings(false); }}
className={`px-4 py-1.5 rounded-full text-xs font-semibold transition-all duration-200 ${
className={`px-3.5 py-[5px] rounded-md text-xs font-semibold transition-all ${
mode === 'work' && !showSettings
? 'bg-bg-card text-accent shadow-sm'
? 'bg-bg-card text-text-primary shadow-[0_1px_3px_rgba(0,0,0,0.06)]'
: 'text-text-muted hover:text-text-secondary'
}`}
>
@@ -65,54 +60,53 @@ export function TopBar() {
</button>
<button
onClick={() => { setMode('agent'); setShowSettings(false); }}
className={`px-4 py-1.5 rounded-full text-xs font-semibold transition-all duration-200 ${
className={`px-3.5 py-[5px] rounded-md text-xs font-semibold transition-all ${
mode === 'agent' && !showSettings
? 'bg-bg-card text-accent shadow-sm'
? 'bg-bg-card text-text-primary shadow-[0_1px_3px_rgba(0,0,0,0.06)]'
: 'text-text-muted hover:text-text-secondary'
}`}
>
{t('nav.agent')}
</button>
</div>
</div>
{/* Right: Actions */}
<div className="flex items-center gap-1">
<button
onClick={toggleLanguage}
className="p-2 rounded-lg text-text-muted hover:text-text-primary hover:bg-bg-hover transition-all"
className="w-8 h-8 rounded-lg text-text-muted hover:text-text-secondary hover:bg-bg-hover transition-all flex items-center justify-center"
title={i18n.language.startsWith('zh') ? t('topbar.switchToEn') : t('topbar.switchToZh')}
>
<Globe size={16} />
<Globe size={15} />
</button>
<button
onClick={toggleTheme}
className="p-2 rounded-lg text-text-muted hover:text-text-primary hover:bg-bg-hover transition-all"
className="w-8 h-8 rounded-lg text-text-muted hover:text-text-secondary hover:bg-bg-hover transition-all flex items-center justify-center"
title={resolvedTheme === 'dark' ? t('topbar.lightMode') : t('topbar.darkMode')}
>
{resolvedTheme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}
{resolvedTheme === 'dark' ? <Sun size={15} /> : <Moon size={15} />}
</button>
<div className="w-px h-4 bg-border-primary mx-1" />
<button
onClick={() => setShowSettings(!showSettings)}
className={`p-2 rounded-lg transition-all ${
className={`w-8 h-8 rounded-lg transition-all flex items-center justify-center ${
showSettings
? 'bg-accent-light text-accent'
: 'text-text-muted hover:text-text-primary hover:bg-bg-hover'
: 'text-text-muted hover:text-text-secondary hover:bg-bg-hover'
}`}
title={t('nav.settings')}
>
<Settings size={16} />
<Settings size={15} />
</button>
<button
onClick={handleLogout}
className="p-2 rounded-lg text-text-muted hover:text-danger hover:bg-danger-bg transition-all ml-0.5"
className="w-8 h-8 rounded-lg text-text-muted hover:text-danger hover:bg-danger-bg transition-all flex items-center justify-center"
title={t('topbar.logout')}
>
<LogOut size={16} />
<LogOut size={15} />
</button>
</div>
</div>
@@ -14,23 +14,25 @@ export function SettingsLayout() {
];
return (
<div className="flex-1 flex bg-bg-secondary overflow-hidden">
<div className="flex-1 flex bg-bg-primary overflow-hidden">
<div className="w-56 bg-bg-sidebar border-r border-border-primary flex flex-col">
<div className="px-5 py-4 border-b border-border-primary">
<h2 className="text-sm font-bold text-text-primary">{t('settings.settings')}</h2>
<div className="px-4 py-3.5">
<span className="text-[10px] font-semibold text-text-muted uppercase tracking-[1.5px]">
{t('settings.settings')}
</span>
</div>
<div className="p-2 space-y-0.5">
<div className="px-1.5 pb-2 space-y-0.5">
{tabs.map((tab) => (
<button
key={tab.key}
onClick={() => setSettingsTab(tab.key)}
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-xs font-medium transition-all ${
className={`w-full flex items-center gap-2.5 px-2.5 py-2 rounded-lg text-xs font-medium transition-all ${
settingsTab === tab.key
? 'bg-accent-light text-accent'
: 'text-text-muted hover:text-text-secondary hover:bg-bg-hover'
? 'bg-bg-card text-text-primary shadow-[0_1px_3px_rgba(0,0,0,0.04)]'
: 'text-text-muted hover:text-text-secondary hover:bg-white/50 dark:hover:bg-white/[0.04]'
}`}
>
<tab.icon size={15} />
<tab.icon size={14} />
{tab.label}
</button>
))}
+3 -1
View File
@@ -88,7 +88,9 @@
"running": "Running",
"completed": "Completed",
"failed": "Failed"
}
},
"total": "Total",
"queued": "Queued"
},
"settings": {
"settings": "Settings",
+3 -1
View File
@@ -88,7 +88,9 @@
"running": "运行中",
"completed": "已完成",
"failed": "失败"
}
},
"total": "总数",
"queued": "排队中"
},
"settings": {
"settings": "设置",
+85 -69
View File
@@ -38,72 +38,83 @@
--color-warning-bg: var(--warning-bg);
--color-glow-purple: var(--glow-purple);
--color-glow-cyan: var(--glow-cyan);
--color-clay: var(--clay);
--color-slate: var(--slate);
--color-terracotta: var(--terracotta);
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* ===== Morandi Light ===== */
:root {
--bg-primary: #fafafa;
--bg-secondary: #f4f4f5;
--bg-tertiary: #e4e4e7;
--bg-card: #ffffff;
--bg-sidebar: #f4f4f5;
--bg-topbar: rgba(255, 255, 255, 0.72);
--bg-input: #f4f4f5;
--bg-hover: #f4f4f5;
--bg-active: #eff6ff;
--bg-glass: rgba(255, 255, 255, 0.6);
--border-primary: #e4e4e7;
--border-secondary: #f4f4f5;
--text-primary: #18181b;
--text-secondary: #3f3f46;
--text-tertiary: #52525b;
--text-muted: #a1a1aa;
--accent: #4f46e5;
--accent-hover: #4338ca;
--accent-light: #e0e7ff;
--accent-text: #3730a3;
--accent-glow: rgba(79, 70, 229, 0.15);
--danger: #dc2626;
--danger-bg: #fef2f2;
--success: #16a34a;
--success-bg: #f0fdf4;
--warning: #d97706;
--warning-bg: #fffbeb;
--glow-purple: #a855f7;
--glow-cyan: #06b6d4;
--bg-primary: #f2f0ed;
--bg-secondary: #eae8e4;
--bg-tertiary: #e0ddd8;
--bg-card: #faf9f7;
--bg-sidebar: #eae8e4;
--bg-topbar: rgba(250, 249, 247, 0.85);
--bg-input: #f2f0ed;
--bg-hover: rgba(255, 255, 255, 0.4);
--bg-active: rgba(156, 175, 136, 0.08);
--bg-glass: rgba(250, 249, 247, 0.72);
--border-primary: #e0ddd8;
--border-secondary: #eae8e4;
--text-primary: #3d3d3d;
--text-secondary: #5a5a5a;
--text-tertiary: #8c8680;
--text-muted: #b5afa8;
--accent: #9caf88;
--accent-hover: #8a9e78;
--accent-light: rgba(156, 175, 136, 0.12);
--accent-text: #7a8e6a;
--accent-glow: rgba(156, 175, 136, 0.2);
--danger: #c4917a;
--danger-bg: rgba(196, 145, 122, 0.08);
--success: #7a8e6a;
--success-bg: rgba(122, 142, 106, 0.08);
--warning: #c4a882;
--warning-bg: rgba(196, 168, 130, 0.08);
--glow-purple: #8a9aaa;
--glow-cyan: #c4a882;
--clay: #c4a882;
--slate: #8a9aaa;
--terracotta: #c4917a;
}
/* ===== Morandi Dark ===== */
.dark {
--bg-primary: #09090b;
--bg-secondary: #0f0f11;
--bg-tertiary: #18181b;
--bg-card: #131316;
--bg-sidebar: #0c0c0e;
--bg-topbar: rgba(9, 9, 11, 0.72);
--bg-input: #18181b;
--bg-hover: #1c1c1f;
--bg-active: rgba(79, 70, 229, 0.12);
--bg-glass: rgba(19, 19, 22, 0.6);
--border-primary: rgba(255, 255, 255, 0.08);
--border-secondary: rgba(255, 255, 255, 0.04);
--text-primary: #fafafa;
--text-secondary: #e4e4e7;
--text-tertiary: #a1a1aa;
--text-muted: #71717a;
--accent: #6366f1;
--accent-hover: #818cf8;
--accent-light: rgba(99, 102, 241, 0.15);
--accent-text: #a5b4fc;
--accent-glow: rgba(99, 102, 241, 0.25);
--danger: #f87171;
--danger-bg: rgba(248, 113, 113, 0.1);
--success: #4ade80;
--success-bg: rgba(74, 222, 128, 0.1);
--warning: #fbbf24;
--warning-bg: rgba(251, 191, 36, 0.1);
--glow-purple: #c084fc;
--glow-cyan: #67e8f9;
--bg-primary: #1c1b19;
--bg-secondary: #232220;
--bg-tertiary: #2d2b28;
--bg-card: #252421;
--bg-sidebar: #1e1d1b;
--bg-topbar: rgba(28, 27, 25, 0.85);
--bg-input: #2d2b28;
--bg-hover: rgba(255, 255, 255, 0.04);
--bg-active: rgba(156, 175, 136, 0.1);
--bg-glass: rgba(37, 36, 33, 0.72);
--border-primary: rgba(255, 255, 255, 0.06);
--border-secondary: rgba(255, 255, 255, 0.03);
--text-primary: #e8e6e3;
--text-secondary: #c8c5c0;
--text-tertiary: #a09c96;
--text-muted: #7a7772;
--accent: #a8bc94;
--accent-hover: #b8caa6;
--accent-light: rgba(156, 175, 136, 0.15);
--accent-text: #c4d4b4;
--accent-glow: rgba(156, 175, 136, 0.2);
--danger: #d4a894;
--danger-bg: rgba(196, 145, 122, 0.1);
--success: #9caf88;
--success-bg: rgba(156, 175, 136, 0.1);
--warning: #c4a882;
--warning-bg: rgba(196, 168, 130, 0.1);
--glow-purple: #a0aab8;
--glow-cyan: #c4b89e;
--clay: #c4a882;
--slate: #8a9aaa;
--terracotta: #c4917a;
}
html {
@@ -118,16 +129,19 @@ body {
-moz-osx-font-smoothing: grayscale;
}
/* Dot pattern background for dark mode */
.dark body::before {
/* Dot pattern background */
body::before {
content: "";
position: fixed;
inset: 0;
background-image: radial-gradient(circle at 1px 1px, rgba(255,255,255,0.04) 1px, transparent 0);
background-image: radial-gradient(circle at 1px 1px, rgba(0, 0, 0, 0.012) 1px, transparent 0);
background-size: 24px 24px;
pointer-events: none;
z-index: 0;
}
.dark body::before {
background-image: radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.015) 1px, transparent 0);
}
/* Custom scrollbar */
::-webkit-scrollbar {
@@ -139,7 +153,7 @@ body {
}
::-webkit-scrollbar-thumb {
background: var(--border-primary);
border-radius: 10px;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
@@ -209,21 +223,23 @@ body {
/* Glass effect */
.glass {
background: var(--bg-glass);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
backdrop-filter: blur(16px) saturate(140%);
-webkit-backdrop-filter: blur(16px) saturate(140%);
border-bottom: 1px solid var(--border-primary);
}
/* Modern card hover */
/* Card hover */
.card-hover {
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, border-color 0.2s ease;
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.25s ease, border-color 0.25s ease;
}
.card-hover:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px -8px rgba(0, 0, 0, 0.12);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
border-color: #d5d0ca;
}
.dark .card-hover:hover {
box-shadow: 0 8px 30px -8px rgba(0, 0, 0, 0.4);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
border-color: rgba(255, 255, 255, 0.1);
}
/* Glow effects */
@@ -231,7 +247,7 @@ body {
box-shadow: 0 0 20px -4px var(--accent-glow);
}
.glow-purple {
box-shadow: 0 0 30px -6px rgba(168, 85, 247, 0.3);
box-shadow: 0 0 30px -6px rgba(138, 154, 170, 0.2);
}
/* Status indicator pulse */