diff --git a/frontend/src/api/conversation.ts b/frontend/src/api/conversation.ts index 1252dca..f3bf163 100644 --- a/frontend/src/api/conversation.ts +++ b/frontend/src/api/conversation.ts @@ -25,6 +25,8 @@ export interface ChatStreamHandlers { onError?: (message: string) => void } +export type ChatRuntime = 'jarvis' | 'hermes' + export interface MessageAttachment { id: string name: string @@ -84,12 +86,13 @@ export const conversationApi = { return api.delete(`/api/conversations/${conversationId}`) }, - chat(message: string, conversationId?: string, fileIds: string[] = [], modelName?: string) { + chat(message: string, conversationId?: string, fileIds: string[] = [], modelName?: string, runtime?: ChatRuntime) { return api.post('/api/conversations/chat', { message, conversation_id: conversationId, file_ids: fileIds, model_name: modelName, + runtime, }) }, @@ -98,6 +101,7 @@ export const conversationApi = { conversationId?: string, fileIds: string[] = [], modelName?: string, + runtime: ChatRuntime = 'jarvis', handlers: ChatStreamHandlers = {}, ) { const token = localStorage.getItem('access_token') @@ -113,6 +117,7 @@ export const conversationApi = { conversation_id: conversationId, file_ids: fileIds, model_name: modelName, + runtime, }), }) diff --git a/frontend/src/api/document.ts b/frontend/src/api/document.ts index 4b6ca82..68b0a61 100644 --- a/frontend/src/api/document.ts +++ b/frontend/src/api/document.ts @@ -50,6 +50,25 @@ export interface UploadResponse { indexed_at?: string | null } +export interface RAGSource { + id: string + title: string + file_type: string + similarity?: number + chunk_content?: string +} + +export interface RAGChatRequest { + query: string + top_k?: number + mode?: 'hybrid' | 'semantic' | 'keyword' +} + +export interface RAGChatResponse { + answer: string + sources: RAGSource[] +} + export const documentApi = { list(folderId?: string | null) { return api.get('/api/documents', { @@ -93,4 +112,8 @@ export const documentApi = { getContent(id: string) { return api.get(`/api/documents/${id}/content`) }, + + ragChat(payload: RAGChatRequest) { + return api.post('/api/documents/rag', payload) + }, } diff --git a/frontend/src/api/scheduleCenter.ts b/frontend/src/api/scheduleCenter.ts index 12ec15b..33a7c81 100644 --- a/frontend/src/api/scheduleCenter.ts +++ b/frontend/src/api/scheduleCenter.ts @@ -1,7 +1,7 @@ import api from './index' import type { Goal } from './goal' import type { Reminder } from './reminder' -import type { Task } from './task' +import type { Task, TaskAssigneeType, TaskDispatchStatus, TaskPriority, TaskQuadrant, TaskStatus } from './task' import type { Todo } from './todo' export interface ScheduleCenterDaySummary { @@ -14,6 +14,47 @@ export interface ScheduleCenterDaySummary { goal_total: number } +export interface ScheduleCenterFocusTask { + id: string + title: string + status: TaskStatus + priority: TaskPriority + quadrant?: TaskQuadrant | null + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null + dispatch_status: TaskDispatchStatus + due_date?: string | null +} + +export interface ScheduleCenterQuadrantTask { + id: string + title: string + status: TaskStatus + priority: TaskPriority + dispatch_status: TaskDispatchStatus + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null +} + +export interface ScheduleCenterQuadrant { + id: TaskQuadrant + title: string + subtitle: string + color: string + glow_color: string + icon: string + tasks: ScheduleCenterQuadrantTask[] +} + +export interface ScheduleCenterCommanderSummary { + total: number + queued: number + running: number + completed: number + failed: number + overall_status: string +} + export interface ScheduleCenterMonthResponse { month: string days: ScheduleCenterDaySummary[] @@ -26,6 +67,9 @@ export interface ScheduleCenterDateResponse { reminders: Reminder[] goals: Goal[] summary: ScheduleCenterDaySummary + focus_tasks: ScheduleCenterFocusTask[] + quadrants: ScheduleCenterQuadrant[] + commander_summary: ScheduleCenterCommanderSummary generated_at: string } diff --git a/frontend/src/api/task.ts b/frontend/src/api/task.ts index cb6a0fe..f5c3932 100644 --- a/frontend/src/api/task.ts +++ b/frontend/src/api/task.ts @@ -2,34 +2,225 @@ import api from './index' export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled' export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent' +export type TaskSource = 'manual' | 'chat' | 'schedule_center' | 'today_status' | 'commander' +export type TaskQuadrant = + | 'urgent-important' + | 'not-urgent-important' + | 'urgent-not-important' + | 'not-urgent-not-important' +export type TaskDispatchStatus = 'idle' | 'queued' | 'running' | 'completed' | 'failed' +export type TaskAssigneeType = + | 'user' + | 'commander' + | 'agent' + | 'planner' + | 'executor' + | 'knowledge' + | 'analyst' + | 'coder' + | 'researcher' -export interface Task { +export interface TaskSubTask { id: string + task_id: string title: string - description?: string + description?: string | null status: TaskStatus - priority: TaskPriority - due_date?: string - completed_at?: string - tags?: string + order_index: number + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null + dispatch_status: TaskDispatchStatus + dispatch_run_id?: string | null + result_summary?: string | null + completed_at?: string | null created_at: string updated_at: string } +export interface TaskHistoryEntry { + id: string + task_id: string + action: string + old_value?: string | null + new_value?: string | null + created_at: string + updated_at: string +} + +export interface TaskDispatchSummary { + status: TaskDispatchStatus + run_id?: string | null + result_summary?: string | null + started_at?: string | null + last_synced_at?: string | null + total_subtasks?: number + dispatched_subtasks?: number + subtask_dispatch_statuses?: Record +} + +export interface Task { + id: string + title: string + description?: string | null + status: TaskStatus + priority: TaskPriority + due_date?: string | null + completed_at?: string | null + tags: string[] + source: TaskSource + conversation_id?: string | null + quadrant?: TaskQuadrant | null + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null + dispatch_status: TaskDispatchStatus + dispatch_run_id?: string | null + result_summary?: string | null + started_at?: string | null + last_synced_at?: string | null + subtask_count?: number + created_at: string + updated_at: string +} + +export interface TaskDetail extends Task { + subtasks: TaskSubTask[] + history: TaskHistoryEntry[] + dispatch: TaskDispatchSummary + dispatch_summary: TaskDispatchSummary +} + +export interface TaskDispatchResponse { + status: TaskDispatchStatus + run_id?: string | null + task: TaskDetail + payload: Record +} + +export interface TaskCreateInput { + title: string + description?: string + status?: TaskStatus + priority?: TaskPriority + due_date?: string + tags?: string[] + source?: TaskSource + conversation_id?: string + quadrant?: TaskQuadrant + assignee_type?: TaskAssigneeType + assignee_id?: string + subtasks?: Array<{ + title: string + description?: string + status?: TaskStatus + order_index?: number + assignee_type?: TaskAssigneeType + assignee_id?: string + }> + dispatch_to_commander?: boolean +} + +export interface TaskUpdateInput { + title?: string + description?: string | null + status?: TaskStatus + priority?: TaskPriority + due_date?: string | null + tags?: string[] + source?: TaskSource + conversation_id?: string | null + quadrant?: TaskQuadrant | null + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null + dispatch_status?: TaskDispatchStatus + dispatch_run_id?: string | null + result_summary?: string | null + started_at?: string | null + last_synced_at?: string | null +} + +export interface TaskSubTaskCreateInput { + title: string + description?: string + status?: TaskStatus + order_index?: number + assignee_type?: TaskAssigneeType + assignee_id?: string +} + +export interface TaskSubTaskUpdateInput { + title?: string + description?: string | null + status?: TaskStatus + order_index?: number + assignee_type?: TaskAssigneeType | null + assignee_id?: string | null + dispatch_status?: TaskDispatchStatus + dispatch_run_id?: string | null + result_summary?: string | null +} + +export interface TaskSubTaskReorderInput { + items: Array<{ id: string; order_index: number }> +} + +export interface TaskDispatchInput { + target?: 'commander' + conversation_id?: string + assignee_type?: TaskAssigneeType + assignee_id?: string +} + export const taskApi = { - list(filters?: { status?: TaskStatus; due_date?: string; date_from?: string; date_to?: string }) { + list(filters?: { + status?: TaskStatus + due_date?: string + date_from?: string + date_to?: string + quadrant?: TaskQuadrant + assignee_type?: TaskAssigneeType + dispatch_status?: TaskDispatchStatus + conversation_id?: string + }) { return api.get('/api/tasks', { params: filters ?? {} }) }, - create(data: { title: string; description?: string; priority?: TaskPriority; due_date?: string }) { - return api.post('/api/tasks', data) + create(data: TaskCreateInput) { + return api.post('/api/tasks', data) }, - update(id: string, data: Partial) { - return api.patch(`/api/tasks/${id}`, data) + detail(id: string) { + return api.get(`/api/tasks/${id}`) + }, + + update(id: string, data: TaskUpdateInput) { + return api.patch(`/api/tasks/${id}`, data) }, delete(id: string) { return api.delete(`/api/tasks/${id}`) }, + + createSubtask(taskId: string, data: TaskSubTaskCreateInput) { + return api.post(`/api/tasks/${taskId}/subtasks`, data) + }, + + updateSubtask(taskId: string, subtaskId: string, data: TaskSubTaskUpdateInput) { + return api.patch(`/api/tasks/${taskId}/subtasks/${subtaskId}`, data) + }, + + deleteSubtask(taskId: string, subtaskId: string) { + return api.delete(`/api/tasks/${taskId}/subtasks/${subtaskId}`) + }, + + reorderSubtasks(taskId: string, data: TaskSubTaskReorderInput) { + return api.post(`/api/tasks/${taskId}/subtasks/reorder`, data) + }, + + dispatch(taskId: string, data: TaskDispatchInput = {}) { + return api.post(`/api/tasks/${taskId}/dispatch`, { target: 'commander', ...data }) + }, + + dispatchSubtask(taskId: string, subtaskId: string, data: TaskDispatchInput = {}) { + return api.post(`/api/tasks/${taskId}/subtasks/${subtaskId}/dispatch`, { target: 'commander', ...data }) + }, } diff --git a/frontend/src/components/chat/KanbanDetail.vue b/frontend/src/components/chat/KanbanDetail.vue index 1a2881d..250351e 100644 --- a/frontend/src/components/chat/KanbanDetail.vue +++ b/frontend/src/components/chat/KanbanDetail.vue @@ -1,86 +1,309 @@ @@ -288,7 +605,8 @@ function removeSubtask(index: number) { .kanban-detail-overlay { position: fixed; inset: 0; - background: rgba(2, 6, 14, 0.85); + background: rgba(2, 6, 14, 0.92); + backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; @@ -296,7 +614,26 @@ function removeSubtask(index: number) { visibility: hidden; transition: opacity var(--transition-mid), visibility var(--transition-mid); z-index: 9999; - isolation: isolate; + overflow: hidden; +} + +.kanban-detail-panel { + position: relative; + width: 95%; + max-width: 1400px; + height: 85vh; + border-radius: 20px; + border: 1px solid rgba(0, 245, 212, 0.3); + background: linear-gradient(180deg, rgba(8, 18, 36, 0.98) 0%, rgba(5, 10, 20, 0.99) 100%); + box-shadow: + 0 0 60px rgba(0, 245, 212, 0.15), + 0 0 120px rgba(0, 245, 212, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); + display: flex; + flex-direction: column; + overflow: hidden; + transform: scale(0.95) translateY(20px); + transition: transform var(--transition-mid), opacity var(--transition-mid); } .kanban-detail-overlay.visible { @@ -306,392 +643,572 @@ function removeSubtask(index: number) { .kanban-detail-panel { position: relative; - width: 90%; - max-width: 1100px; - max-height: 90%; - border-radius: 16px; - border: 1px solid rgba(34, 211, 238, 0.22); - background: linear-gradient(180deg, rgba(12, 20, 36, 0.98), rgba(8, 14, 28, 0.96)); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 60px rgba(34, 211, 238, 0.1); + width: 95%; + max-width: 1400px; + max-height: 90vh; + border-radius: 20px; + border: 1px solid rgba(0, 245, 212, 0.3); + background: linear-gradient(180deg, rgba(8, 18, 36, 0.98) 0%, rgba(5, 10, 20, 0.99) 100%); + box-shadow: + 0 0 60px rgba(0, 245, 212, 0.15), + 0 0 120px rgba(0, 245, 212, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.05); display: flex; flex-direction: column; overflow: hidden; - transform: scale(0.95); - transition: transform var(--transition-mid); + transform: scale(0.95) translateY(20px); + transition: transform var(--transition-mid), opacity var(--transition-mid); } .kanban-detail-overlay.visible .kanban-detail-panel { - transform: scale(1); + transform: scale(1) translateY(0); + opacity: 1; } +.panel-grid-overlay { + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(0, 245, 212, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 245, 212, 0.03) 1px, transparent 1px); + background-size: 30px 30px; + pointer-events: none; + z-index: 0; +} + +/* Header */ .detail-header { display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 24px; - border-bottom: 1px solid rgba(0, 243, 255, 0.1); -} - -.header-actions { - display: flex; - align-items: center; - gap: 16px; -} - -.btn-delete-icon { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(255, 77, 79, 0.3); - background: rgba(255, 77, 79, 0.08); - color: #ff4d4f; - cursor: pointer; - transition: all var(--transition-fast); - display: flex; - align-items: center; - justify-content: center; -} - -.btn-delete-icon:hover { - background: rgba(255, 77, 79, 0.2); - border-color: #ff4d4f; -} - -.btn-delete-icon :deep(svg) { - stroke: currentColor; -} - -.task-title-section { - flex: 1; - min-width: 0; -} - -.task-title { - font-size: 22px; - font-weight: 600; - color: #e0f7ff; - margin-bottom: 10px; - display: inline-flex; - align-items: center; - gap: 8px; - max-width: 100%; -} - -.check-icon { - color: #52c41a; + flex-direction: column; + gap: 10px; + padding: 20px 24px; + border-bottom: 1px solid rgba(0, 245, 212, 0.12); + position: relative; + z-index: 1; + background: linear-gradient(180deg, rgba(0, 245, 212, 0.06) 0%, transparent 100%); flex-shrink: 0; } +.header-top { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header-left { + flex: 1; + display: flex; + align-items: center; + gap: 12px; +} + +.quadrant-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 5px 12px; + background: rgba(0, 0, 0, 0.4); + border: 1px solid var(--badge-color); + border-radius: 20px; + width: fit-content; +} + +.badge-dot { + width: 6px; + height: 6px; + background: var(--badge-color); + border-radius: 50%; + box-shadow: 0 0 8px var(--badge-color); + animation: badge-pulse 2s ease-in-out infinite; +} + +@keyframes badge-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.badge-text { + font-family: var(--font-mono); + font-size: 10px; + color: var(--badge-color); + letter-spacing: 0.1em; +} + +.task-title-row { + display: flex; + align-items: center; + gap: 12px; + flex: 1; +} + +.task-complete-btn { + background: none; + border: none; + cursor: pointer; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.complete-box { + width: 26px; + height: 26px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 6px; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + color: var(--accent-cyan); + transition: all 0.2s ease; +} + +.task-complete-btn:hover .complete-box { + border-color: var(--accent-cyan); + box-shadow: 0 0 12px rgba(0, 245, 212, 0.4); +} + +.task-complete-btn.completed .complete-box { + background: var(--accent-cyan); + border-color: var(--accent-cyan); +} + +.task-complete-btn.completed .complete-box svg { + color: #000; +} + .title-text { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 400px; + font-family: var(--font-display); + font-size: 20px; + font-weight: 600; + color: #fff; + letter-spacing: 0.02em; + margin: 0; + cursor: pointer; } .title-input { - font-size: 22px; + font-family: var(--font-display); + font-size: 20px; font-weight: 600; - font-family: inherit; - color: #e0f7ff; - background: rgba(0, 243, 255, 0.08); - border: 1px solid rgba(0, 243, 255, 0.3); + color: #fff; + background: rgba(0, 245, 212, 0.1); + border: 1px solid rgba(0, 245, 212, 0.4); border-radius: 6px; - padding: 4px 10px; + padding: 2px 10px; outline: none; - min-width: 200px; - max-width: 400px; -} - -.title-input:focus { - border-color: rgba(0, 243, 255, 0.6); - box-shadow: 0 0 12px rgba(0, 245, 212, 0.2); + min-width: 300px; } .btn-edit-title { width: 28px; height: 28px; border-radius: 6px; - border: 1px solid transparent; - background: transparent; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(0, 0, 0, 0.3); color: var(--text-dim); cursor: pointer; - transition: all var(--transition-fast); display: flex; align-items: center; justify-content: center; - opacity: 0; - flex-shrink: 0; + transition: all 0.2s ease; + opacity: 0.5; } -.task-title:hover .btn-edit-title { +.task-title-row:hover .btn-edit-title { opacity: 1; } .btn-edit-title:hover { - background: rgba(0, 243, 255, 0.1); - border-color: rgba(0, 243, 255, 0.2); + border-color: var(--accent-cyan); color: var(--accent-cyan); } -.btn-edit-title :deep(svg) { - stroke: currentColor; +.header-meta { + display: flex; + align-items: center; + gap: 10px; + padding-left: 38px; } -.task-meta { - font-size: 14px; +.header-meta .meta-item { + display: flex; + align-items: center; + gap: 5px; + font-family: var(--font-mono); + font-size: 11px; color: var(--text-dim); } -.avatar { - display: inline-block; - width: 24px; - height: 24px; - border-radius: 50%; - background: linear-gradient(135deg, #00f3ff, #0891b2); - color: #000; - text-align: center; - line-height: 24px; - font-weight: 600; - font-size: 12px; - margin-right: 6px; +.header-meta .meta-separator { + color: rgba(255, 255, 255, 0.1); } -.detail-content { - padding: 20px 24px; - flex: 1; - overflow-y: auto; - min-height: 0; +.meta-select { + background: transparent; + border: none; + color: var(--text-secondary); + font-family: var(--font-mono); + font-size: 11px; + cursor: pointer; + outline: none; + padding: 2px 4px; } -.main-content { - display: flex; - flex-direction: column; - gap: 24px; +.meta-select option { + background: #0a0f1a; + color: #fff; } -.section-title { - font-size: 16px; - font-weight: 600; - color: #e0f7ff; - margin-bottom: 12px; +.meta-input { + width: 96px; + padding: 4px 8px; + border-radius: 6px; + border: 1px solid rgba(0, 245, 212, 0.15); + background: rgba(0, 0, 0, 0.35); + color: var(--text-secondary); + font-family: var(--font-mono); + font-size: 11px; + outline: none; +} + +.meta-input::placeholder { + color: var(--text-dim); +} + +.priority-select-wrapper { + padding-left: 6px; + border-left: 2px solid var(--priority-color, #00d4ff); +} + +.priority-select { + color: var(--priority-color, #00d4ff); + font-weight: 500; +} + +.priority-select-wrapper[style*="--priority-color"] { + border-left-color: var(--priority-color); +} + +.header-description { + padding-left: 38px; +} + +.header-description .description-box { + width: 100%; + min-height: 60px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 245, 212, 0.1); + border-radius: 8px; + padding: 10px 12px; + color: var(--text-secondary); + font-family: var(--font-body); + font-size: 13px; + line-height: 1.5; + resize: vertical; + outline: none; + transition: all 0.2s ease; + box-sizing: border-box; +} + +.header-description .description-box:focus { + border-color: rgba(0, 245, 212, 0.3); +} + +.header-description .description-box::placeholder { + color: var(--text-dim); +} + +.header-actions { display: flex; align-items: center; gap: 8px; } -.section-title .icon { - font-size: 16px; - color: var(--text-dim); +.btn-action { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + padding: 0; + border-radius: 8px; + border: 1px solid rgba(0, 245, 212, 0.3); + background: rgba(0, 0, 0, 0.4); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s ease; } -.section-title .tab { - font-size: 14px; +.btn-action:hover { + border-color: var(--accent-cyan); + color: var(--accent-cyan); +} + +.btn-action.btn-save { + background: rgba(0, 245, 212, 0.15); + color: var(--accent-cyan); +} + +.btn-action.btn-delete { + border-color: rgba(255, 71, 87, 0.4); + color: #ff6b7a; +} + +.btn-action.btn-delete:hover { + background: rgba(255, 71, 87, 0.15); + border-color: #ff4757; +} + +.btn-close { + width: 34px; + height: 34px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: rgba(0, 0, 0, 0.4); color: var(--text-dim); cursor: pointer; - padding-bottom: 4px; - border-bottom: 2px solid transparent; - margin-right: 12px; - font-weight: normal; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; } -.section-title .tab.active { - color: #e0f7ff; - border-bottom-color: #00f3ff; +.btn-close:hover { + border-color: rgba(255, 71, 87, 0.5); + color: #ff4757; + background: rgba(255, 71, 87, 0.1); +} + +/* Content Grid - 2 Column Layout */ +.detail-content { + flex: 1; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + padding: 16px 24px; + overflow-y: auto; + position: relative; + z-index: 1; + min-height: 0; +} + +.content-col { + display: flex; + flex-direction: column; + gap: 16px; + min-height: 0; + overflow: hidden; +} + +.content-col.subtasks-col { + flex: 1; + overflow: hidden; + min-height: 0; +} + +.content-col.right-col { + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; + min-height: 0; +} + +.subtasks-col .section, +.right-col .section { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + min-height: 0; +} + +.subtask-list { + flex: 1; + overflow-y: auto; + min-height: 0; +} + +/* Section */ +.section { + background: rgba(0, 20, 40, 0.5); + border: 1px solid rgba(0, 245, 212, 0.1); + border-radius: 12px; + padding: 16px; + transition: all 0.3s ease; +} + +.section:hover { + border-color: rgba(0, 245, 212, 0.2); +} + +.section-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.section-icon { + color: var(--accent-cyan); + opacity: 0.8; +} + +.section-title { + font-family: var(--font-display); + font-size: 12px; font-weight: 600; + color: #fff; + letter-spacing: 0.08em; } +.section-count { + margin-left: auto; + font-family: var(--font-mono); + font-size: 10px; + color: var(--accent-cyan); + padding: 2px 8px; + background: rgba(0, 245, 212, 0.1); + border-radius: 10px; +} + +/* Description (inline in header) */ .description-box { width: 100%; + min-height: 60px; background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 243, 255, 0.1); + border: 1px solid rgba(0, 245, 212, 0.1); border-radius: 8px; - padding: 16px; - min-height: 100px; - color: var(--text-dim); - font-size: 14px; - font-family: inherit; + padding: 10px 12px; + color: var(--text-secondary); + font-family: var(--font-body); + font-size: 13px; + line-height: 1.5; resize: vertical; outline: none; - transition: border-color var(--transition-fast); + transition: all 0.2s ease; box-sizing: border-box; } .description-box:focus { - border-color: rgba(0, 243, 255, 0.3); + border-color: rgba(0, 245, 212, 0.3); } .description-box::placeholder { color: var(--text-dim); } -.task-info-list { - display: flex; - flex-direction: column; - gap: 12px; - font-size: 14px; +/* Subtask */ +.subtask-progress { + margin-bottom: 12px; } -.info-item { - display: flex; - align-items: center; - gap: 8px; - color: #e0f7ff; +.progress-bar { + height: 3px; + background: rgba(0, 0, 0, 0.4); + border-radius: 2px; + overflow: hidden; } -.info-item .icon { - color: var(--text-dim); - font-size: 14px; - width: 20px; - text-align: center; -} - -.agent-select { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 243, 255, 0.2); - border-radius: 6px; - color: #e0f7ff; - font-size: 13px; - font-family: inherit; - padding: 6px 10px; - cursor: pointer; - outline: none; - transition: border-color var(--transition-fast); -} - -.agent-select:hover { - border-color: rgba(0, 243, 255, 0.4); -} - -.agent-select:focus { - border-color: rgba(0, 243, 255, 0.5); -} - -.agent-select option { - background: #0a0f1a; - color: #e0f7ff; -} - -.priority-select { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 243, 255, 0.2); - border-radius: 6px; - color: #e0f7ff; - font-size: 13px; - font-family: inherit; - padding: 6px 10px; - cursor: pointer; - outline: none; - transition: border-color var(--transition-fast); -} - -.priority-select:hover { - border-color: rgba(0, 243, 255, 0.4); -} - -.priority-select:focus { - border-color: rgba(0, 243, 255, 0.5); -} - -.priority-select option, -.status-select option { - background: #0a0f1a; - color: #e0f7ff; -} - -.status-select { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 243, 255, 0.2); - border-radius: 6px; - color: #e0f7ff; - font-size: 13px; - font-family: inherit; - padding: 6px 10px; - cursor: pointer; - outline: none; - transition: border-color var(--transition-fast); -} - -.status-select:hover { - border-color: rgba(0, 243, 255, 0.4); -} - -.status-select:focus { - border-color: rgba(0, 243, 255, 0.5); -} - -.priority-high { - color: #ff4d4f; - font-weight: 600; -} - -.status-completed { - color: var(--text-dim); -} - -.subtask-section { - justify-content: space-between; - align-items: center; -} - -.btn-add-subtask { - padding: 6px 12px; - border: 1px solid rgba(0, 243, 255, 0.3); - border-radius: 4px; - background: rgba(0, 243, 255, 0.06); - color: var(--accent-cyan); - font-size: 12px; - cursor: pointer; - transition: all var(--transition-fast); -} - -.btn-add-subtask:hover { - background: rgba(0, 243, 255, 0.12); - border-color: rgba(0, 243, 255, 0.5); -} - -.subtask-empty { - color: var(--text-dim); - font-size: 14px; +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--accent-cyan), #00f5d4); + border-radius: 2px; + transition: width 0.3s ease; + box-shadow: 0 0 8px rgba(0, 245, 212, 0.5); } .subtask-list { display: flex; flex-direction: column; - gap: 8px; + gap: 6px; + flex: 1; + overflow-y: auto; + min-height: 0; } .subtask-item { display: flex; align-items: center; - gap: 10px; - padding: 8px; - background: rgba(0, 0, 0, 0.2); - border: 1px solid rgba(0, 243, 255, 0.08); + gap: 8px; + padding: 8px 10px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 245, 212, 0.08); border-radius: 8px; - transition: border-color var(--transition-fast), opacity var(--transition-fast); + transition: all 0.2s ease; +} + +.subtask-item:hover { + border-color: rgba(0, 245, 212, 0.2); + background: rgba(0, 245, 212, 0.03); } .subtask-item.drag-over { - border-color: rgba(0, 243, 255, 0.5); + border-color: var(--accent-cyan); border-style: dashed; } +.subtask-item.completed { + opacity: 0.6; +} + +.subtask-drag { + background: none; + border: none; + color: var(--text-dim); + cursor: grab; + padding: 2px; + display: flex; + align-items: center; + opacity: 0.5; +} + +.subtask-drag:hover { + opacity: 1; + color: var(--accent-cyan); +} + +.subtask-checkbox-wrapper { + display: flex; + align-items: center; + cursor: pointer; +} + .subtask-checkbox { + position: absolute; + opacity: 0; +} + +.custom-checkbox { width: 16px; height: 16px; - cursor: pointer; - flex-shrink: 0; + border: 2px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + color: #000; +} + +.custom-checkbox.checked { + background: var(--accent-cyan); + border-color: var(--accent-cyan); } .subtask-input { flex: 1; background: transparent; border: none; - color: #e0f7ff; - font-size: 13px; - font-family: inherit; + color: var(--text-secondary); + font-family: var(--font-body); + font-size: 12px; outline: none; + min-width: 0; } .subtask-input.completed { @@ -703,106 +1220,246 @@ function removeSubtask(index: number) { color: var(--text-dim); } -.subtask-delete { - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--text-dim); - font-size: 18px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all var(--transition-fast); - flex-shrink: 0; -} - -.subtask-delete:hover { - background: rgba(255, 77, 79, 0.2); - color: #ff4d4f; -} - -.subtask-drag { - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--text-dim); - font-size: 14px; - cursor: grab; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - transition: all var(--transition-fast); - flex-shrink: 0; -} - -.subtask-drag:hover { - background: rgba(0, 243, 255, 0.1); - color: var(--accent-cyan); -} - .subtask-agent { - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 243, 255, 0.15); + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(0, 245, 212, 0.1); border-radius: 4px; - color: #e0f7ff; + color: var(--text-secondary); font-size: 11px; - font-family: inherit; - padding: 4px 6px; + padding: 3px 6px; cursor: pointer; outline: none; - transition: border-color var(--transition-fast); - flex-shrink: 0; - min-width: 80px; -} - -.subtask-agent:hover { - border-color: rgba(0, 243, 255, 0.3); -} - -.subtask-agent:focus { - border-color: rgba(0, 243, 255, 0.4); + width: 50px; } .subtask-agent option { background: #0a0f1a; - color: #e0f7ff; +} + +.subtask-delete { + background: none; + border: none; + color: var(--text-dim); + cursor: pointer; + padding: 2px; + display: flex; + align-items: center; + border-radius: 3px; + opacity: 0; + transition: all 0.2s ease; +} + +.subtask-item:hover .subtask-delete { + opacity: 1; +} + +.subtask-delete:hover { + color: #ff4757; + background: rgba(255, 71, 87, 0.15); +} + +.subtask-empty { + text-align: center; + padding: 16px; + color: var(--text-dim); + font-size: 12px; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.empty-add { + display: flex; + align-items: center; + gap: 5px; + padding: 6px 12px; + border: 1px dashed rgba(0, 245, 212, 0.3); + border-radius: 6px; + background: transparent; + color: var(--accent-cyan); + font-size: 11px; + cursor: pointer; + transition: all 0.2s ease; +} + +.empty-add:hover { + background: rgba(0, 245, 212, 0.1); + border-style: solid; +} + +.add-subtask-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px; + border: 1px dashed rgba(0, 245, 212, 0.2); + border-radius: 8px; + background: transparent; + color: var(--text-dim); + font-size: 11px; + cursor: pointer; + transition: all 0.2s ease; + margin-top: 4px; +} + +.add-subtask-btn:hover { + border-color: var(--accent-cyan); + color: var(--accent-cyan); + background: rgba(0, 245, 212, 0.05); +} + +/* Timeline */ +.timeline { + display: flex; + flex-direction: column; + max-height: 180px; + overflow-y: auto; +} + +.timeline-item { + display: flex; + gap: 12px; + padding-bottom: 14px; +} + +.timeline-item:last-child { + padding-bottom: 0; +} + +.timeline-marker { + display: flex; + flex-direction: column; + align-items: center; + width: 14px; +} + +.marker-dot { + width: 8px; + height: 8px; + background: var(--accent-cyan); + border-radius: 50%; + box-shadow: 0 0 8px rgba(0, 245, 212, 0.5); + flex-shrink: 0; +} + +.marker-line { + width: 1px; + flex: 1; + background: linear-gradient(180deg, rgba(0, 245, 212, 0.4), rgba(0, 245, 212, 0.1)); + margin-top: 5px; +} + +.timeline-content { + flex: 1; +} + +.timeline-action { + font-size: 12px; + color: var(--text-secondary); + margin-bottom: 3px; +} + +.timeline-meta { + font-family: var(--font-mono); + font-size: 9px; + color: var(--text-dim); +} + +/* Comments */ +.comments-list { + display: flex; + flex-direction: column; + gap: 12px; + max-height: 160px; + overflow-y: auto; + margin-bottom: 12px; +} + +.comment-item { + display: flex; + gap: 10px; +} + +.comment-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + background: linear-gradient(135deg, var(--accent-cyan), #0891b2); + color: #000; + font-weight: 700; + font-size: 11px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.comment-body { + flex: 1; + min-width: 0; +} + +.comment-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; +} + +.comment-user { + font-family: var(--font-mono); + font-size: 11px; + color: var(--accent-cyan); +} + +.comment-time { + font-family: var(--font-mono); + font-size: 9px; + color: var(--text-dim); +} + +.comment-content { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; } .comment-empty { + text-align: center; + padding: 14px; color: var(--text-dim); - font-size: 14px; - padding: 12px 0; + font-size: 12px; +} + +.comment-empty.helper { + padding: 8px 0 0; + text-align: left; } .comment-input-area { display: flex; - gap: 10px; - align-items: flex-end; - border-top: 1px solid rgba(0, 243, 255, 0.1); - padding-top: 12px; + gap: 8px; } .comment-input { flex: 1; min-height: 50px; - padding: 10px 14px; - border: 1px solid rgba(0, 243, 255, 0.2); + padding: 8px 12px; + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(0, 245, 212, 0.15); border-radius: 8px; - background: rgba(0, 0, 0, 0.3); - color: #e0f7ff; - font-size: 14px; - resize: vertical; + color: var(--text-secondary); + font-size: 12px; + resize: none; outline: none; - transition: border-color var(--transition-fast); + transition: all 0.2s ease; + box-sizing: border-box; } .comment-input:focus { - border-color: rgba(0, 243, 255, 0.5); + border-color: rgba(0, 245, 212, 0.3); } .comment-input::placeholder { @@ -810,35 +1467,110 @@ function removeSubtask(index: number) { } .btn-comment { - padding: 10px 20px; - background: rgba(0, 243, 255, 0.1); - border: 1px solid rgba(0, 243, 255, 0.2); + width: 36px; + height: 36px; + background: rgba(0, 245, 212, 0.1); + border: 1px solid rgba(0, 245, 212, 0.2); border-radius: 8px; color: var(--accent-cyan); - font-size: 14px; cursor: pointer; - transition: all var(--transition-fast); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + flex-shrink: 0; } .btn-comment:hover { - background: rgba(0, 243, 255, 0.2); + background: rgba(0, 245, 212, 0.2); } -/* Scrollbar styling */ -.detail-content::-webkit-scrollbar { - width: 8px; +/* Footer */ +.detail-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 24px; + border-top: 1px solid rgba(0, 245, 212, 0.1); + background: rgba(0, 0, 0, 0.3); + position: relative; + z-index: 1; + flex-shrink: 0; } -.detail-content::-webkit-scrollbar-track { +.footer-info { + display: flex; + align-items: center; + gap: 8px; +} + +.footer-label { + font-family: var(--font-mono); + font-size: 9px; + color: var(--text-dim); + letter-spacing: 0.1em; +} + +.footer-value { + font-family: var(--font-mono); + font-size: 10px; + color: var(--accent-cyan); +} + +.footer-sync { + display: flex; + align-items: center; + gap: 6px; +} + +.sync-dot { + width: 5px; + height: 5px; + background: var(--accent-cyan); + border-radius: 50%; + animation: sync-blink 1.5s ease-in-out infinite; +} + +@keyframes sync-blink { + 0%, 100% { opacity: 1; box-shadow: 0 0 5px var(--accent-cyan); } + 50% { opacity: 0.4; } +} + +.sync-text { + font-family: var(--font-mono); + font-size: 9px; + color: var(--accent-cyan); + letter-spacing: 0.1em; + opacity: 0.7; +} + +/* Scrollbar */ +.detail-content::-webkit-scrollbar, +.subtask-list::-webkit-scrollbar, +.timeline::-webkit-scrollbar, +.comments-list::-webkit-scrollbar { + width: 4px; +} + +.detail-content::-webkit-scrollbar-track, +.subtask-list::-webkit-scrollbar-track, +.timeline::-webkit-scrollbar-track, +.comments-list::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.2); } -.detail-content::-webkit-scrollbar-thumb { - background: rgba(0, 243, 255, 0.2); - border-radius: 4px; +.detail-content::-webkit-scrollbar-thumb, +.subtask-list::-webkit-scrollbar-thumb, +.timeline::-webkit-scrollbar-thumb, +.comments-list::-webkit-scrollbar-thumb { + background: rgba(0, 245, 212, 0.2); + border-radius: 2px; } -.detail-content::-webkit-scrollbar-thumb:hover { - background: rgba(0, 243, 255, 0.4); +.detail-content::-webkit-scrollbar-thumb:hover, +.subtask-list::-webkit-scrollbar-thumb:hover, +.timeline::-webkit-scrollbar-thumb:hover, +.comments-list::-webkit-scrollbar-thumb:hover { + background: rgba(0, 245, 212, 0.4); } - \ No newline at end of file + diff --git a/frontend/src/components/chat/KanbanPanel.vue b/frontend/src/components/chat/KanbanPanel.vue index a0eac11..f7a88d7 100644 --- a/frontend/src/components/chat/KanbanPanel.vue +++ b/frontend/src/components/chat/KanbanPanel.vue @@ -1,104 +1,155 @@ @@ -139,186 +220,608 @@ const quadrants = computed(() => [ .kanban-frame { height: 100%; - border-radius: 18px; - border: 1px solid rgba(34, 211, 238, 0.18); - background: linear-gradient(180deg, rgba(8, 14, 28, 0.96), rgba(6, 10, 20, 0.92)); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 0 20px rgba(34, 211, 238, 0.08); + border-radius: 16px; + border: 1px solid rgba(0, 245, 212, 0.25); + background: + linear-gradient(135deg, rgba(8, 18, 36, 0.95) 0%, rgba(5, 10, 20, 0.98) 100%), + repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 245, 212, 0.02) 2px, + rgba(0, 245, 212, 0.02) 4px + ); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.05), + 0 0 40px rgba(0, 245, 212, 0.1), + 0 0 80px rgba(0, 245, 212, 0.05); display: flex; flex-direction: column; overflow: hidden; + position: relative; } +/* Tech grid overlay */ +.kanban-frame::before { + content: ''; + position: absolute; + inset: 0; + background-image: + linear-gradient(rgba(0, 245, 212, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(0, 245, 212, 0.03) 1px, transparent 1px); + background-size: 20px 20px; + pointer-events: none; + z-index: 0; +} + +/* ── Header ── */ .kanban-header { display: flex; - justify-content: space-between; align-items: center; - padding: 14px 16px; - border-bottom: 1px solid rgba(0, 243, 255, 0.1); + padding: 16px 20px; + border-bottom: 1px solid rgba(0, 245, 212, 0.15); flex-shrink: 0; + position: relative; + z-index: 1; + background: linear-gradient(180deg, rgba(0, 245, 212, 0.08) 0%, transparent 100%); } .kanban-title-row { display: flex; align-items: center; - gap: 6px; + gap: 12px; } -.kanban-cloud { - font-size: 16px; +.kanban-icon { + position: relative; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; +} + +.icon-core { + font-size: 20px; + color: var(--accent-cyan); + text-shadow: 0 0 10px var(--accent-cyan); + z-index: 1; +} + +.icon-pulse { + position: absolute; + inset: 0; + border: 1px solid var(--accent-cyan); + border-radius: 50%; + animation: icon-pulse 2s ease-in-out infinite; +} + +@keyframes icon-pulse { + 0%, 100% { transform: scale(1); opacity: 0.8; } + 50% { transform: scale(1.3); opacity: 0; } +} + +.kanban-title-group { + display: flex; + flex-direction: column; + gap: 2px; } .kanban-title { font-family: var(--font-display); - font-size: 14px; - font-weight: 600; - color: #e0f7ff; - letter-spacing: 0.08em; -} - -.kanban-nav { - display: flex; - gap: 20px; - font-size: 14px; -} - -.kanban-nav .nav-link { - color: var(--text-secondary); - text-decoration: none; -} - -.kanban-nav .nav-link:hover { + font-size: 16px; + font-weight: 700; color: var(--accent-cyan); + letter-spacing: 0.15em; + text-shadow: 0 0 20px rgba(0, 245, 212, 0.5); +} + +.kanban-subtitle { + font-family: var(--font-mono); + font-size: 9px; + color: var(--text-dim); + letter-spacing: 0.1em; +} + +.kanban-tech-lines { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin: 0 20px; +} + +.tech-line { + height: 1px; + flex: 1; + background: linear-gradient(90deg, transparent, rgba(0, 245, 212, 0.3), transparent); +} + +.tech-dot { + width: 4px; + height: 4px; + background: var(--accent-cyan); + border-radius: 50%; + box-shadow: 0 0 8px var(--accent-cyan); } .kanban-close { - margin-left: 12px; - width: 28px; - height: 28px; + width: 32px; + height: 32px; border-radius: 8px; - border: 1px solid rgba(148, 163, 184, 0.16); - background: rgba(8, 14, 26, 0.86); + border: 1px solid rgba(148, 163, 184, 0.2); + background: rgba(8, 14, 26, 0.8); color: var(--text-dim); - font-size: 18px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all var(--transition-fast); + position: relative; + overflow: hidden; } .kanban-close:hover { - border-color: rgba(34, 211, 238, 0.4); - color: var(--accent-cyan); - background: rgba(8, 20, 36, 0.96); + border-color: rgba(255, 71, 87, 0.5); + color: #ff4757; + background: rgba(255, 71, 87, 0.1); + box-shadow: 0 0 15px rgba(255, 71, 87, 0.2); } +.close-line { + position: absolute; + width: 14px; + height: 1.5px; + background: currentColor; + transition: all var(--transition-fast); +} + +.line-1 { transform: rotate(45deg); } +.line-2 { transform: rotate(-45deg); } + +.kanban-close:hover .line-1 { transform: rotate(135deg); } +.kanban-close:hover .line-2 { transform: rotate(45deg); } + +/* ── Quadrants ── */ .kanban-quadrants { flex: 1; display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; - gap: 10px; + gap: 12px; padding: 12px; min-height: 0; overflow: hidden; + position: relative; + z-index: 1; } .kanban-quadrant { - background: rgba(0, 20, 40, 0.4); - border: 1px solid rgba(255, 255, 255, 0.06); + background: + linear-gradient(180deg, rgba(0, 20, 40, 0.6) 0%, rgba(0, 10, 20, 0.8) 100%); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; display: flex; flex-direction: column; overflow: hidden; + position: relative; + transition: all 0.3s ease; +} + +.kanban-quadrant:hover { + border-color: var(--quadrant-color); + box-shadow: + 0 0 20px var(--quadrant-glow), + inset 0 0 30px rgba(0, 0, 0, 0.3); + transform: scale(1.01); +} + +.kanban-quadrant:hover .quadrant-glow { + opacity: 1; +} + +.quadrant-glow { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 60%; + background: radial-gradient(ellipse at top, var(--quadrant-glow) 0%, transparent 70%); + opacity: 0.3; + transition: opacity 0.3s ease; + pointer-events: none; +} + +.quadrant-scan { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--quadrant-color), transparent); + animation: quadrant-scan 3s linear infinite; + opacity: 0.6; +} + +@keyframes quadrant-scan { + 0% { top: 0; opacity: 0.6; } + 50% { opacity: 0.3; } + 100% { top: 100%; opacity: 0; } } .quadrant-header { - padding: 8px 10px; - color: #fff; - font-weight: 600; - font-size: 11px; + padding: 12px 14px; display: flex; justify-content: space-between; align-items: center; - background: var(--quadrant-color); - flex-shrink: 0; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, transparent 100%); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + position: relative; + z-index: 1; +} + +.quadrant-header-left { + display: flex; + align-items: center; + gap: 10px; +} + +.quadrant-icon { + font-size: 18px; + color: var(--quadrant-color); + text-shadow: 0 0 10px var(--quadrant-glow); +} + +.quadrant-title-group { + display: flex; + flex-direction: column; + gap: 1px; } .quadrant-title { - letter-spacing: 0.05em; + font-family: var(--font-display); + font-size: 12px; + font-weight: 600; + color: #fff; + letter-spacing: 0.08em; } -.quadrant-check { +.quadrant-subtitle { + font-family: var(--font-mono); + font-size: 8px; + color: var(--quadrant-color); + letter-spacing: 0.15em; opacity: 0.8; - background: none; - border: none; - color: inherit; - font-size: inherit; +} + +.quadrant-actions { + display: flex; + align-items: center; + gap: 10px; +} + +.quadrant-add { + width: 22px; + height: 22px; + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.15); + background: rgba(0, 0, 0, 0.3); + color: var(--text-dim); + display: flex; + align-items: center; + justify-content: center; cursor: pointer; + transition: all var(--transition-fast); padding: 0; +} + +.quadrant-add:hover { + border-color: var(--quadrant-color); + color: var(--quadrant-color); + background: rgba(255, 255, 255, 0.05); + box-shadow: 0 0 10px var(--quadrant-glow); +} + +.quadrant-add .add-icon { + font-size: 14px; + font-weight: 600; line-height: 1; } -.quadrant-check:hover { - opacity: 1; +.quadrant-stats { + display: flex; + align-items: baseline; + gap: 2px; + font-family: var(--font-mono); +} + +.stat-value { + font-size: 18px; + font-weight: 700; + color: var(--quadrant-color); + text-shadow: 0 0 10px var(--quadrant-glow); +} + +.stat-separator { + font-size: 12px; + color: var(--text-dim); + margin: 0 2px; +} + +.stat-total { + font-size: 11px; + color: var(--text-dim); } .quadrant-content { flex: 1; - padding: 6px 8px; + padding: 8px 10px; overflow-y: auto; min-height: 0; + position: relative; + z-index: 1; } +/* ── Tasks ── */ .task-item { display: flex; align-items: center; - gap: 6px; - padding: 6px 4px; + gap: 10px; + padding: 8px 6px; font-size: 11px; - color: #e0f7ff; - border-bottom: 1px solid rgba(255, 255, 255, 0.04); + color: var(--text-secondary); + border-bottom: 1px solid rgba(255, 255, 255, 0.03); cursor: pointer; - transition: background-color 0.2s; + transition: all 0.2s ease; + position: relative; } .task-item:hover { - background-color: rgba(0, 243, 255, 0.08); + background: rgba(0, 245, 212, 0.05); + padding-left: 10px; } -.task-item input[type="checkbox"] { - width: 12px; - height: 12px; - cursor: pointer; - flex-shrink: 0; +.task-item:hover .task-indicator { + opacity: 1; + transform: scaleY(1); } .task-item.completed { + color: var(--text-dim); +} + +.task-item.completed .task-title { text-decoration: line-through; - color: rgba(224, 247, 255, 0.5); - background-color: rgba(0, 243, 255, 0.06); + opacity: 0.5; +} + +.task-checkbox { + flex-shrink: 0; +} + +.checkbox-box { + width: 16px; + height: 16px; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + background: rgba(0, 0, 0, 0.3); +} + +.checkbox-box.checked { + background: var(--quadrant-color); + border-color: var(--quadrant-color); + box-shadow: 0 0 8px var(--quadrant-glow); +} + +.check-mark { + font-size: 10px; + color: #000; + font-weight: bold; } .task-title { + flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + transition: all 0.2s ease; } +.task-indicator { + width: 3px; + height: 12px; + background: var(--quadrant-color); + border-radius: 2px; + opacity: 0; + transform: scaleY(0); + transition: all 0.2s ease; + box-shadow: 0 0 6px var(--quadrant-glow); +} + +.task-indicator.done { + opacity: 1; + transform: scaleY(1); + background: var(--accent-green); + box-shadow: 0 0 6px rgba(107, 207, 127, 0.4); +} + +/* ── Empty State ── */ .empty-state { display: flex; - justify-content: center; + flex-direction: column; align-items: center; + justify-content: center; height: 100%; - color: rgba(224, 247, 255, 0.4); - font-size: 12px; - text-align: center; - padding: 20px; + gap: 8px; + position: relative; } -/* Scrollbar styling */ -.quadrant-content::-webkit-scrollbar { +.empty-icon { + font-size: 28px; + color: var(--quadrant-color); + opacity: 0.4; +} + +.empty-text { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-dim); + letter-spacing: 0.1em; +} + +.empty-pulse { + position: absolute; + width: 60px; + height: 60px; + border: 1px solid var(--quadrant-color); + border-radius: 50%; + opacity: 0.2; + animation: empty-pulse 2s ease-in-out infinite; +} + +@keyframes empty-pulse { + 0%, 100% { transform: scale(1); opacity: 0.2; } + 50% { transform: scale(1.3); opacity: 0; } +} + +/* ── Add Task Buttons ── */ +.empty-add-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + margin-top: 10px; + border: 1px dashed rgba(255, 255, 255, 0.2); + border-radius: 8px; + background: rgba(0, 0, 0, 0.3); + color: var(--text-dim); + font-family: var(--font-mono); + font-size: 10px; + cursor: pointer; + transition: all 0.2s ease; +} + +.empty-add-btn:hover { + border-color: var(--quadrant-color); + color: var(--quadrant-color); + background: rgba(255, 255, 255, 0.05); + box-shadow: 0 0 15px var(--quadrant-glow); +} + +.inline-add-btn { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 8px 6px; + margin-top: 6px; + border: 1px dashed rgba(255, 255, 255, 0.1); + border-radius: 6px; + background: transparent; + color: var(--text-dim); + font-family: var(--font-mono); + font-size: 10px; + cursor: pointer; + transition: all 0.2s ease; + opacity: 0.6; +} + +.inline-add-btn:hover { + opacity: 1; + border-color: var(--quadrant-color); + color: var(--quadrant-color); + background: rgba(255, 255, 255, 0.03); +} + +.add-icon-small { + font-size: 12px; + font-weight: 600; +} + +/* ── Footer ── */ +.kanban-footer { + display: flex; + align-items: center; + padding: 12px 20px; + border-top: 1px solid rgba(0, 245, 212, 0.1); + background: linear-gradient(180deg, transparent 0%, rgba(0, 245, 212, 0.05) 100%); + position: relative; + z-index: 1; + gap: 16px; +} + +.footer-stat { + display: flex; + flex-direction: column; + gap: 2px; +} + +.footer-label { + font-family: var(--font-mono); + font-size: 8px; + color: var(--text-dim); + letter-spacing: 0.1em; +} + +.footer-value { + font-family: var(--font-display); + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.footer-value.completed { + color: var(--accent-green); + text-shadow: 0 0 10px rgba(107, 207, 127, 0.5); +} + +.footer-value.rate { + color: var(--accent-cyan); + text-shadow: 0 0 10px rgba(0, 245, 212, 0.5); +} + +.footer-divider { + width: 1px; + height: 24px; + background: rgba(0, 245, 212, 0.2); +} + +.footer-time { + margin-left: auto; + display: flex; + align-items: center; + gap: 6px; +} + +.time-dot { width: 6px; + height: 6px; + background: var(--accent-cyan); + border-radius: 50%; + animation: time-blink 1.5s ease-in-out infinite; +} + +@keyframes time-blink { + 0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--accent-cyan); } + 50% { opacity: 0.4; box-shadow: none; } +} + +.time-text { + font-family: var(--font-mono); + font-size: 8px; + color: var(--accent-cyan); + letter-spacing: 0.15em; + opacity: 0.7; +} + +/* Scrollbar */ +.quadrant-content::-webkit-scrollbar { + width: 4px; } .quadrant-content::-webkit-scrollbar-track { @@ -326,11 +829,11 @@ const quadrants = computed(() => [ } .quadrant-content::-webkit-scrollbar-thumb { - background: rgba(0, 243, 255, 0.2); - border-radius: 3px; + background: rgba(0, 245, 212, 0.2); + border-radius: 2px; } .quadrant-content::-webkit-scrollbar-thumb:hover { - background: rgba(0, 243, 255, 0.4); + background: rgba(0, 245, 212, 0.4); } - \ No newline at end of file +