diff --git a/Projects/Team Collaboration Board/README.md b/Projects/Team Collaboration Board/README.md new file mode 100644 index 00000000..e0caa60f --- /dev/null +++ b/Projects/Team Collaboration Board/README.md @@ -0,0 +1,27 @@ +# Team Collaboration Board + +An interactive team workspace containing project dashboards, Kanban task boards (Backlog, In Progress, In Review, Done), dynamic member registries with initials avatars, task checklists, and global progress trackers. + +## Core Features + +- **Kanban Task Boards**: Organize task items across 4 columns: + * Backlog -> In Progress -> In Review -> Done. + * Interactively shift cards using arrows to instantly update column indicators. +- **Member Directory Registry**: Create and manage a custom database of team members. + * Autogenerates unique avatar color icons from user names. + * Dropdown menus allow quick task assignments. +- **Task Inspector Drawer**: Click any task card to expand details: + * Modify description, due date, priority (Low, Medium, High), and assignees. + * Track checklists sub-tasks (e.g. Code refactoring, QA tests) which update card progress bars in real time. +- **Project Progress Indicators**: Top-level progress gauge calculating the global percentage of completed tasks relative to total items. +- **Filters & Searches**: Real-time filters to restrict boards rendering based on priority, text search matches, or assigned members. + +## Run it + +Open `index.html` in any modern web browser. + +## Technical Details + +- **HTML5 & CSS3**: Glassmorphic dark theme dashboard templates, range slider trackings, grid lanes, and rounded badges. +- **Vanilla JavaScript**: State-driven array rendering, filter queries matching, and sync mechanisms committing JSON models in `localStorage`. +- **Storage**: Persists boards data locally in client `localStorage`. diff --git a/Projects/Team Collaboration Board/index.html b/Projects/Team Collaboration Board/index.html new file mode 100644 index 00000000..b273cd0f --- /dev/null +++ b/Projects/Team Collaboration Board/index.html @@ -0,0 +1,260 @@ + + + + + + Team Collaboration Board + + + + + + + + + +
+ + +
+
+ + +
+
+ Global Project Progress + 0% Complete +
+
+
+
+
+
+
+ + +
+ + + + + +
+
+

Collaboration Pipeline

+ +
+ + +
+ + +
+
+ Backlog + 0 +
+
+ +
+
+ + +
+
+ In Progress + 0 +
+
+ +
+
+ + +
+
+ In Review + 0 +
+
+ +
+
+ + +
+
+ Done + 0 +
+
+ +
+
+ +
+
+ + +
+ + +
+
+ +
+

Task Inspector

+

Select any task card from the boards to view description logs, complete subtasks, modify assignees, or remove cards.

