diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index b7d068b..7c34a2e 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -128,6 +128,13 @@ a:focus-visible { border-radius: var(--radius-sm); } +button:not(:disabled):active, +.btn:not(:disabled):active, +[role="button"]:not(:disabled):active { + transform: scale(0.97); + transition: transform 0.08s ease; +} + /* Responsive breakpoints: 480px (phone), 768px (tablet), 1024px (desktop) */ @media (max-width: 768px) { .hide-mobile { diff --git a/frontend/src/components/NoteCard.vue b/frontend/src/components/NoteCard.vue index d3461f0..cefdc8c 100644 --- a/frontend/src/components/NoteCard.vue +++ b/frontend/src/components/NoteCard.vue @@ -65,10 +65,11 @@ function goEdit() { color: inherit; background: var(--color-bg-card); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(99, 102, 241, 0.06); - transition: box-shadow 0.2s; + transition: box-shadow 0.2s, transform 0.18s ease; } .note-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10), 0 0 0 1px rgba(99, 102, 241, 0.14); + transform: translateY(-2px); } /* Compact single-row layout */ diff --git a/frontend/src/components/StatusBadge.vue b/frontend/src/components/StatusBadge.vue index fae4892..1167cbd 100644 --- a/frontend/src/components/StatusBadge.vue +++ b/frontend/src/components/StatusBadge.vue @@ -37,16 +37,16 @@ const labels: Record = { letter-spacing: 0.025em; } .status-todo { - background: var(--color-status-todo-bg); - color: var(--color-status-todo); + background: color-mix(in srgb, var(--color-status-todo-bg) 78%, var(--color-status-todo) 22%); + color: color-mix(in srgb, var(--color-status-todo) 85%, #000 15%); } .status-in_progress { - background: var(--color-status-in-progress-bg); - color: var(--color-status-in-progress); + background: color-mix(in srgb, var(--color-status-in-progress-bg) 78%, var(--color-status-in-progress) 22%); + color: color-mix(in srgb, var(--color-status-in-progress) 85%, #000 15%); } .status-done { - background: var(--color-status-done-bg); - color: var(--color-status-done); + background: color-mix(in srgb, var(--color-status-done-bg) 78%, var(--color-status-done) 22%); + color: color-mix(in srgb, var(--color-status-done) 85%, #000 15%); } .clickable { cursor: pointer; diff --git a/frontend/src/components/TagPill.vue b/frontend/src/components/TagPill.vue index f2ecaa1..687ee9b 100644 --- a/frontend/src/components/TagPill.vue +++ b/frontend/src/components/TagPill.vue @@ -36,9 +36,11 @@ defineEmits<{ font-size: 0.8rem; cursor: pointer; white-space: nowrap; + transition: color 0.15s, background 0.15s; } .tag-pill:hover { - filter: brightness(0.95); + color: var(--color-primary); + background: rgba(99, 102, 241, 0.08); } .dismiss { background: none; diff --git a/frontend/src/components/TaskCard.vue b/frontend/src/components/TaskCard.vue index dd6d436..9a97798 100644 --- a/frontend/src/components/TaskCard.vue +++ b/frontend/src/components/TaskCard.vue @@ -102,10 +102,11 @@ function isOverdue(): boolean { color: inherit; background: var(--color-bg-card); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(99, 102, 241, 0.06); - transition: box-shadow 0.2s; + transition: box-shadow 0.2s, transform 0.18s ease; } .task-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10), 0 0 0 1px rgba(99, 102, 241, 0.14); + transform: translateY(-2px); } /* Compact single-row layout */ diff --git a/frontend/src/views/ChatView.vue b/frontend/src/views/ChatView.vue index 011a181..8318baf 100644 --- a/frontend/src/views/ChatView.vue +++ b/frontend/src/views/ChatView.vue @@ -999,13 +999,17 @@ onUnmounted(() => { border-radius: var(--radius-sm); cursor: pointer; margin-bottom: 0.25rem; + border-left: 3px solid transparent; + transition: background 0.15s, border-color 0.15s; } .conv-item:hover { - background: var(--color-bg-card); + background: rgba(99,102,241,0.05); + border-left: 3px solid var(--color-primary); } .conv-item.active { background: color-mix(in srgb, var(--color-primary) 15%, transparent); color: var(--color-primary); + border-left: 3px solid var(--color-primary); } .conv-info { flex: 1; diff --git a/frontend/src/views/GraphView.vue b/frontend/src/views/GraphView.vue index dd05858..b4f2e47 100644 --- a/frontend/src/views/GraphView.vue +++ b/frontend/src/views/GraphView.vue @@ -258,7 +258,7 @@ function initGraph() { openPeek(d); } }) - .on("mouseover", (event: MouseEvent, d: GraphNode) => { + .on("mouseover", function(event: MouseEvent, d: GraphNode) { const rect = containerRef.value!.getBoundingClientRect(); tooltip.value = { visible: true, @@ -266,14 +266,28 @@ function initGraph() { y: event.clientY - rect.top - 8, node: d, }; + select(this as SVGGElement).select("circle") + .transition().duration(150) + .attr("r", (d.radius ?? 8) * 1.3); + select(this as SVGGElement).select("text") + .transition().duration(150) + .style("font-weight", "600") + .style("font-size", "11px"); }) .on("mousemove", (event: MouseEvent) => { const rect = containerRef.value!.getBoundingClientRect(); tooltip.value.x = event.clientX - rect.left + 12; tooltip.value.y = event.clientY - rect.top - 8; }) - .on("mouseout", () => { + .on("mouseout", function(_event: MouseEvent, d: GraphNode) { tooltip.value.visible = false; + select(this as SVGGElement).select("circle") + .transition().duration(150) + .attr("r", d.radius ?? 8); + select(this as SVGGElement).select("text") + .transition().duration(150) + .style("font-weight", null) + .style("font-size", null); }); nodeSel diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 5650e3b..305d434 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -397,6 +397,7 @@ function clearDashboardResponse() { /> +

All clear

Due Today

@@ -439,6 +440,7 @@ function clearDashboardResponse() { />
+

All clear

In Progress

@@ -484,7 +486,7 @@ function clearDashboardResponse() { />
-

No notes yet.

+

No recent notes

+ New Note
@@ -691,10 +693,11 @@ function clearDashboardResponse() { text-decoration: none; color: inherit; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(99, 102, 241, 0.06); - transition: box-shadow 0.15s; + transition: box-shadow 0.18s ease, transform 0.18s ease; } .project-mini-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.10), 0 0 0 1px rgba(99, 102, 241, 0.14); + transform: translateY(-2px); } .project-mini-title { font-size: 0.9rem; @@ -748,6 +751,15 @@ function clearDashboardResponse() { } .muted { color: var(--color-text-muted); } +.section-empty { + text-align: center; + padding: 1rem 0.5rem; + color: var(--color-text-muted); + font-size: 0.82rem; + font-style: italic; + margin: 0; +} + .loading { color: var(--color-text-secondary); } .empty-state { text-align: center; padding: 1.5rem 0; } .empty-state::before { diff --git a/frontend/src/views/ProjectListView.vue b/frontend/src/views/ProjectListView.vue index f1171bb..639cfc0 100644 --- a/frontend/src/views/ProjectListView.vue +++ b/frontend/src/views/ProjectListView.vue @@ -150,13 +150,16 @@ function truncate(text: string | null, max = 120): string { -

Loading...

+
+
+

{{ error }}

-
+
+

No projects yet

-

Create your first project to organize tasks and notes.

- +

Organise your notes and tasks into a project

+
@@ -327,32 +330,28 @@ function truncate(text: string | null, max = 120): string { color: var(--color-danger); } -.empty-state { - text-align: center; - margin-top: 3rem; +.empty-state-rich { text-align: center; padding: 3rem 1rem; color: var(--color-text-muted); } +.empty-icon { font-size: 2.5rem; margin-bottom: 0.75rem; opacity: 0.3; } +.empty-title { font-size: 1rem; font-weight: 600; color: var(--color-text-secondary); margin: 0 0 0.35rem; } +.empty-sub { font-size: 0.85rem; margin: 0 0 1rem; } +.empty-action { display: inline-block; padding: 0.4rem 1rem; border: 1px solid var(--color-primary); border-radius: var(--radius-sm); color: var(--color-primary); background: none; cursor: pointer; font-size: 0.85rem; transition: background 0.15s, color 0.15s; } +.empty-action:hover { background: var(--color-primary); color: #fff; } + +.skeleton-card { + height: 140px; + border-radius: var(--radius-md); + background: linear-gradient(90deg, var(--color-bg-secondary) 25%, var(--color-border) 50%, var(--color-bg-secondary) 75%); + background-size: 200% 100%; + animation: skeleton-shimmer 1.4s ease infinite; } -.empty-title { - font-size: 1.1rem; - font-weight: 600; - margin: 0 0 0.25rem; - color: var(--color-text); +@keyframes skeleton-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } } -.empty-subtitle { - color: var(--color-text-muted); - margin: 0 0 1rem; - font-size: 0.95rem; -} -.btn-cta { - display: inline-block; - padding: 0.45rem 1rem; - background: var(--color-primary); - color: #fff; - border: none; - border-radius: var(--radius-sm); - text-decoration: none; - font-size: 0.9rem; - cursor: pointer; - font-family: inherit; +.skeleton-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; } .projects-grid { @@ -367,7 +366,7 @@ function truncate(text: string | null, max = 120): string { border-radius: var(--radius-md); padding: 1rem 1.1rem; cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.18s ease; display: flex; flex-direction: column; gap: 0.5rem; @@ -375,6 +374,7 @@ function truncate(text: string | null, max = 120): string { .project-card:hover { border-color: var(--color-primary); box-shadow: 0 2px 8px var(--color-shadow); + transform: translateY(-2px); } .card-header { diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index 73d866b..fccea9d 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -1031,10 +1031,11 @@ function formatDate(dateStr?: string | null): string { text-decoration: none; color: var(--color-text); font-size: 0.875rem; - transition: border-color 0.15s; + transition: border-color 0.15s, transform 0.18s ease; } .task-card:hover { border-color: var(--color-primary); + transform: translateY(-2px); } .task-card-done .task-title { text-decoration: line-through; @@ -1069,11 +1070,13 @@ function formatDate(dateStr?: string | null): string { color: var(--color-text-muted); } .col-empty { - font-size: 0.8rem; - color: var(--color-text-muted); text-align: center; - padding: 0.5rem 0; - margin: 0; + padding: 1.5rem 0.5rem; + color: var(--color-text-muted); + font-size: 0.82rem; + border: 1px dashed var(--color-border); + border-radius: var(--radius-sm); + margin-top: 0.5rem; } /* Notes list */ @@ -1093,10 +1096,11 @@ function formatDate(dateStr?: string | null): string { text-decoration: none; color: var(--color-text); font-size: 0.9rem; - transition: border-color 0.15s; + transition: border-color 0.15s, transform 0.15s ease; } .note-row:hover { border-color: var(--color-primary); + transform: translateY(-1px); } .note-title { font-weight: 500; @@ -1193,7 +1197,7 @@ function formatDate(dateStr?: string | null): string { display: flex; gap: 0.15rem; margin-left: 0.25rem; - opacity: 0; + opacity: 0.35; transition: opacity 0.15s; } .milestone-header:hover .ms-actions { diff --git a/frontend/src/views/TasksListView.vue b/frontend/src/views/TasksListView.vue index 041a74b..0f4a1b8 100644 --- a/frontend/src/views/TasksListView.vue +++ b/frontend/src/views/TasksListView.vue @@ -247,7 +247,9 @@ function toggleGroup(key: string) {
-

Loading...

+
+
+
@@ -426,6 +431,23 @@ function toggleGroup(key: string) { padding: 0; } +/* Skeleton loading */ +.task-list-skeleton { + margin-top: 1rem; +} +.skeleton-row { + height: 44px; + border-radius: var(--radius-sm); + background: linear-gradient(90deg, var(--color-bg-secondary) 25%, var(--color-border) 50%, var(--color-bg-secondary) 75%); + background-size: 200% 100%; + animation: skeleton-shimmer 1.4s ease infinite; + margin-bottom: 0.3rem; +} +@keyframes skeleton-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + /* Flat compact list */ .cards-list { display: flex; @@ -433,6 +455,13 @@ function toggleGroup(key: string) { gap: 0.3rem; margin-top: 1rem; } +.cards-list > div { + border-radius: var(--radius-sm, 6px); + transition: background 0.15s; +} +.cards-list > div:hover { + background: rgba(99,102,241,0.05); +} /* Grouped view */ .task-group { @@ -513,6 +542,40 @@ function toggleGroup(key: string) { text-decoration: none; font-size: 0.9rem; } +.empty-state-rich { + text-align: center; + padding: 3rem 1rem; + color: var(--color-text-muted); +} +.empty-icon { + font-size: 2.5rem; + margin-bottom: 0.75rem; + opacity: 0.3; +} +.empty-state-rich .empty-title { + font-size: 1rem; + font-weight: 600; + color: var(--color-text-secondary); + margin: 0 0 0.35rem; +} +.empty-sub { + font-size: 0.85rem; + margin: 0 0 1rem; +} +.empty-action { + display: inline-block; + padding: 0.4rem 1rem; + border: 1px solid var(--color-primary); + border-radius: var(--radius-sm); + color: var(--color-primary); + text-decoration: none; + font-size: 0.85rem; + transition: background 0.15s, color 0.15s; +} +.empty-action:hover { + background: var(--color-primary); + color: #fff; +} .kb-active-item { outline: 2px solid var(--color-primary); diff --git a/frontend/src/views/WorkspaceView.vue b/frontend/src/views/WorkspaceView.vue index 59d90a4..de73437 100644 --- a/frontend/src/views/WorkspaceView.vue +++ b/frontend/src/views/WorkspaceView.vue @@ -261,11 +261,15 @@ onUnmounted(async () => {
- + +
+ +
+
@@ -376,11 +380,15 @@ onUnmounted(async () => {
- + +
+ +
+
@@ -461,6 +469,21 @@ onUnmounted(async () => { min-width: 0; } +.panel-inner { + height: 100%; + display: flex; + flex-direction: column; +} + +.panel-fade-enter-active, +.panel-fade-leave-active { + transition: opacity 0.18s ease; +} +.panel-fade-enter-from, +.panel-fade-leave-to { + opacity: 0; +} + /* Chat panel */ .ws-chat-panel { display: flex;