diff --git a/.memory/worklog.json b/.memory/worklog.json index e0b63ba..6bc3dc9 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -188,6 +188,13 @@ "message": "auto-save 2026-05-12 18:35 (~3)", "hash": "64db093", "files_changed": 3 + }, + { + "ts": "2026-05-12T18:41:07+08:00", + "type": "commit", + "message": "auto-save 2026-05-12 18:40 (~2)", + "hash": "864781d", + "files_changed": 2 } ] } diff --git a/web/app/globals.css b/web/app/globals.css index 23f5cce..dbba155 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -214,6 +214,73 @@ .glass-node__body { padding: 0.85rem 1rem 1rem; } +/* ============================================================ + Kanban 卡片(Storyflow/参考图风格) + ============================================================ */ +.kanban-card { + position: relative; + border-radius: 14px; + padding: 12px 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; +} +.kanban-card:hover { + transform: translateY(-1px); + border-color: rgba(255, 255, 255, 0.16); + box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.5); +} +/* dark 模式下:低明度彩色 */ +.dark .kanban-violet { background: linear-gradient(160deg, rgba(167, 139, 250, 0.18), rgba(139, 92, 246, 0.08)); } +.dark .kanban-pink { background: linear-gradient(160deg, rgba(244, 114, 182, 0.18), rgba(236, 72, 153, 0.08)); } +.dark .kanban-orange { background: linear-gradient(160deg, rgba(251, 146, 60, 0.18), rgba(249, 115, 22, 0.08)); } +.dark .kanban-blue { background: linear-gradient(160deg, rgba(96, 165, 250, 0.18), rgba(59, 130, 246, 0.08)); } +.dark .kanban-green { background: linear-gradient(160deg, rgba(74, 222, 128, 0.18), rgba(34, 197, 94, 0.08)); } +.dark .kanban-cyan { background: linear-gradient(160deg, rgba(34, 211, 238, 0.18), rgba(6, 182, 212, 0.08)); } +.dark .kanban-rose { background: linear-gradient(160deg, rgba(251, 113, 133, 0.18), rgba(244, 63, 94, 0.08)); } +.dark .kanban-amber { background: linear-gradient(160deg, rgba(252, 211, 77, 0.18), rgba(245, 158, 11, 0.08)); } + +/* light 模式:pastel 暖底 */ +:root:not(.dark) .kanban-violet { background: #ede9fe; border-color: rgba(139, 92, 246, 0.18); } +:root:not(.dark) .kanban-pink { background: #fce7f3; border-color: rgba(236, 72, 153, 0.18); } +:root:not(.dark) .kanban-orange { background: #ffedd5; border-color: rgba(249, 115, 22, 0.18); } +:root:not(.dark) .kanban-blue { background: #dbeafe; border-color: rgba(59, 130, 246, 0.18); } +:root:not(.dark) .kanban-green { background: #dcfce7; border-color: rgba(34, 197, 94, 0.18); } +:root:not(.dark) .kanban-cyan { background: #cffafe; border-color: rgba(6, 182, 212, 0.18); } +:root:not(.dark) .kanban-rose { background: #ffe4e6; border-color: rgba(244, 63, 94, 0.18); } +:root:not(.dark) .kanban-amber { background: #fef3c7; border-color: rgba(245, 158, 11, 0.18); } + +.kanban-tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 999px; + font-size: 10.5px; + font-weight: 500; + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.1); + color: var(--text-soft); +} +:root:not(.dark) .kanban-tag { + background: rgba(255, 255, 255, 0.7); + border-color: rgba(0, 0, 0, 0.06); + color: var(--text-soft); +} + +.kanban-meta { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.06); + font-size: 10.5px; + color: var(--text-faint); +} +:root:not(.dark) .kanban-meta { + border-top-color: rgba(0, 0, 0, 0.06); +} + .glass-node__row { display: flex; align-items: center; gap: 0.5rem; font-size: 12px; color: var(--text-soft); diff --git a/web/components/dashboard.tsx b/web/components/dashboard.tsx index 7a7a726..962b7cb 100644 --- a/web/components/dashboard.tsx +++ b/web/components/dashboard.tsx @@ -35,6 +35,41 @@ function MiniCard({ children, className = "", onClick }: { children: ReactNode; ) } +type KanbanTone = "violet" | "pink" | "orange" | "blue" | "green" | "cyan" | "rose" | "amber" + +function KanbanCard({ + tone = "violet", + tags, + title, + className = "", + onClick, + meta, + children, +}: { + tone?: KanbanTone + tags?: string[] + title?: ReactNode + className?: string + onClick?: (e: React.MouseEvent) => void + meta?: ReactNode + children?: ReactNode +}) { + return ( +