+
+ + + +
+ +
+ + + + + + + diff --git a/Projects/Team Collaboration Board/project.json b/Projects/Team Collaboration Board/project.json new file mode 100644 index 00000000..8aa15d70 --- /dev/null +++ b/Projects/Team Collaboration Board/project.json @@ -0,0 +1,17 @@ +{ + "title": "Team Collaboration Board", + "description": "An interactive team collaboration workspace featuring Kanban task lanes, custom member registries, and subtask milestones checklists.", + "author": { + "name": "Sujal", + "github": "Sujal" + }, + "tags": [ + "productivity", + "collaboration", + "kanban", + "vanilla-js", + "localstorage" + ], + "entry": "index.html", + "thumbnail": "thumbnail.svg" +} diff --git a/Projects/Team Collaboration Board/script.js b/Projects/Team Collaboration Board/script.js new file mode 100644 index 00000000..4871029a --- /dev/null +++ b/Projects/Team Collaboration Board/script.js @@ -0,0 +1,686 @@ +// Team Collaboration Board - Interaction Script + +// Predefined default seed team members +const SEED_MEMBERS = [ + { id: "member-alice", name: "Alice Smith", initials: "AS", color: "#e11d48" }, // Rose + { id: "member-bob", name: "Bob Johnson", initials: "BJ", color: "#2563eb" }, // Blue + { id: "member-charlie", name: "Charlie Brown", initials: "CB", color: "#16a34a" }, // Green + { id: "member-diana", name: "Diana Prince", initials: "DP", color: "#d97706" } // Amber +]; + +// Predefined default seed task cards +const SEED_TASKS = [ + { + id: "task-oauth", + title: "Implement OAuth Login Flow", + description: "Set up login authorization via Google and Github providers using client-side tokens.", + priority: "High", + status: "In Progress", + date: "2026-06-20", + assignee: "member-alice", + subtasks: [ + { id: "sub-oauth-1", text: "Create API developers client keys", completed: true }, + { id: "sub-oauth-2", text: "Integrate login button elements in header", completed: true }, + { id: "sub-oauth-3", text: "Store OAuth access tokens locally in browser", completed: false } + ] + }, + { + id: "task-landing", + title: "Design Landing Page UI", + description: "Design a high-fidelity landing page mockup highlighting the value proposition and key widgets.", + priority: "Medium", + status: "Done", + date: "2026-06-15", + assignee: "member-bob", + subtasks: [ + { id: "sub-land-1", text: "Review initial wireframes draft", completed: true }, + { id: "sub-land-2", text: "Export Figma design assets", completed: true } + ] + }, + { + id: "task-db", + title: "Optimize DB Query Indices", + description: "Analyze latency metrics and add key indexes to tables to resolve performance issues.", + priority: "High", + status: "In Review", + date: "2026-06-18", + assignee: "member-charlie", + subtasks: [ + { id: "sub-db-1", text: "Inspect slow query index logs", completed: true }, + { id: "sub-db-2", text: "Write database index migrations", completed: true }, + { id: "sub-db-3", text: "Perform local stress latency checks", completed: false } + ] + }, + { + id: "task-docs", + title: "Write API Integration Docs", + description: "Draft complete documentation detailing routes parameters, payloads, and response codes.", + priority: "Low", + status: "Backlog", + date: "2026-06-25", + assignee: "Unassigned", + subtasks: [ + { id: "sub-doc-1", text: "Outline main request routes structures", completed: false }, + { id: "sub-doc-2", text: "Insert JSON response payload code snippets", completed: false } + ] + } +]; + +// Kanban columns pipeline order +const LANES = ["Backlog", "In Progress", "In Review", "Done"]; + +// Avatar colors selection list for newly registered members +const RANDOM_COLORS = ["#6366f1", "#0ea5e9", "#10b981", "#f59e0b", "#ec4899", "#8b5cf6", "#14b8a6", "#ef4444"]; + +// State Variables +let tasks = []; +let members = []; +let activeTaskId = null; + +// DOM Elements +const txtSearch = document.getElementById("txt-search"); +const selPriorityFilter = document.getElementById("sel-priority-filter"); +const selAssigneeFilter = document.getElementById("sel-assignee-filter"); + +const txtMemberName = document.getElementById("txt-member-name"); +const btnAddMember = document.getElementById("btn-add-member"); +const membersList = document.getElementById("members-list"); + +const btnResetWorkspace = document.getElementById("btn-reset-workspace"); +const btnAddTask = document.getElementById("btn-add-task"); + +const colBacklog = document.getElementById("col-backlog"); +const colProgress = document.getElementById("col-progress"); +const colReview = document.getElementById("col-review"); +const colDone = document.getElementById("col-done"); + +const lblGlobalProgressPct = document.getElementById("lbl-global-progress-pct"); +const globalProgressFill = document.getElementById("global-progress-fill"); + +const inspectorEmpty = document.getElementById("inspector-empty"); +const inspectorActive = document.getElementById("inspector-active"); + +// Form controls details inspector +const taskTitle = document.getElementById("task-title"); +const taskDescription = document.getElementById("task-description"); +const selTaskPriority = document.getElementById("sel-task-priority"); +const taskDate = document.getElementById("task-date"); +const selTaskAssignee = document.getElementById("sel-task-assignee"); +const selTaskStatus = document.getElementById("sel-task-status"); + +const subtasksList = document.getElementById("subtasks-list"); +const txtNewSubtask = document.getElementById("txt-new-subtask"); +const btnAddSubtask = document.getElementById("btn-add-subtask"); + +const btnDeleteTask = document.getElementById("btn-delete-task"); +const btnSaveTask = document.getElementById("btn-save-task"); + +// INITIALIZATION +window.addEventListener("DOMContentLoaded", () => { + loadData(); + setupEventListeners(); + syncWorkspace(); +}); + +// Load from localStorage or defaults +function loadData() { + const storedMembers = localStorage.getItem("collabboard_members"); + const storedTasks = localStorage.getItem("collabboard_tasks"); + + if (storedMembers) { + try { members = JSON.parse(storedMembers); } catch (e) { members = [...SEED_MEMBERS]; } + } else { + members = [...SEED_MEMBERS]; + localStorage.setItem("collabboard_members", JSON.stringify(members)); + } + + if (storedTasks) { + try { tasks = JSON.parse(storedTasks); } catch (e) { tasks = [...SEED_TASKS]; } + } else { + tasks = [...SEED_TASKS]; + localStorage.setItem("collabboard_tasks", JSON.stringify(tasks)); + } +} + +function saveToStorage() { + localStorage.setItem("collabboard_members", JSON.stringify(members)); + localStorage.setItem("collabboard_tasks", JSON.stringify(tasks)); + syncWorkspace(); +} + +// Global UI State Sync +function syncWorkspace() { + calculateProgress(); + populateDropdownFilters(); + renderMembers(); + renderBoards(); + + if (activeTaskId) { + const task = tasks.find(t => t.id === activeTaskId); + if (task) { + renderSubtasks(task); + } + } +} + +// EVENT LISTENERS +function setupEventListeners() { + // Filters + txtSearch.addEventListener("input", renderBoards); + selPriorityFilter.addEventListener("change", renderBoards); + selAssigneeFilter.addEventListener("change", renderBoards); + + // Buttons actions + btnAddMember.addEventListener("click", registerNewMember); + txtMemberName.addEventListener("keypress", (e) => { + if (e.key === "Enter") registerNewMember(); + }); + + btnAddTask.addEventListener("click", createNewTask); + btnResetWorkspace.addEventListener("click", resetWorkspace); + + // Inspector actions + btnSaveTask.addEventListener("click", saveTaskChanges); + btnDeleteTask.addEventListener("click", deleteTaskCard); + btnAddSubtask.addEventListener("click", handleAddSubtaskSubmit); + txtNewSubtask.addEventListener("keypress", (e) => { + if (e.key === "Enter") handleAddSubtaskSubmit(); + }); + + // Track status lane modifications dynamically + selTaskStatus.addEventListener("change", () => { + if (activeTaskId) { + const task = tasks.find(t => t.id === activeTaskId); + if (task) { + task.status = selTaskStatus.value; + saveToStorage(); + } + } + }); +} + +// CALCULATE PROJECT COMPLETION STATS +function calculateProgress() { + const total = tasks.length; + const done = tasks.filter(t => t.status === "Done").length; + const pct = total > 0 ? Math.round((done / total) * 100) : 0; + + lblGlobalProgressPct.textContent = `${pct}% Complete (${done}/${total})`; + globalProgressFill.style.width = `${pct}%`; + + // Lane count badges updates + document.getElementById("count-backlog").textContent = tasks.filter(t => t.status === "Backlog").length; + document.getElementById("count-progress").textContent = tasks.filter(t => t.status === "In Progress").length; + document.getElementById("count-review").textContent = tasks.filter(t => t.status === "In Review").length; + document.getElementById("count-done").textContent = done; +} + +// POPULATE ASSIGNEE DROPDOWNS +function populateDropdownFilters() { + // Save current values to restore them + const prevFilterVal = selAssigneeFilter.value; + const prevTaskVal = selTaskAssignee.value; + + selAssigneeFilter.innerHTML = ``; + selTaskAssignee.innerHTML = ``; + + members.forEach(m => { + const optFilter = document.createElement("option"); + optFilter.value = m.id; + optFilter.textContent = m.name; + selAssigneeFilter.appendChild(optFilter); + + const optTask = document.createElement("option"); + optTask.value = m.id; + optTask.textContent = m.name; + selTaskAssignee.appendChild(optTask); + }); + + // Restore previous selections if valid + if ([...selAssigneeFilter.options].some(o => o.value === prevFilterVal)) { + selAssigneeFilter.value = prevFilterVal; + } + if ([...selTaskAssignee.options].some(o => o.value === prevTaskVal)) { + selTaskAssignee.value = prevTaskVal; + } +} + +// RENDER MEMBERS DIRECTORY (LEFT COLUMN) +function renderMembers() { + membersList.innerHTML = ""; + if (members.length === 0) { + membersList.innerHTML = `
  • No registered members.
  • `; + return; + } + + members.forEach(m => { + const li = document.createElement("li"); + li.className = "member-item"; + li.innerHTML = ` +
    + ${m.initials} + ${escapeHtml(m.name)} +
    + + `; + membersList.appendChild(li); + }); +} + +// RENDER KANBAN PIPELINE BOARD (CENTER) +function renderBoards() { + const query = txtSearch.value.trim().toLowerCase(); + const priorityFilter = selPriorityFilter.value; + const assigneeFilter = selAssigneeFilter.value; + + // Clear lane cards wrappers + colBacklog.innerHTML = ""; + colProgress.innerHTML = ""; + colReview.innerHTML = ""; + colDone.innerHTML = ""; + + const filteredTasks = tasks.filter(task => { + const matchesSearch = + task.title.toLowerCase().includes(query) || + task.description.toLowerCase().includes(query); + + const matchesPriority = priorityFilter === "All" || task.priority === priorityFilter; + const matchesAssignee = assigneeFilter === "All" || task.assignee === assigneeFilter; + + return matchesSearch && matchesPriority && matchesAssignee; + }); + + filteredTasks.forEach(task => { + const card = createTaskCard(task); + + switch (task.status) { + case "Backlog": + colBacklog.appendChild(card); + break; + case "In Progress": + colProgress.appendChild(card); + break; + case "In Review": + colReview.appendChild(card); + break; + case "Done": + colDone.appendChild(card); + break; + } + }); + + // Fill empty column placeholders + const columns = [ + { col: colBacklog, text: "Backlog is empty" }, + { col: colProgress, text: "No tasks in progress" }, + { col: colReview, text: "No reviews requested" }, + { col: colDone, text: "No completed task cards" } + ]; + + columns.forEach(w => { + if (w.col.children.length === 0) { + const div = document.createElement("div"); + div.className = "empty-lane-placeholder"; + div.style.textAlign = "center"; + div.style.padding = "2rem 0.5rem"; + div.style.color = "var(--text-muted)"; + div.style.fontSize = "0.75rem"; + div.style.border = "1px dashed rgba(255,255,255,0.02)"; + div.style.borderRadius = "var(--radius-sm)"; + div.textContent = w.text; + w.col.appendChild(div); + } + }); +} + +// CREATE TASK CARD COMPONENT +function createTaskCard(task) { + const card = document.createElement("div"); + card.className = "task-card"; + if (task.id === activeTaskId) { + card.classList.add("active-card"); + } + + // Subtask progress calculation + const totalSubtasks = task.subtasks ? task.subtasks.length : 0; + const completedSubtasks = task.subtasks ? task.subtasks.filter(s => s.completed).length : 0; + const pct = totalSubtasks > 0 ? Math.round((completedSubtasks / totalSubtasks) * 100) : 0; + + // Retrieve assignee avatar metadata + let avatarHtml = `?`; + if (task.assignee !== "Unassigned") { + const member = members.find(m => m.id === task.assignee); + if (member) { + avatarHtml = `${member.initials}`; + } + } + + // Priority class + let prioClass = "prio-low"; + if (task.priority === "Medium") prioClass = "prio-medium"; + if (task.priority === "High") prioClass = "prio-high"; + + card.innerHTML = ` +
    + ${task.priority} + ${task.date ? formatDateShort(task.date) : "No date"} +
    +
    ${escapeHtml(task.title)}
    + +
    +
    + Subtasks + ${completedSubtasks}/${totalSubtasks} (${pct}%) +
    +
    +
    +
    +
    + + + `; + + // Click inspect details card + card.addEventListener("click", () => { + selectTaskCard(task.id); + }); + + // Arrow shifts + const btnLeft = card.querySelector(".btn-shift-left"); + const btnRight = card.querySelector(".btn-shift-right"); + + const currentIdx = LANES.indexOf(task.status); + + if (currentIdx === 0) btnLeft.classList.add("disabled"); + if (currentIdx === LANES.length - 1) btnRight.classList.add("disabled"); + + btnLeft.addEventListener("click", (e) => { + e.stopPropagation(); + shiftTaskColumn(task.id, -1); + }); + + btnRight.addEventListener("click", (e) => { + e.stopPropagation(); + shiftTaskColumn(task.id, 1); + }); + + return card; +} + +// Format YYYY-MM-DD to simple text e.g. Jun 20 +function formatDateShort(dateStr) { + if (!dateStr) return ""; + const parts = dateStr.split("-"); + if (parts.length < 3) return dateStr; + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + const mIdx = parseInt(parts[1], 10) - 1; + return `${months[mIdx]} ${parseInt(parts[2], 10)}`; +} + +// SHIFT CARD STATUS LEL +function shiftTaskColumn(taskId, direction) { + const task = tasks.find(t => t.id === taskId); + if (!task) return; + + const currentIdx = LANES.indexOf(task.status); + const newIdx = currentIdx + direction; + + if (newIdx >= 0 && newIdx < LANES.length) { + task.status = LANES[newIdx]; + saveToStorage(); + + // Sync active inspector dropdown status + if (activeTaskId === taskId) { + selTaskStatus.value = task.status; + } + } +} + +// REGISTER NEW TEAM MEMBER +function registerNewMember() { + const name = txtMemberName.value.trim(); + if (!name) { + alert("Please enter a Member Name."); + return; + } + + // Generate initials + const nameParts = name.split(" "); + let initials = nameParts[0].charAt(0); + if (nameParts.length > 1) { + initials += nameParts[nameParts.length - 1].charAt(0); + } + initials = initials.toUpperCase(); + + // Generate random avatar color + const color = RANDOM_COLORS[Math.floor(Math.random() * RANDOM_COLORS.length)]; + + const id = "member-" + Date.now(); + const newMember = { + id: id, + name: name, + initials: initials, + color: color + }; + + members.push(newMember); + txtMemberName.value = ""; + saveToStorage(); +} + +// DELETE REGISTERED MEMBER (RESTORES ASSIGNMENTS TO UNASSIGNED) +window.removeRegisteredMember = function(id) { + const member = members.find(m => m.id === id); + if (!member) return; + + if (confirm(`Are you sure you want to unregister ${member.name}? All tasks assigned to them will reset to Unassigned.`)) { + // Reset tasks assigned + tasks.forEach(t => { + if (t.assignee === id) { + t.assignee = "Unassigned"; + } + }); + + members = members.filter(m => m.id !== id); + saveToStorage(); + } +}; + +// SELECT TASK FOR DETAILS VIEW +function selectTaskCard(taskId) { + activeTaskId = taskId; + const task = tasks.find(t => t.id === taskId); + + if (!task) { + deselectTaskCard(); + return; + } + + // Highlights visual selection borders outline + document.querySelectorAll(".task-card").forEach(c => c.classList.remove("active-card")); + + // Bind values to inputs + taskTitle.value = task.title; + taskDescription.value = task.description; + selTaskPriority.value = task.priority; + taskDate.value = task.date || ""; + selTaskAssignee.value = task.assignee; + selTaskStatus.value = task.status; + + // Render checklist subtasks + renderSubtasks(task); + + // Toggle visible cards + inspectorEmpty.classList.add("hidden"); + inspectorActive.classList.remove("hidden"); + + // Rerender cards list to show highlight outline + renderBoards(); +} + +// DESELECT INSPECTOR ACTIVE +function deselectTaskCard() { + activeTaskId = null; + inspectorActive.classList.add("hidden"); + inspectorEmpty.classList.remove("hidden"); + renderBoards(); +} + +// SAVE ACTIVE TASK VALUES +function saveTaskChanges() { + if (!activeTaskId) return; + + const task = tasks.find(t => t.id === activeTaskId); + if (!task) return; + + // Validate inputs + if (!taskTitle.value.trim()) { + alert("Please enter a Task Title."); + return; + } + + task.title = taskTitle.value.trim(); + task.description = taskDescription.value.trim(); + task.priority = selTaskPriority.value; + task.date = taskDate.value; + task.assignee = selTaskAssignee.value; + task.status = selTaskStatus.value; + + saveToStorage(); +} + +// DELETE ACTIVE TASK CARD +function deleteTaskCard() { + if (!activeTaskId) return; + + const task = tasks.find(t => t.id === activeTaskId); + if (!task) return; + + if (confirm(`Are you sure you want to delete the task card: "${task.title}"?`)) { + tasks = tasks.filter(t => t.id !== activeTaskId); + saveToStorage(); + deselectTaskCard(); + } +} + +// CREATE NEW BLANK TASK +function createNewTask() { + const newTask = { + id: "task-" + Date.now(), + title: "New Task Spec", + description: "", + priority: "Medium", + status: "Backlog", + date: new Date().toISOString().split("T")[0], + assignee: "Unassigned", + subtasks: [ + { id: "sub-" + Date.now() + "-1", text: "Task research details", completed: false }, + { id: "sub-" + Date.now() + "-2", text: "Execution drafts", completed: false } + ] + }; + + tasks.unshift(newTask); + saveToStorage(); + selectTaskCard(newTask.id); +} + +// RENDER CHECKLIST MILSTESTONE SUBTASKS +function renderSubtasks(task) { + subtasksList.innerHTML = ""; + + if (!task.subtasks || task.subtasks.length === 0) { + subtasksList.innerHTML = `
  • No subtasks created yet.
  • `; + return; + } + + task.subtasks.forEach(sub => { + const li = document.createElement("li"); + li.className = `subtask-item ${sub.completed ? "completed" : ""}`; + + li.innerHTML = ` +
    + + ${escapeHtml(sub.text)} +
    + + `; + + // Toggle complete checklist + li.querySelector("input").addEventListener("change", () => { + sub.completed = !sub.completed; + saveToStorage(); + }); + + li.querySelector(".subtask-left").addEventListener("click", (e) => { + if (e.target.tagName !== "INPUT") { + sub.completed = !sub.completed; + saveToStorage(); + } + }); + + // Delete subtask + li.querySelector(".btn-delete-subtask").addEventListener("click", (e) => { + e.stopPropagation(); + task.subtasks = task.subtasks.filter(s => s.id !== sub.id); + saveToStorage(); + }); + + subtasksList.appendChild(li); + }); +} + +// ADD NEW CUSTOM CHECKLIST ITEM +function handleAddSubtaskSubmit() { + if (!activeTaskId) return; + + const task = tasks.find(t => t.id === activeTaskId); + if (!task) return; + + const text = txtNewSubtask.value.trim(); + if (!text) return; + + if (!task.subtasks) { + task.subtasks = []; + } + + const newSub = { + id: "sub-" + Date.now() + "-" + Math.random().toString(36).substr(2, 4), + text: text, + completed: false + }; + + task.subtasks.push(newSub); + txtNewSubtask.value = ""; + saveToStorage(); +} + +// RESET WORKSPACE DATA PARAMETERS +function resetWorkspace() { + if (confirm("Reset Board workspace data? This will restore the default sandbox task cards and members registry.")) { + members = [...SEED_MEMBERS]; + tasks = [...SEED_TASKS]; + + localStorage.setItem("collabboard_members", JSON.stringify(members)); + localStorage.setItem("collabboard_tasks", JSON.stringify(tasks)); + + deselectTaskCard(); + syncWorkspace(); + } +} + +// ESCAPE HTML TO PREVENT XSS +function escapeHtml(str) { + if (!str) return ""; + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/Projects/Team Collaboration Board/style.css b/Projects/Team Collaboration Board/style.css new file mode 100644 index 00000000..e868ed08 --- /dev/null +++ b/Projects/Team Collaboration Board/style.css @@ -0,0 +1,937 @@ +/* Team Collaboration Board - Styling */ + +:root { + --bg-main: #090d16; + --bg-card: #111827; + --bg-card-hover: #1f2937; + --border-color: #374151; + --text-main: #f3f4f6; + --text-muted: #9ca3af; + --text-highlight: #ffffff; + + /* Accent theme color palettes */ + --clr-primary: #0ea5e9; + --clr-primary-glow: rgba(14, 165, 233, 0.15); + + --clr-emerald: #10b981; + --clr-emerald-glow: rgba(16, 185, 129, 0.15); + + --clr-amber: #f59e0b; + --clr-amber-glow: rgba(245, 158, 11, 0.15); + + --clr-rose: #ef4444; + --clr-rose-glow: rgba(239, 68, 68, 0.15); + + --clr-violet: #6366f1; + --clr-violet-glow: rgba(99, 102, 241, 0.15); + + --font-headers: 'Outfit', sans-serif; + --font-body: 'Inter', sans-serif; + + --transition-speed: 0.25s; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --glow-shadow: 0 0 20px rgba(14, 165, 233, 0.25); +} + +/* GENERAL STYLES */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-main); + color: var(--text-main); + font-family: var(--font-body); + min-height: 100vh; + display: flex; + flex-direction: column; + position: relative; + overflow-x: hidden; + line-height: 1.5; +} + +/* GLOW BACKGROUND */ +.glow-bg { + position: absolute; + top: -150px; + left: 50%; + transform: translateX(-50%); + width: 800px; + height: 400px; + background: radial-gradient(circle, rgba(99, 102, 241, 0.1) 0%, rgba(14, 165, 233, 0.05) 50%, rgba(9, 13, 22, 0) 100%); + filter: blur(80px); + z-index: -1; + pointer-events: none; +} + +/* SCROLLBARS */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* HEADER */ +.app-header { + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border-color); + padding: 1rem 2rem; + position: sticky; + top: 0; + z-index: 100; +} + +.header-container { + max-width: 1600px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1.5rem; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo h1 { + font-family: var(--font-headers); + font-size: 1.75rem; + font-weight: 800; + letter-spacing: -0.5px; + background: linear-gradient(135deg, #ffffff 30%, var(--clr-primary) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.logo h1 span { + color: var(--text-muted); + -webkit-text-fill-color: var(--text-muted); + font-weight: 400; +} + +.glow-icon { + font-size: 1.5rem; + color: var(--clr-primary); + text-shadow: 0 0 10px rgba(14, 165, 233, 0.5); + animation: pulse 3s infinite alternate; +} + +.logo .badge { + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 0.6rem; + background: rgba(31, 41, 55, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; +} + +/* Global Progress bar */ +.global-progress-wrapper { + display: flex; + flex-direction: column; + gap: 0.35rem; + min-width: 280px; +} + +.progress-lbl-row { + display: flex; + justify-content: space-between; + font-size: 0.75rem; + color: var(--text-muted); +} + +.progress-lbl-row strong { + color: var(--text-highlight); +} + +.progress-bar-container { + height: 6px; + background: rgba(255, 255, 255, 0.08); + border-radius: 3px; + overflow: hidden; + border: 1px solid rgba(255,255,255,0.02); +} + +.progress-bar-fill { + height: 100%; + width: 0%; + background: linear-gradient(90deg, var(--clr-primary) 0%, var(--clr-emerald) 100%); + border-radius: 3px; + transition: width 0.3s ease; +} + +/* MAIN LAYOUT */ +.main-container { + display: grid; + grid-template-columns: 280px 1fr 340px; + gap: 1.5rem; + max-width: 1600px; + width: 100%; + margin: 0 auto; + padding: 1.5rem 2rem; + flex: 1; +} + +/* SECTION CARDS */ +.section-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 1.25rem; + margin-bottom: 1.25rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: border-color var(--transition-speed); +} + +.section-card:hover { + border-color: rgba(255, 255, 255, 0.08); +} + +.card-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding-bottom: 0.5rem; +} + +.card-header-row h3 { + font-family: var(--font-headers); + font-size: 1.1rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-highlight); +} + +/* FORMS CONTROLS */ +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + font-size: 0.8rem; + font-weight: 500; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.4rem; + letter-spacing: 0.5px; +} + +input[type="text"], +input[type="number"], +input[type="date"], +select, +textarea { + width: 100%; + padding: 0.65rem 0.9rem; + background-color: rgba(9, 13, 22, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-main); + font-family: var(--font-body); + font-size: 0.9rem; + transition: border-color var(--transition-speed), box-shadow var(--transition-speed); +} + +input[type="text"]:focus, +input[type="number"]:focus, +input[type="date"]:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--clr-primary); + box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2); +} + +textarea { + min-height: 90px; + resize: vertical; +} + +.form-row { + display: flex; + gap: 1rem; +} + +.flex-1 { + flex: 1; +} + +/* SEARCH INPUT WRAPPER */ +.search-input-wrapper { + position: relative; +} + +.search-input-wrapper i { + position: absolute; + left: 0.9rem; + top: 50%; + transform: translateY(-50%); + color: var(--text-muted); +} + +.search-input-wrapper input { + padding-left: 2.2rem; +} + +/* BUTTONS */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.65rem 1.25rem; + border-radius: var(--radius-sm); + font-family: var(--font-body); + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-speed); + border: 1px solid transparent; + width: 100%; +} + +.btn-sm { + padding: 0.45rem 0.9rem; + font-size: 0.8rem; +} + +.btn-primary { + background: linear-gradient(135deg, var(--clr-primary) 0%, #0284c7 100%); + color: white; + box-shadow: var(--glow-shadow); +} + +.btn-primary:hover { + transform: translateY(-1px); + filter: brightness(1.1); + box-shadow: 0 0 25px rgba(14, 165, 233, 0.4); +} + +.btn-secondary { + background: rgba(31, 41, 55, 0.8); + border-color: var(--border-color); + color: var(--text-main); +} + +.btn-secondary:hover { + background: rgba(55, 65, 81, 0.8); + border-color: var(--text-muted); +} + +.btn-danger { + background: linear-gradient(135deg, var(--clr-rose) 0%, #dc2626 100%); + color: white; +} + +.btn-danger:hover { + filter: brightness(1.1); +} + +.footer-btn { + background: transparent; + border: 1px dashed var(--border-color); + color: var(--text-muted); + padding: 0.75rem; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-speed); + font-family: var(--font-body); + font-size: 0.85rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; +} + +.footer-btn:hover { + border-color: var(--clr-rose); + color: var(--clr-rose); + background: rgba(239, 68, 68, 0.05); +} + +/* SIDEBAR LEFT */ +.sidebar-column { + display: flex; + flex-direction: column; +} + +.member-add-form { + padding: 0.75rem; + background: rgba(9, 13, 22, 0.4); + border: 1px solid rgba(255, 255, 255, 0.02); + border-radius: var(--radius-sm); + margin-bottom: 1rem; +} + +.registered-members-section h5 { + font-family: var(--font-headers); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.5rem; +} + +.members-ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.4rem; + max-height: 180px; + overflow-y: auto; +} + +.member-item { + display: flex; + align-items: center; + justify-content: space-between; + background: rgba(9, 13, 22, 0.6); + border: 1px solid var(--border-color); + padding: 0.4rem 0.6rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; +} + +.member-profile { + display: flex; + align-items: center; + gap: 0.5rem; +} + +/* AVATAR BADGES */ +.avatar-badge { + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.7rem; + font-weight: 700; + color: #ffffff; + text-transform: uppercase; + flex-shrink: 0; + border: 1px solid rgba(255,255,255,0.1); +} + +.btn-delete-member { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 0.2rem; + transition: color var(--transition-speed); +} + +.btn-delete-member:hover { + color: var(--clr-rose); +} + +/* CENTER KANBAN BOARD */ +.kanban-column { + display: flex; + flex-direction: column; +} + +.kanban-actions-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.kanban-actions-row h2 { + font-family: var(--font-headers); + font-size: 1.35rem; + font-weight: 700; + color: var(--text-highlight); +} + +.kanban-actions-row .btn { + width: auto; +} + +.kanban-board-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.75rem; + flex: 1; + min-height: calc(100vh - 220px); +} + +.kanban-lane-col { + background: rgba(17, 24, 39, 0.4); + border: 1px solid rgba(255, 255, 255, 0.03); + border-radius: var(--radius-md); + display: flex; + flex-direction: column; + padding: 0.5rem; + transition: border-color var(--transition-speed), background var(--transition-speed); +} + +.kanban-lane-col:hover { + border-color: rgba(255, 255, 255, 0.05); + background: rgba(17, 24, 39, 0.5); +} + +.lane-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.6rem 0.75rem; + border-radius: var(--radius-sm); + margin-bottom: 0.75rem; + font-family: var(--font-headers); + font-weight: 700; + font-size: 0.9rem; +} + +.lane-count { + font-size: 0.75rem; + font-weight: 600; + background: rgba(0, 0, 0, 0.3); + padding: 0.1rem 0.5rem; + border-radius: 20px; + color: var(--text-highlight); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.backlog-header { + background: var(--clr-violet-glow); + color: #a5b4fc; + border-left: 4px solid var(--clr-violet); +} +.progress-header { + background: var(--clr-primary-glow); + color: #7dd3fc; + border-left: 4px solid var(--clr-primary); +} +.review-header { + background: var(--clr-amber-glow); + color: #fde047; + border-left: 4px solid var(--clr-amber); +} +.done-header { + background: var(--clr-emerald-glow); + color: #6ee7b7; + border-left: 4px solid var(--clr-emerald); +} + +.lane-cards-wrapper { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.6rem; + min-height: 200px; + overflow-y: auto; + padding-bottom: 1rem; +} + +/* TASK CARDS */ +.task-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + padding: 0.75rem; + cursor: pointer; + transition: all var(--transition-speed); + position: relative; + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.task-card:hover { + transform: translateY(-2px); + border-color: var(--text-muted); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.task-card.active-card { + border-color: var(--clr-primary); + background: rgba(31, 41, 55, 0.5); + box-shadow: 0 0 10px rgba(14, 165, 233, 0.15); +} + +.card-top-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +.priority-badge { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + padding: 0.1rem 0.4rem; + border-radius: 4px; +} + +.prio-high { + background: var(--clr-rose-glow); + color: #fca5a5; + border: 1px solid var(--clr-rose); +} +.prio-medium { + background: var(--clr-amber-glow); + color: #fde047; + border: 1px solid var(--clr-amber); +} +.prio-low { + background: var(--clr-primary-glow); + color: #7dd3fc; + border: 1px solid var(--clr-primary); +} + +.card-date { + font-size: 0.7rem; + color: var(--text-muted); +} + +.card-title { + font-family: var(--font-headers); + font-size: 0.9rem; + font-weight: 600; + color: var(--text-highlight); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-progress-wrapper { + margin-top: 0.25rem; +} + +.card-progress-text { + font-size: 0.65rem; + color: var(--text-muted); + display: flex; + justify-content: space-between; + margin-bottom: 0.15rem; +} + +.card-progress-bar { + height: 3px; + background: rgba(255, 255, 255, 0.08); + border-radius: 2px; + overflow: hidden; +} + +.card-progress-fill { + height: 100%; + width: 0%; + background: var(--clr-primary); + border-radius: 2px; + transition: width var(--transition-speed); +} + +.card-footer-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.4rem; + padding-top: 0.4rem; + border-top: 1px solid rgba(255, 255, 255, 0.03); +} + +/* Card Quick Movement Controls */ +.card-quick-actions { + display: flex; + gap: 0.35rem; +} + +.card-quick-btn { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 0.1rem 0.3rem; + border-radius: 4px; + transition: all var(--transition-speed); +} + +.card-quick-btn:hover { + color: var(--text-highlight); + background: rgba(255, 255, 255, 0.08); +} + +.card-quick-btn.disabled { + opacity: 0.2; + cursor: not-allowed; + pointer-events: none; +} + +/* RIGHT INSPECTOR COLUMN */ +.inspector-column { + display: flex; + flex-direction: column; +} + +.inspector-welcome-card { + background: var(--bg-card); + border: 1px dashed var(--border-color); + border-radius: var(--radius-md); + padding: 2.5rem 1.5rem; + text-align: center; + color: var(--text-muted); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + min-height: 400px; +} + +.pulse-icon { + font-size: 2.5rem; + color: rgba(255, 255, 255, 0.15); + animation: float 4s infinite ease-in-out; +} + +.inspector-welcome-card h4 { + font-family: var(--font-headers); + color: var(--text-highlight); + font-size: 1.15rem; + font-weight: 600; +} + +.inspector-welcome-card p { + font-size: 0.85rem; + line-height: 1.4; +} + +.inspector-workspace { + animation: fadeIn var(--transition-speed) ease; +} + +.hidden { + display: none !important; +} + +/* Subtasks inside Inspector */ +.subtasks-section { + margin: 1.25rem 0; + padding: 1rem; + background: rgba(9, 13, 22, 0.4); + border: 1px solid rgba(255, 255, 255, 0.03); + border-radius: var(--radius-sm); +} + +.subtasks-section h5 { + font-family: var(--font-headers); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.75rem; + letter-spacing: 0.5px; +} + +.subtasks-ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-bottom: 0.75rem; + max-height: 160px; + overflow-y: auto; +} + +.subtask-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + padding: 0.4rem 0.5rem; + border-radius: 4px; + background: rgba(17, 24, 39, 0.6); + border: 1px solid rgba(255, 255, 255, 0.02); + transition: all var(--transition-speed); +} + +.subtask-item:hover { + background: rgba(17, 24, 39, 0.9); +} + +.subtask-left { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + cursor: pointer; +} + +.subtask-left input[type="checkbox"] { + accent-color: var(--clr-primary); + cursor: pointer; + width: 15px; + height: 15px; +} + +.subtask-text { + font-size: 0.85rem; + transition: color var(--transition-speed); + word-break: break-word; +} + +.subtask-item.completed .subtask-text { + text-decoration: line-through; + color: var(--text-muted); +} + +.btn-delete-subtask { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 0.2rem; + transition: color var(--transition-speed); +} + +.btn-delete-subtask:hover { + color: var(--clr-rose); +} + +.add-subtask-row { + display: flex; + gap: 0.5rem; +} + +.add-subtask-row input { + padding: 0.45rem 0.75rem; + font-size: 0.8rem; +} + +.add-subtask-row button { + padding: 0.45rem; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; +} + +/* Inspector actions */ +.inspector-actions { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-top: 1.5rem; + padding-top: 1rem; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.inspector-actions .btn { + flex: 1; +} + +/* FOOTER */ +.app-footer { + border-top: 1px solid var(--border-color); + padding: 1.5rem 2rem; + text-align: center; + background-color: rgba(9, 13, 22, 0.8); + font-size: 0.85rem; + color: var(--text-muted); + margin-top: auto; +} + +.footer-container a { + color: var(--clr-primary); + text-decoration: none; + transition: color var(--transition-speed); +} + +.footer-container a:hover { + color: #38bdf8; + text-decoration: underline; +} + +/* ANIMATIONS */ +@keyframes pulse { + 0% { text-shadow: 0 0 8px rgba(14, 165, 233, 0.3); } + 100% { text-shadow: 0 0 16px rgba(14, 165, 233, 0.7); } +} + +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-8px); } + 100% { transform: translateY(0px); } +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } +} + +/* RESPONSIVE LAYOUT BREAKPOINTS */ +@media (max-width: 1200px) { + .main-container { + grid-template-columns: 260px 1fr; + } + .inspector-column { + grid-column: span 2; + } + .inspector-welcome-card { + min-height: 200px; + padding: 1.5rem; + } +} + +@media (max-width: 900px) { + .main-container { + grid-template-columns: 1fr; + padding: 1rem; + } + .sidebar-column, .kanban-column, .inspector-column { + grid-column: span 1; + } + .kanban-board-grid { + grid-template-columns: 1fr; + gap: 1rem; + min-height: auto; + } + .kanban-lane-col { + min-height: 180px; + } + .header-container { + flex-direction: column; + align-items: flex-start; + } + .global-progress-wrapper { + width: 100%; + } +} diff --git a/Projects/Team Collaboration Board/thumbnail.svg b/Projects/Team Collaboration Board/thumbnail.svg new file mode 100644 index 00000000..e68d65ae --- /dev/null +++ b/Projects/Team Collaboration Board/thumbnail.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CollabBoard + + + HUB + + + + + Progress: 50% + + + + + + + + + + + + Backlog + + 1 + + + + + + LOW + Write Docs + + + + + + U + + + + + + + + + + In Progress + + 1 + + + + + + HIGH + OAuth Login + + + + + + AS + + + + + + + + + + In Review + + 1 + + + + + + HIGH + Optimize DB + + + + + + CB + + + + + + + + + + Done + + 1 + + + + + + MEDIUM + Design Landing + + + + + + BJ + + +