From 1de4169303bb617654ca6615b436d7f94705d8fb Mon Sep 17 00:00:00 2001 From: Iris Date: Fri, 26 Jun 2026 15:37:16 +0000 Subject: [PATCH] Match taskboard to Nexus dark board --- public/app.js | 52 +++--- public/index.html | 4 +- public/styles.css | 397 ++++++++++++++++++++++++++++------------------ server.js | 2 +- 4 files changed, 275 insertions(+), 180 deletions(-) diff --git a/public/app.js b/public/app.js index d58ea0b..c810c51 100644 --- a/public/app.js +++ b/public/app.js @@ -130,10 +130,13 @@ function boardView() {
OpenClaw ${connected ? "verbunden" : "pruefen"} -
-
+
+ + +
+
${states.map((name) => column(name)).join("")}
`; @@ -142,8 +145,8 @@ function boardView() { function column(name) { const tasks = state.tasks.filter((task) => task.state === name); return ` -
-

${name}${tasks.length}

+
+
${name}${tasks.length}
${tasks.map(taskCard).join("") || `

Keine Aufgaben

`}
@@ -152,24 +155,22 @@ function column(name) { } function taskCard(task) { - const index = states.indexOf(task.state); return ` -
+
- ${escapeHtml(task.priority)} - ${escapeHtml(task.assignee || "luna")} + ${escapeHtml(task.priority)} +

${escapeHtml(task.title)}

${task.detail ? `

${escapeHtml(task.detail)}

` : ""}
+
`; @@ -232,18 +233,31 @@ function bindBoard() { dialogMeta.textContent = `Status ${task.state} · Aktualisiert ${formatDate(task.updatedAt)} · Erstellt ${formatDate(task.createdAt)}`; dialog.showModal(); }; - document.querySelector("#new-task").addEventListener("click", () => { - resetDialog(); - dialog.showModal(); + document.querySelector("#quick-create").addEventListener("submit", async (event) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const title = String(formData.get("title") || "").trim(); + if (!title) return; + await api("/api/tasks", { + method: "POST", + body: JSON.stringify({ + title, + state: "Backlog", + priority: "Normal", + assignee: state.settings?.defaultAssignee || "luna" + }) + }); + await loadTasks(); + renderApp(); }); document.querySelector("#cancel-task").addEventListener("click", () => dialog.close()); dialog.addEventListener("click", (event) => { if (event.target === dialog) dialog.close(); }); - document.querySelectorAll("[data-move]").forEach((button) => { - button.addEventListener("click", async (event) => { + document.querySelectorAll("[data-status-task]").forEach((select) => { + select.addEventListener("change", async (event) => { event.stopPropagation(); - await api(`/api/tasks/${button.dataset.move}`, { method: "PATCH", body: JSON.stringify({ state: button.dataset.state }) }); + await api(`/api/tasks/${select.dataset.statusTask}`, { method: "PATCH", body: JSON.stringify({ state: select.value }) }); await loadTasks(); renderApp(); }); diff --git a/public/index.html b/public/index.html index 8659a36..0b5b62c 100644 --- a/public/index.html +++ b/public/index.html @@ -4,10 +4,10 @@ Maxi Mission Control - +
- + diff --git a/public/styles.css b/public/styles.css index b7f5515..eebbbee 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,18 +1,20 @@ :root { - color-scheme: light; - --bg: #f3f5f8; - --panel: #ffffff; - --panel-soft: #eef2f7; - --text: #18212f; - --muted: #667085; - --line: #d9e0ea; - --accent: #3268d8; - --accent-strong: #224fa8; - --accent-soft: #e8efff; - --warn: #b7791f; - --danger: #b42335; - --ok: #14845f; - --shadow: 0 12px 30px rgba(24, 33, 47, 0.09); + color-scheme: dark; + --bg: #080a0f; + --panel: #10131a; + --panel-soft: #0d1016; + --card: #12151c; + --text: #e8eaf0; + --muted: #7e8799; + --line: #202530; + --line-strong: #292f3b; + --accent: #8b7cf6; + --accent-strong: #b8adff; + --accent-soft: rgba(139, 124, 246, 0.12); + --warn: #e5b05e; + --danger: #ec7b82; + --ok: #51d49a; + --shadow: 0 28px 90px rgba(0, 0, 0, 0.42); } * { @@ -21,10 +23,11 @@ body { margin: 0; + min-width: 320px; min-height: 100vh; - background: var(--bg); + background: radial-gradient(circle at 70% -20%, #17152b 0, transparent 34%), var(--bg); color: var(--text); - font: 15px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font: 12px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; letter-spacing: 0; } @@ -33,7 +36,7 @@ button, input, textarea, select { } button { - border: 0; + color: inherit; cursor: pointer; } @@ -46,155 +49,176 @@ button { .login { width: min(420px, 100%); - background: var(--panel); + padding: 32px; border: 1px solid var(--line); - border-radius: 8px; + border-radius: 12px; + background: linear-gradient(145deg, rgba(18, 21, 29, 0.98), rgba(10, 12, 18, 0.98)); box-shadow: var(--shadow); - padding: 28px; } .login h1, .view-title h1 { margin: 0; - font-size: 28px; + font-size: 26px; line-height: 1.1; } .login p, .view-title p { margin: 8px 0 0; color: var(--muted); + font-size: 11px; } .field { display: grid; - gap: 6px; + gap: 7px; margin-top: 16px; } label { - color: var(--muted); - font-size: 13px; + color: #aab1bf; + font-size: 10px; font-weight: 650; } input, textarea, select { width: 100%; - min-height: 40px; - border: 1px solid var(--line); - border-radius: 6px; - background: #fff; - color: var(--text); + min-height: 38px; padding: 9px 10px; + border: 1px solid var(--line-strong); + border-radius: 7px; outline: none; + color: #e7e9ef; + background: #0c0f14; } textarea { resize: vertical; - min-height: 84px; + min-height: 92px; } input:focus, textarea:focus, select:focus { - border-color: var(--accent); - box-shadow: 0 0 0 3px rgba(37, 111, 91, 0.14); + border-color: #6559b8; + box-shadow: 0 0 0 3px rgba(139, 124, 246, 0.1); } .primary, .ghost, .danger { min-height: 38px; - border-radius: 6px; padding: 8px 12px; + border-radius: 7px; font-weight: 700; } .primary { - background: var(--accent); - color: #fff; + border: 1px solid #443d7c; + background: #211d39; + color: #c1b8ff; } .primary:hover { - background: var(--accent-strong); + background: #29234a; } .ghost { border: 1px solid var(--line); - background: #fff; - color: var(--text); + background: var(--panel); + color: #a5adba; } .danger { - background: #fff1f1; - color: var(--danger); - border: 1px solid #edc9c9; + border: 1px solid rgba(236, 123, 130, 0.3); + background: rgba(193, 70, 79, 0.12); + color: #efa2a7; } .app-shell { min-height: 100vh; display: grid; - grid-template-columns: 248px minmax(0, 1fr); + grid-template-columns: 224px minmax(0, 1fr); } .sidebar { - border-right: 1px solid var(--line); - background: #fff; - padding: 20px 16px; + position: sticky; + top: 0; + height: 100vh; display: flex; flex-direction: column; gap: 20px; + padding: 22px 14px 14px; + border-right: 1px solid #1a1e27; + background: rgba(9, 11, 16, 0.94); + backdrop-filter: blur(18px); } .brand { display: grid; - gap: 4px; + gap: 3px; + padding: 0 8px 5px; } .brand strong { - font-size: 18px; + font-size: 13px; + letter-spacing: 0.14em; + text-transform: uppercase; } .brand span, .user small, .meta { color: var(--muted); - font-size: 13px; + font-size: 10px; } .nav { - display: grid; - gap: 8px; + display: flex; + flex-direction: column; + gap: 3px; } .nav button { display: flex; align-items: center; - gap: 9px; + gap: 10px; width: 100%; - min-height: 40px; - border-radius: 6px; + min-height: 34px; + padding: 9px 10px; + border: 0; + border-radius: 7px; background: transparent; - color: var(--text); - padding: 8px 10px; + color: #8991a1; text-align: left; } +.nav button:hover, .nav button.active { + background: var(--accent-soft); + color: #ececf5; +} + .nav button.active { - background: var(--panel-soft); - color: var(--accent-strong); - font-weight: 750; + box-shadow: inset 2px 0 var(--accent); } .user { margin-top: auto; display: grid; gap: 12px; + padding-top: 10px; + border-top: 1px solid #1b1f28; } .content { min-width: 0; - padding: 24px; + padding: 16px; } .topbar { + min-height: 62px; display: flex; gap: 16px; justify-content: space-between; - align-items: flex-start; - margin-bottom: 20px; + align-items: center; + margin: -16px -16px 16px; + padding: 0 30px; + border-bottom: 1px solid #191d25; + background: rgba(8, 10, 15, 0.68); + backdrop-filter: blur(16px); } .actions { @@ -207,104 +231,118 @@ input:focus, textarea:focus, select:focus { .status-pill { display: inline-flex; align-items: center; - gap: 8px; - min-height: 32px; + gap: 7px; + min-height: 30px; padding: 6px 10px; border: 1px solid var(--line); - border-radius: 999px; - background: #fff; - color: var(--muted); + border-radius: 8px; + background: var(--panel); + color: #8c95a5; + font-size: 10px; white-space: nowrap; } .dot { - width: 9px; - height: 9px; + width: 7px; + height: 7px; border-radius: 50%; background: var(--warn); } .dot.ok { background: var(--ok); + box-shadow: 0 0 7px rgba(81, 212, 154, 0.4); } -.board { +.quick-create { display: grid; - grid-template-columns: repeat(4, minmax(220px, 1fr)); + grid-template-columns: minmax(180px, 360px) max-content; + gap: 8px; + align-items: stretch; + margin-bottom: 12px; +} + +.quick-create input { + font-size: 10px; +} + +.quick-create button { + min-width: 100px; + padding: 0 14px; + border: 1px solid #443d7c; + border-radius: 8px; + background: #211d39; + color: #c1b8ff; + font-size: 9px; +} + +.kanban { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; align-items: start; } -.column { +.kanban-column { min-width: 0; - background: #edf1f6; + min-height: 380px; + padding: 12px; border: 1px solid var(--line); - border-radius: 8px; - padding: 10px; - min-height: 520px; + border-radius: 9px; + background: rgba(14, 17, 23, 0.8); } -.column h2 { +.kanban-column > header { display: flex; justify-content: space-between; - align-items: center; - margin: 0 0 10px; - font-size: 14px; - text-transform: uppercase; - color: var(--muted); + padding: 5px 3px 14px; + color: #aeb4c0; + font-size: 10px; } -.count { - display: inline-grid; - place-items: center; - min-width: 24px; - height: 24px; - border-radius: 999px; - background: #fff; - color: var(--text); +.kanban-column > header b { + padding: 1px 6px; + border-radius: 8px; + background: #242936; } .dropzone { - min-height: 456px; + min-height: 320px; border-radius: 8px; transition: background 0.16s ease, box-shadow 0.16s ease; } .dropzone.drag-over { - background: rgba(50, 104, 216, 0.08); - box-shadow: inset 0 0 0 2px rgba(50, 104, 216, 0.28); + background: rgba(139, 124, 246, 0.08); + box-shadow: inset 0 0 0 2px rgba(139, 124, 246, 0.25); } .empty-state { margin: 0; - padding: 32px 6px; + padding: 35px 0; text-align: center; - color: var(--muted); - font-size: 13px; + color: #596171; + font-size: 9px; } -.task { - display: grid; - gap: 10px; - background: #fff; - border: 1px solid var(--line); +.task-card { + margin-bottom: 9px; + padding: 14px; + border: 1px solid #252a35; border-radius: 8px; - padding: 12px; - margin-bottom: 10px; - box-shadow: 0 4px 14px rgba(23, 32, 28, 0.04); + background: var(--card); cursor: grab; - transition: border-color 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease; + transition: border-color 0.14s ease, transform 0.14s ease; } -.task:hover, -.task:focus-visible { - border-color: rgba(50, 104, 216, 0.45); - box-shadow: 0 12px 24px rgba(24, 33, 47, 0.1); +.task-card:hover, +.task-card:focus-visible { + border-color: #343947; outline: none; transform: translateY(-1px); } -.task.dragging { +.task-card.dragging { opacity: 0.55; cursor: grabbing; } @@ -316,68 +354,99 @@ input:focus, textarea:focus, select:focus { justify-content: space-between; } -.task h3 { - margin: 0; - font-size: 15px; +.task-card h3 { + margin: 10px 0 12px; + color: #eef0f5; + font-size: 11px; + line-height: 1.4; + overflow-wrap: anywhere; } -.task p { - margin: 0; +.task-card p { + margin: -4px 0 12px; color: var(--muted); - font-size: 13px; + font-size: 10px; + line-height: 1.45; overflow-wrap: anywhere; } +.task-card select { + width: 100%; + min-height: 32px; + margin-bottom: 12px; + padding: 7px 8px; + border: 1px solid #292f3b; + border-radius: 7px; + color: #c8ccd5; + background: #0c0f14; + font-size: 9px; + cursor: pointer; +} + .task-footer { display: flex; - gap: 6px; + gap: 5px; align-items: center; justify-content: space-between; + color: var(--muted); + font-size: 8px; +} + +.priority { + color: #9b91e6; + font-size: 8px; + text-transform: uppercase; +} + +.priority.urgent, .priority.high { + color: var(--warn); +} + +.priority.low { + color: var(--ok); +} + +.task-edit-btn { + width: 24px; + height: 24px; + display: grid; + place-items: center; + border: 1px solid transparent; + border-radius: 6px; + background: transparent; + color: #687181; + opacity: 0; +} + +.task-card:hover .task-edit-btn, +.task-card:focus-within .task-edit-btn { + opacity: 1; +} + +.task-edit-btn:hover { + border-color: #343947; + color: #c1b8ff; + background: #18152a; } .badge { display: inline-flex; align-items: center; - min-height: 24px; + min-height: 22px; padding: 3px 8px; - border-radius: 999px; + border-radius: 10px; background: var(--accent-soft); color: var(--accent-strong); - font-size: 12px; + font-size: 9px; font-weight: 750; } -.priority-urgent .badge, -.priority-high .badge { - background: #fff3d8; - color: #8a4f00; -} - -.priority-low .badge { - background: #e9f8f0; - color: #0d6b4d; -} - -.task-controls { - display: flex; - gap: 4px; -} - -.icon-button { - width: 30px; - height: 30px; - border-radius: 6px; - border: 1px solid var(--line); - background: #fff; - color: var(--text); -} - .panel { - background: #fff; - border: 1px solid var(--line); - border-radius: 8px; - padding: 18px; max-width: 760px; + padding: 18px; + border: 1px solid var(--line); + border-radius: 9px; + background: linear-gradient(145deg, rgba(18, 21, 29, 0.96), rgba(12, 15, 21, 0.96)); } .panel + .panel { @@ -398,14 +467,22 @@ input:focus, textarea:focus, select:focus { dialog { width: min(560px, calc(100vw - 32px)); - border: 1px solid var(--line); - border-radius: 8px; - box-shadow: var(--shadow); padding: 22px; + border: 1px solid var(--line); + border-radius: 10px; + background: linear-gradient(145deg, rgba(18, 21, 29, 0.98), rgba(10, 12, 18, 0.98)); + color: var(--text); + box-shadow: var(--shadow); } dialog::backdrop { - background: rgba(18, 26, 23, 0.35); + background: rgba(0, 0, 0, 0.68); + backdrop-filter: blur(3px); +} + +dialog h2 { + margin: 0; + font-size: 18px; } .dialog-actions { @@ -417,8 +494,9 @@ dialog::backdrop { .dialog-meta { min-height: 20px; + margin-top: 4px; color: var(--muted); - font-size: 12px; + font-size: 10px; } @media (max-width: 980px) { @@ -428,18 +506,19 @@ dialog::backdrop { .sidebar { position: sticky; - top: 0; z-index: 2; + height: auto; border-right: 0; border-bottom: 1px solid var(--line); padding: 12px; } .nav, .user { - grid-template-columns: repeat(3, minmax(0, 1fr)); + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); } - .board { + .kanban { grid-template-columns: repeat(2, minmax(220px, 1fr)); } } @@ -451,17 +530,19 @@ dialog::backdrop { .topbar { display: grid; + margin: -16px -16px 16px; + padding: 16px; } .actions { justify-content: flex-start; } - .board, .grid { + .quick-create, .kanban, .grid { grid-template-columns: 1fr; } - .column { + .kanban-column { min-height: 260px; } } diff --git a/server.js b/server.js index d295c6b..50fd9e2 100644 --- a/server.js +++ b/server.js @@ -317,7 +317,7 @@ async function serveStatic(req, res) { const file = await readFile(filePath); const ext = path.extname(filePath); const type = ext === ".html" ? "text/html; charset=utf-8" : ext === ".css" ? "text/css; charset=utf-8" : ext === ".js" ? "text/javascript; charset=utf-8" : "application/octet-stream"; - send(res, 200, file, { "content-type": type, "cache-control": ext === ".html" ? "no-store" : "public, max-age=3600" }); + send(res, 200, file, { "content-type": type, "cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate" }); } catch { const file = await readFile(path.join(publicDir, "index.html")); send(res, 200, file, { "content-type": "text/html; charset=utf-8" });