UI polish: hover lift, skeleton loaders, empty states, badge contrast, transitions

Card & row interactions:
- NoteCard, TaskCard, ProjectCard: translateY(-2px) lift on hover + extended transitions
- Kanban task cards (ProjectView), note rows: same lift treatment
- Task rows in flat/grouped list: soft indigo bg tint on hover
- Chat conversation items: left-border accent + bg tint on hover (was border-only)
- HomeView project mini-cards: translateY(-2px) lift on hover

Global feedback:
- Buttons: scale(0.97) on :active press (theme.css, :not(:disabled) scoped)
- TagPill: color → primary + indigo bg tint on hover
- StatusBadge: increased opacity 15% → 22% + stronger text color for readability

Loading & empty states:
- TasksListView: shimmer skeleton loader (6 rows) + rich empty state with CTA
- ProjectListView: shimmer skeleton cards (4) + rich empty state with CTA
- HomeView: section-empty messages for overdue/high-priority/notes sections
- ProjectView kanban columns: dashed-border styled empty state

Transitions & graph:
- WorkspaceView panels: panel-fade opacity transition on show/hide (v-if inner wrap)
- GraphView D3 nodes: grow to r*1.3 + bold label on mouseover (150ms transition)
- Milestone action buttons: base opacity 0 → 0.35 for discoverability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 18:34:52 -04:00
parent 3ef0e9f2c5
commit 474ed1fe05
12 changed files with 195 additions and 64 deletions
+16 -2
View File
@@ -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