UI Components Hard
Scheduler Timeline
A Gantt-style project scheduler timeline with draggable tasks, milestones, and dependency arrows. Shows task progress and team assignments.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
display: flex;
align-items: flex-start;
justify-content: center;
padding: 2rem 1rem;
}
/* โโ Wrapper โโ */
.tl-wrapper {
width: min(1040px, 100%);
background: #1e293b;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1.25rem;
overflow: hidden;
}
/* โโ Header โโ */
.tl-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
gap: 1rem;
flex-wrap: wrap;
}
.tl-header-left {
display: flex;
align-items: baseline;
gap: 0.625rem;
}
.tl-title {
font-size: 1.125rem;
font-weight: 700;
color: #f1f5f9;
}
.tl-subtitle {
font-size: 0.8125rem;
color: #64748b;
font-weight: 500;
}
.tl-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.view-toggle {
display: flex;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.5rem;
overflow: hidden;
}
.view-btn {
padding: 0.375rem 0.75rem;
background: transparent;
border: none;
color: #64748b;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.view-btn.active {
background: rgba(56, 189, 248, 0.15);
color: #38bdf8;
}
.btn-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.5rem;
color: #94a3b8;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.btn-icon:hover {
background: rgba(255, 255, 255, 0.1);
color: #f1f5f9;
}
.btn-secondary {
padding: 0.375rem 0.875rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.625rem;
color: #94a3b8;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
color: #f1f5f9;
}
/* โโ Body layout โโ */
.tl-body {
display: flex;
overflow: hidden;
}
/* โโ Task list panel โโ */
.tl-task-panel {
width: 260px;
flex-shrink: 0;
border-right: 1px solid rgba(255, 255, 255, 0.06);
display: flex;
flex-direction: column;
}
.tl-task-panel-header {
display: flex;
justify-content: space-between;
padding: 0.625rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.02);
font-size: 0.75rem;
font-weight: 600;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.05em;
height: 40px;
align-items: center;
}
.tl-task-list {
display: flex;
flex-direction: column;
}
.tl-task-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
height: 44px;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
gap: 0.5rem;
}
.tl-task-row.group {
background: rgba(255, 255, 255, 0.03);
padding-left: 1rem;
}
.tl-task-row.task {
padding-left: 1.75rem;
}
.tl-task-row.milestone {
padding-left: 1.75rem;
}
.tl-task-name {
font-size: 0.8125rem;
color: #e2e8f0;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.tl-task-row.group .tl-task-name {
color: #94a3b8;
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.tl-task-row.milestone .tl-task-name {
color: #fbbf24;
}
.tl-task-avatar {
width: 1.5rem;
height: 1.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6rem;
font-weight: 700;
color: #0f172a;
flex-shrink: 0;
}
/* โโ Timeline panel โโ */
.tl-timeline-panel {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
display: flex;
flex-direction: column;
position: relative;
}
.tl-date-header {
display: flex;
height: 40px;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
background: rgba(255, 255, 255, 0.02);
position: sticky;
top: 0;
z-index: 5;
}
.tl-date-col {
flex-shrink: 0;
border-right: 1px solid rgba(255, 255, 255, 0.04);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.6875rem;
font-weight: 600;
color: #475569;
text-transform: uppercase;
letter-spacing: 0.03em;
white-space: nowrap;
}
.tl-date-col.today-col {
color: #38bdf8;
}
/* โโ Grid area โโ */
.tl-grid-area {
position: relative;
flex: 1;
}
.tl-grid-row {
height: 44px;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
position: relative;
}
.tl-grid-row.group-row {
background: rgba(255, 255, 255, 0.01);
}
.tl-grid-cell {
position: absolute;
top: 0;
height: 100%;
border-right: 1px solid rgba(255, 255, 255, 0.03);
}
/* โโ Task bar โโ */
.tl-bar {
position: absolute;
top: 50%;
transform: translateY(-50%);
height: 24px;
border-radius: 0.375rem;
display: flex;
align-items: center;
overflow: hidden;
cursor: grab;
transition: box-shadow 0.12s;
z-index: 2;
}
.tl-bar:hover {
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
}
.tl-bar:active {
cursor: grabbing;
}
.tl-bar-progress {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: rgba(0, 0, 0, 0.2);
border-radius: inherit;
}
.tl-bar-label {
position: relative;
padding: 0 0.375rem 0 0.5rem;
font-size: 0.6875rem;
font-weight: 600;
color: #0f172a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.tl-bar-avatar {
width: 18px;
height: 18px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.25);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.55rem;
font-weight: 700;
color: #fff;
margin-right: 0.25rem;
flex-shrink: 0;
}
/* โโ Milestone diamond โโ */
.tl-milestone {
position: absolute;
top: 50%;
transform: translateY(-50%) rotate(45deg);
width: 14px;
height: 14px;
background: #fbbf24;
border: 2px solid #0f172a;
z-index: 2;
cursor: pointer;
}
/* โโ Today line โโ */
.tl-today-line {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background: #f87171;
z-index: 10;
pointer-events: none;
}
.tl-today-line::before {
content: "Today";
position: absolute;
top: -22px;
left: 50%;
transform: translateX(-50%);
font-size: 0.625rem;
font-weight: 700;
color: #f87171;
white-space: nowrap;
background: #1e293b;
padding: 0 0.25rem;
}
/* โโ Tooltip โโ */
.tl-tooltip {
position: fixed;
background: #0f172a;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 0.625rem;
padding: 0.75rem 1rem;
z-index: 200;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
min-width: 200px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.tl-tooltip.visible {
opacity: 1;
}
.tl-tooltip-title {
font-size: 0.875rem;
font-weight: 700;
color: #f1f5f9;
margin-bottom: 0.5rem;
}
.tl-tooltip-row {
display: flex;
justify-content: space-between;
gap: 1rem;
font-size: 0.8rem;
color: #94a3b8;
margin-top: 0.25rem;
}
.tt-label {
color: #475569;
}
/* โโ Scrollbar โโ */
.tl-timeline-panel::-webkit-scrollbar {
height: 6px;
}
.tl-timeline-panel::-webkit-scrollbar-track {
background: transparent;
}
.tl-timeline-panel::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}(function () {
"use strict";
/* โโ Data โโ */
const AVATAR_COLORS = {
Alice: "#38bdf8",
Bob: "#a78bfa",
Carol: "#fb923c",
David: "#34d399",
Emma: "#f87171",
Milestone: "#fbbf24",
};
const tasks = [
{ id: "g1", type: "group", name: "Feature Development" },
{
id: "t1",
type: "task",
name: "User Authentication",
assignee: "Alice",
color: "#38bdf8",
start: "2026-01-05",
end: "2026-01-23",
progress: 100,
group: "g1",
},
{
id: "t2",
type: "task",
name: "Dashboard UI",
assignee: "Bob",
color: "#a78bfa",
start: "2026-01-12",
end: "2026-02-06",
progress: 80,
group: "g1",
},
{
id: "t3",
type: "task",
name: "API Integration",
assignee: "Carol",
color: "#fb923c",
start: "2026-01-26",
end: "2026-02-20",
progress: 55,
group: "g1",
},
{ id: "g2", type: "group", name: "Testing" },
{
id: "t4",
type: "task",
name: "Unit Tests",
assignee: "David",
color: "#34d399",
start: "2026-02-09",
end: "2026-02-27",
progress: 30,
group: "g2",
},
{
id: "t5",
type: "task",
name: "E2E Testing",
assignee: "Emma",
color: "#f87171",
start: "2026-02-23",
end: "2026-03-13",
progress: 10,
group: "g2",
},
{ id: "g3", type: "group", name: "Launch" },
{ id: "m1", type: "milestone", name: "Beta Release", start: "2026-03-06", group: "g3" },
{ id: "m2", type: "milestone", name: "Production Deploy", start: "2026-03-27", group: "g3" },
];
/* โโ Config โโ */
const DAY_W = 28; // px per day
const ROW_H = 44;
const TIMELINE_START = new Date(2026, 0, 5); // Jan 5, 2026
const TIMELINE_END = new Date(2026, 2, 29); // Mar 29, 2026
const TODAY = new Date(2026, 2, 2); // today for demo
let currentZoom = "week";
let scrollOffset = 0;
/* โโ Elements โโ */
const taskList = document.getElementById("tl-task-list");
const dateHeader = document.getElementById("tl-date-header");
const gridArea = document.getElementById("tl-grid-area");
const todayLine = document.getElementById("tl-today-line");
const timelinePanel = document.getElementById("tl-timeline-panel");
const tooltip = document.getElementById("tl-tooltip");
/* โโ Helpers โโ */
function daysBetween(a, b) {
return Math.round((b - a) / 86400000);
}
function dateFromOffset(days) {
const d = new Date(TIMELINE_START);
d.setDate(d.getDate() + days);
return d;
}
function offsetFromDate(d) {
return daysBetween(TIMELINE_START, d);
}
function fmtDate(d) {
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
return `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
}
function parseDate(str) {
const [y, m, d] = str.split("-").map(Number);
return new Date(y, m - 1, d);
}
const totalDays = daysBetween(TIMELINE_START, TIMELINE_END) + 1;
/* โโ Render task list โโ */
function renderTaskList() {
taskList.innerHTML = "";
tasks.forEach((t) => {
const row = document.createElement("div");
row.className = `tl-task-row ${t.type}`;
row.dataset.id = t.id;
const name = document.createElement("span");
name.className = "tl-task-name";
name.textContent = t.type === "milestone" ? `โ ${t.name}` : t.name;
row.appendChild(name);
if (t.type === "task") {
const av = document.createElement("div");
av.className = "tl-task-avatar";
av.style.background = AVATAR_COLORS[t.assignee] || "#64748b";
av.textContent = t.assignee.slice(0, 2).toUpperCase();
row.appendChild(av);
}
taskList.appendChild(row);
});
}
/* โโ Render date header โโ */
function renderDateHeader() {
dateHeader.innerHTML = "";
dateHeader.style.width = `${totalDays * DAY_W}px`;
if (currentZoom === "week") {
// Group by weeks
let d = new Date(TIMELINE_START);
while (d <= TIMELINE_END) {
const weekStart = new Date(d);
const daysInWeek = Math.min(7, daysBetween(d, TIMELINE_END) + 1);
const col = document.createElement("div");
col.className = "tl-date-col";
col.style.width = `${daysInWeek * DAY_W}px`;
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
col.textContent = `${months[weekStart.getMonth()]} ${weekStart.getDate()}`;
if (
isSameDay(weekStart, TODAY) ||
(TODAY >= weekStart && TODAY < new Date(weekStart.getTime() + daysInWeek * 86400000))
) {
col.classList.add("today-col");
}
dateHeader.appendChild(col);
d.setDate(d.getDate() + 7);
}
} else if (currentZoom === "month") {
// Group by months
let d = new Date(TIMELINE_START.getFullYear(), TIMELINE_START.getMonth(), 1);
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
while (d <= TIMELINE_END) {
const monthStart = new Date(Math.max(d, TIMELINE_START));
const monthEnd = new Date(d.getFullYear(), d.getMonth() + 1, 0);
const clampedEnd = new Date(Math.min(monthEnd, TIMELINE_END));
const days = daysBetween(monthStart, clampedEnd) + 1;
const col = document.createElement("div");
col.className = "tl-date-col";
col.style.width = `${days * DAY_W}px`;
col.textContent = `${months[d.getMonth()]} ${d.getFullYear()}`;
if (d.getMonth() === TODAY.getMonth()) col.classList.add("today-col");
dateHeader.appendChild(col);
d.setMonth(d.getMonth() + 1);
}
} else {
// Quarter
const col = document.createElement("div");
col.className = "tl-date-col";
col.style.width = `${totalDays * DAY_W}px`;
col.textContent = "Q1 2026";
dateHeader.appendChild(col);
}
}
/* โโ Render grid โโ */
function renderGrid() {
// Remove old rows (but keep today line)
gridArea.querySelectorAll(".tl-grid-row").forEach((r) => r.remove());
tasks.forEach((t, idx) => {
const row = document.createElement("div");
row.className = `tl-grid-row${t.type === "group" ? " group-row" : ""}`;
row.style.width = `${totalDays * DAY_W}px`;
// Vertical grid cells
for (let d = 0; d < totalDays; d++) {
const cell = document.createElement("div");
cell.className = "tl-grid-cell";
cell.style.left = `${d * DAY_W}px`;
cell.style.width = `${DAY_W}px`;
row.appendChild(cell);
}
if (t.type === "task") {
const startDate = parseDate(t.start);
const endDate = parseDate(t.end);
const startOff = offsetFromDate(startDate);
const duration = daysBetween(startDate, endDate) + 1;
const bar = document.createElement("div");
bar.className = "tl-bar";
bar.style.left = `${startOff * DAY_W + 2}px`;
bar.style.width = `${duration * DAY_W - 4}px`;
bar.style.background = t.color;
bar.dataset.id = t.id;
const progress = document.createElement("div");
progress.className = "tl-bar-progress";
progress.style.width = `${t.progress}%`;
bar.appendChild(progress);
const label = document.createElement("span");
label.className = "tl-bar-label";
label.textContent = t.name;
bar.appendChild(label);
const avatar = document.createElement("div");
avatar.className = "tl-bar-avatar";
avatar.textContent = t.assignee.slice(0, 2).toUpperCase();
bar.appendChild(avatar);
// Drag logic
setupBarDrag(bar, t);
// Tooltip
bar.addEventListener("mouseenter", (e) => showTooltip(e, t));
bar.addEventListener("mousemove", (e) => positionTooltip(e));
bar.addEventListener("mouseleave", hideTooltip);
row.appendChild(bar);
} else if (t.type === "milestone") {
const startDate = parseDate(t.start);
const startOff = offsetFromDate(startDate);
const diamond = document.createElement("div");
diamond.className = "tl-milestone";
diamond.style.left = `${startOff * DAY_W + DAY_W / 2 - 7}px`;
diamond.title = t.name;
diamond.addEventListener("mouseenter", (e) => showTooltip(e, t));
diamond.addEventListener("mousemove", (e) => positionTooltip(e));
diamond.addEventListener("mouseleave", hideTooltip);
row.appendChild(diamond);
}
gridArea.appendChild(row);
});
// Today line
const todayOff = offsetFromDate(TODAY);
todayLine.style.left = `${todayOff * DAY_W + DAY_W / 2}px`;
gridArea.appendChild(todayLine); // re-append to keep on top
}
/* โโ Drag โโ */
function setupBarDrag(bar, task) {
let startX,
startLeft,
dragging = false;
bar.addEventListener("mousedown", (e) => {
e.preventDefault();
dragging = true;
startX = e.clientX;
startLeft = parseInt(bar.style.left);
document.body.style.userSelect = "none";
});
document.addEventListener("mousemove", (e) => {
if (!dragging) return;
hideTooltip();
const dx = e.clientX - startX;
const newLeft = Math.max(0, startLeft + dx);
bar.style.left = `${newLeft}px`;
// Update task dates
const dayOffset = Math.round(newLeft / DAY_W);
const newStart = dateFromOffset(dayOffset);
const duration = daysBetween(parseDate(task.start), parseDate(task.end));
const newEnd = new Date(newStart);
newEnd.setDate(newEnd.getDate() + duration);
task.start = fmtDateISO(newStart);
task.end = fmtDateISO(newEnd);
});
document.addEventListener("mouseup", () => {
if (!dragging) return;
dragging = false;
document.body.style.userSelect = "";
});
}
function fmtDateISO(d) {
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
/* โโ Tooltip โโ */
function showTooltip(e, task) {
document.getElementById("tt-title").textContent = task.name;
document.getElementById("tt-assignee").textContent = task.assignee || "โ";
document.getElementById("tt-start").textContent = task.start
? fmtDate(parseDate(task.start))
: "โ";
document.getElementById("tt-end").textContent = task.end ? fmtDate(parseDate(task.end)) : "โ";
document.getElementById("tt-progress").textContent =
task.progress !== undefined ? `${task.progress}%` : "Milestone";
tooltip.classList.add("visible");
positionTooltip(e);
}
function positionTooltip(e) {
const tw = tooltip.offsetWidth;
const th = tooltip.offsetHeight;
let x = e.clientX + 12;
let y = e.clientY + 12;
if (x + tw > window.innerWidth - 8) x = e.clientX - tw - 12;
if (y + th > window.innerHeight - 8) y = e.clientY - th - 12;
tooltip.style.left = `${x}px`;
tooltip.style.top = `${y}px`;
}
function hideTooltip() {
tooltip.classList.remove("visible");
}
/* โโ View zoom โโ */
document.querySelectorAll(".view-btn").forEach((btn) => {
btn.addEventListener("click", () => {
document.querySelectorAll(".view-btn").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
currentZoom = btn.dataset.zoom;
renderDateHeader();
renderGrid();
});
});
/* โโ Scroll navigation โโ */
document.getElementById("tl-prev").addEventListener("click", () => {
timelinePanel.scrollLeft = Math.max(0, timelinePanel.scrollLeft - 7 * DAY_W);
});
document.getElementById("tl-next").addEventListener("click", () => {
timelinePanel.scrollLeft += 7 * DAY_W;
});
document.getElementById("tl-today").addEventListener("click", () => {
const todayOff = offsetFromDate(TODAY);
timelinePanel.scrollLeft = Math.max(0, todayOff * DAY_W - 100);
});
/* โโ Sync scroll โโ */
timelinePanel.addEventListener("scroll", () => {
// Keep task list in sync vertically (not needed here as both scroll together)
});
function isSameDay(a, b) {
return (
a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate()
);
}
/* โโ Init โโ */
renderTaskList();
renderDateHeader();
renderGrid();
// Scroll to today
setTimeout(() => {
const todayOff = offsetFromDate(TODAY);
timelinePanel.scrollLeft = Math.max(0, todayOff * DAY_W - 150);
}, 50);
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Scheduler Timeline</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="tl-wrapper">
<!-- Header -->
<div class="tl-header">
<div class="tl-header-left">
<h2 class="tl-title">Project Timeline</h2>
<span class="tl-subtitle">Q1 2026</span>
</div>
<div class="tl-header-right">
<div class="view-toggle">
<button class="view-btn active" data-zoom="week">Week</button>
<button class="view-btn" data-zoom="month">Month</button>
<button class="view-btn" data-zoom="quarter">Quarter</button>
</div>
<button class="btn-icon" id="tl-prev" aria-label="Scroll left">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m15 18-6-6 6-6"/></svg>
</button>
<button class="btn-icon" id="tl-next" aria-label="Scroll right">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m9 18 6-6-6-6"/></svg>
</button>
<button class="btn-secondary" id="tl-today">Today</button>
</div>
</div>
<!-- Body -->
<div class="tl-body">
<!-- Task list panel -->
<div class="tl-task-panel">
<div class="tl-task-panel-header">
<span>Task</span>
<span>Assignee</span>
</div>
<div class="tl-task-list" id="tl-task-list"></div>
</div>
<!-- Timeline panel -->
<div class="tl-timeline-panel" id="tl-timeline-panel">
<div class="tl-date-header" id="tl-date-header"></div>
<div class="tl-grid-area" id="tl-grid-area">
<div class="tl-today-line" id="tl-today-line"></div>
</div>
</div>
</div>
</div>
<!-- Tooltip -->
<div class="tl-tooltip" id="tl-tooltip" aria-hidden="true">
<div class="tl-tooltip-title" id="tt-title"></div>
<div class="tl-tooltip-row"><span class="tt-label">Assignee</span><span id="tt-assignee"></span></div>
<div class="tl-tooltip-row"><span class="tt-label">Start</span><span id="tt-start"></span></div>
<div class="tl-tooltip-row"><span class="tt-label">End</span><span id="tt-end"></span></div>
<div class="tl-tooltip-row"><span class="tt-label">Progress</span><span id="tt-progress"></span></div>
</div>
<script src="script.js"></script>
</body>
</html>Scheduler Timeline
A Gantt-style project timeline that renders task bars on a scrollable date grid. Tasks and milestones are organized in a hierarchical left panel, with bars positioned and sized by start/end dates. A red dashed today line marks the current date, and clicking any bar reveals a detailed tooltip with progress, assignee, and date information.
Features
- Hierarchical task list with group headers and individual tasks
- Task bars sized and positioned by start/end dates with progress fill
- Milestone diamonds rendered on the timeline axis
- Avatar initials on bars for assigned team members
- Draggable task bars to shift dates horizontally
- Today vertical line for at-a-glance schedule status
- Zoom controls (Week / Month / Quarter view)