Skip to main content
Back to examples
Popular features/Work and material resources
Loading live demo…from dhtmlxcode.com
Popular features

Work and material resources

Track people and shared equipment on one capacity panel, where a day cell turns red once demand passes the limit.

gantt.plugins({ tooltip: true });

gantt.message({
	text: "Week 2 needs 3 GPU nodes but the pool holds 2. Double-click a task to reassign, or drag its bar into free space.",
	expire: -1
});

gantt.config.date_format       = "%Y-%m-%d";
gantt.config.resources         = true;        // parse the "resources" collection + assignments
gantt.config.resource_store    = "resource";
gantt.config.resource_property = "resources";
gantt.config.work_time         = true;
gantt.config.order_branch      = true;
gantt.config.open_tree_initially = true;
gantt.config.row_height        = 34;
gantt.config.bar_height        = 22;
gantt.config.scale_height      = 50;
gantt.config.min_column_width  = 44;
gantt.config.grid_width        = 420;
gantt.config.scales = [
	{ unit: "week", step: 1, format: function (d) { return "Week of " + gantt.date.date_to_str("%d %M")(d); } },
	{ unit: "day",  step: 1, format: "%d" }
];

let WORK_DAY = 8;   // hours/day capacity for a person

/* People get a color; equipment renders as neutral pills. */
let PEOPLE_COLORS = { 10: "#6366f1", 11: "#0ea5e9", 12: "#10b981" };

let RESOURCES = [
	{ id: 1,  text: "People",                 parent: null, category: "people" },
	{ id: 2,  text: "Shared infrastructure",  parent: null, category: "equipment" },

	{ id: 10, text: "Ana Kim  (ML)",   parent: 1, type: "work", unit: "h/day", capacity: WORK_DAY, category: "people" },
	{ id: 11, text: "Ben Ruiz (Backend)", parent: 1, type: "work", unit: "h/day", capacity: WORK_DAY, category: "people" },
	{ id: 12, text: "Cleo Mara (QA)",  parent: 1, type: "work", unit: "h/day", capacity: WORK_DAY, category: "people" },

	{ id: 20, text: "CI build agents", parent: 2, type: "material", unit: "agents",  capacity: 4, category: "equipment" },
	{ id: 21, text: "GPU eval nodes",  parent: 2, type: "material", unit: "nodes",   capacity: 2, category: "equipment" },
	{ id: 22, text: "Device lab",      parent: 2, type: "material", unit: "devices", capacity: 6, category: "equipment" },
	{ id: 23, text: "Staging seats",   parent: 2, type: "material", unit: "seats",   capacity: 3, category: "equipment" }
];
function getResourceStore() {
	return gantt.getDatastore(gantt.config.resource_store);
}

/* Bar color by the task's first person. */
let barStyles = document.createElement("style");
barStyles.textContent = Object.keys(PEOPLE_COLORS).map(function (id) {
	return ".gantt_task_line.owner_" + id + " { background: " + PEOPLE_COLORS[id] + "; }";
}).join("\n");
document.head.appendChild(barStyles);

function isAssignable(task) {
	return task.type !== gantt.config.types.project && task.type !== gantt.config.types.milestone;
}
function firstPerson(task) {
	let list = task.resources || [];
	for (let i = 0; i < list.length; i++) {
		let resource = getResourceStore().getItem(list[i].resource_id);
		if (resource && resource.type === "work") return list[i].resource_id;
	}
	return null;
}

/* ---------------------------------------------------------------------------
 *  CAPACITY MATH: one rule for both kinds: concurrent demand vs ceiling
 * ------------------------------------------------------------------------ */
function ceiling(resource) {
	return resource.type === "work" ? WORK_DAY : resource.capacity;
}
function cellLoad(resource, tasks) {
	let load = 0;
	tasks.forEach(function (task) {
		gantt.getResourceAssignments(resource.id, task.id).forEach(function (assignment) { load += Number(assignment.value); });
	});
	return load;
}
function peakLoad(resource) {
	let byDay = {};
	gantt.getResourceAssignments(resource.id).forEach(function (assignment) {
		let task = gantt.getTask(assignment.task_id);
		for (let date = task.start_date; date < task.end_date; date = gantt.date.add(date, 1, "day")) {
			if (gantt.isWorkTime({ date: date, task })) byDay[+date] = (byDay[+date] || 0) + Number(assignment.value);
		}
	});
	let peak = 0;
	for (let k in byDay) peak = Math.max(peak, byDay[k]);
	return peak;
}

/* ---------------------------------------------------------------------------
 *  TEMPLATES
 * ------------------------------------------------------------------------ */
gantt.templates.timeline_cell_class = function (task, date) {
	return gantt.isWorkTime({ date, task }) ? "" : "week_end";
};
gantt.templates.task_class = function (start, end, task) {
	if (!isAssignable(task)) return "";
	let person = firstPerson(task);
	return person ? "owner_" + person : "no_owner";
};
gantt.templates.tooltip_text = function (start, end, task) {
	if (!isAssignable(task)) return "<b>" + task.text + "</b>";
	let rows = (task.resources || []).filter(function (assignment) {
		let resource = getResourceStore().getItem(assignment.resource_id);
		return resource && resource.type; // skip not-yet-picked rows (default to a group folder id)
	}).map(function (assignment) {
		let resource = getResourceStore().getItem(assignment.resource_id);
		return resource.text + ": " + assignment.value + " " + resource.unit; // work resources already carry unit "h/day"
	}).join("<br>");
	return "<b>" + task.text + "</b><br>" + (rows || "No resources");
};

/* Per-day allocation dot in the resource panel. */
gantt.templates.resource_cell_class = function (start, end, resource, tasks) {
	if (getResourceStore().hasChild(resource.id)) return "";
	let kind = resource.type === "work" ? "res_work" : "res_material";
	return "resource_marker " + kind + " " + (cellLoad(resource, tasks) > ceiling(resource) ? "workday_over" : "workday_ok");
};
gantt.templates.resource_cell_value = function (start, end, resource, tasks) {
	if (getResourceStore().hasChild(resource.id)) return "";
	let load = cellLoad(resource, tasks);
	if (!load) return "";
	if (resource.type === "work") return "<div>" + load + "</div>";                          // hours in a circle
	return "<div>" + load + "<span class='cap'>/" + resource.capacity + "</span></div>";     // used / pool in a pill
};

/* ---------------------------------------------------------------------------
 *  MAIN GRID
 * ------------------------------------------------------------------------ */
gantt.config.columns = [
	{ name: "text", label: "Task", tree: true, width: 210, resize: true },
	{ name: "start_date", label: "Start", align: "center", width: 100, resize: true },
	{ name: "resources", label: "Resources", align: "left", width: 156, resize: true, template: function (task) {
		if (!isAssignable(task)) return "";
		let list = (task.resources || []).filter(function (assignment) {
			let resource = getResourceStore().getItem(assignment.resource_id);
			return resource && resource.type; // skip not-yet-picked rows (default to a group folder id)
		});
		if (!list.length) return "<span class='muted'>None</span>";
		return list.map(function (assignment) {
			let resource = getResourceStore().getItem(assignment.resource_id);
			if (resource.type === "work") {
				return "<span class='chip' style='background:" + PEOPLE_COLORS[resource.id] + "' title='" + resource.text + ", " + assignment.value + "h/day'>" + resource.text.charAt(0) + "</span>";
			}
			return "<span class='pill' title='" + resource.text + "'>" + assignment.value + " " + resource.unit + "</span>";
		}).join(" ");
	}},
	{ name: "duration", label: "Days", align: "center", width: 50, resize: true }
];

/* ---------------------------------------------------------------------------
 *  LIGHTBOX: double-click a task to add, edit or remove its assignments
 * ------------------------------------------------------------------------ */
let assignResourceEditor = { type: "select", map_to: "resource_id", options: gantt.serverList("resourceOptions") };
let assignValueEditor    = { type: "number", map_to: "value", min: 0, max: 20 };

let resourceLightboxConfig = {
	columns: [
		{ name: "resource", label: "Resource", align: "center", width: 160, editor: assignResourceEditor, template: function (assignment) {
			let resource = getResourceStore().getItem(assignment.resource_id);
			// New rows default to the first resource-tree node, a group folder (no .type):
			// treat that the same as "nothing picked yet" rather than showing the folder name.
			return (resource && resource.type) ? resource.text : "<span class='muted'>Choose a resource...</span>";
		}},
		{ name: "value", label: "Amount", align: "center", width: 90, editor: assignValueEditor, template: function (assignment) {
			let resource = getResourceStore().getItem(assignment.resource_id);
			if (!assignment.value || !resource || !resource.type) return "";
			return resource.type === "work" ? assignment.value + " h/day" : assignment.value + " " + resource.unit;
		}},
		{ name: "delete", label: "", align: "center", width: 40, template: function (assignment) {
			return "<div data-assignment-id='" + assignment.id + "' data-assignment-delete='" + assignment.id + "' class='dhx_gantt_icon dhx_gantt_icon_delete'></div>";
		}}
	]
};

gantt.locale.labels.section_resources = "Resources";
gantt.config.lightbox = {
	sections: [
		{ name: "description", height: 38, map_to: "text", type: "textarea", focus: true },
		{ name: "resource_selector", label: "Resources", type: "resource_selector", config: resourceLightboxConfig },
		{ name: "time", type: "duration", map_to: "auto" }
	]
};

/* ---------------------------------------------------------------------------
 *  RESOURCE PANEL  (bottom grid + per-day timeline)
 * ------------------------------------------------------------------------ */
let resourceConfig = {
	scale_height: 30,
	row_height: 34,
	scales: [{ unit: "day", step: 1, format: "%d" }],
	columns: [
		{ name: "name", label: "Resource", tree: true, width: 210, resize: true, template: function (resource) {
			if (getResourceStore().hasChild(resource.id)) return resource.text;
			return "<span class='res_kind res_kind--" + resource.type + "'></span>" + resource.text;
		}},
		{ name: "capacity", label: "Capacity", align: "center", width: 96, template: function (resource) {
			if (getResourceStore().hasChild(resource.id)) return "";
			return resource.type === "work" ? WORK_DAY + " h/day" : resource.capacity + " " + resource.unit;
		}},
		{ name: "peak", label: "Peak", align: "center", width: 76, template: function (resource) {
			if (getResourceStore().hasChild(resource.id)) return "";
			let person = peakLoad(resource), over = person > ceiling(resource);
			return "<span class='res_peak" + (over ? " res_peak--over" : "") + "'>" + person + (resource.type === "work" ? "h" : "") + "</span>";
		}}
	]
};

let resourceTemplates = {
	grid_row_class: function (start, end, resource) { return getResourceStore().hasChild(resource.id) ? "group_row" : ""; },
	task_row_class: function (start, end, resource) { return getResourceStore().hasChild(resource.id) ? "group_row" : ""; }
};

gantt.config.layout = {
	css: "gantt_container",
	rows: [
		{ gravity: 2, cols: [
			{ view: "grid", group: "grids", scrollY: "scrollVer" },
			{ resizer: true, width: 1 },
			{ view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
			{ view: "scrollbar", id: "scrollVer", group: "vertical" }
		]},
		{ resizer: true, width: 1, next: "resources" },
		{ gravity: 1, id: "resources", config: resourceConfig, templates: resourceTemplates, cols: [
			{ view: "resourceGrid", group: "grids", scrollY: "resourceVScroll" },
			{ resizer: true, width: 1 },
			{ view: "resourceTimeline", scrollX: "scrollHor", scrollY: "resourceVScroll" },
			{ view: "scrollbar", id: "resourceVScroll", group: "vertical" }
		]},
		{ view: "scrollbar", id: "scrollHor" }
	]
};

/* ---------------------------------------------------------------------------
 *  INIT + DATA
 *  Week 2: ranking + safety + regression evals overlap -> 3 GPU nodes needed,
 *  pool is 2 -> red. Everyone stays under 8h/day AND Ana is free in week 3,
 *  so the manager can drag one eval there to level the load back to green.
 * ------------------------------------------------------------------------ */
gantt.init("gantt_here");

/* The resources plugin auto-fills "resourceOptions" from the WHOLE resource tree
 * (group folders included) as soon as it parses: rebuild it to leaf items only
 * so the assignment editor's dropdown doesn't list "People" / "Shared infrastructure"
 * as pickable resources. */
getResourceStore().attachEvent("onParse", function () {
	gantt.updateCollection("resourceOptions", RESOURCES.filter(function (resource) { return !!resource.type; }).map(function (resource) {
		return { key: resource.id, label: resource.text };
	}));
});

gantt.parse({
	tasks: [
		{ id: 100, text: "AI Search 4.0", type: "project", open: true },

		{ id: 11, text: "Data pipeline",      parent: 100, start_date: "2026-07-06", duration: 4, progress: 0.7, resources: [{ resource_id: 11, value: 6 }, { resource_id: 20, value: 2 }] },
		{ id: 12, text: "Model training run", parent: 100, start_date: "2026-07-06", duration: 4, progress: 0.6, resources: [{ resource_id: 10, value: 6 }, { resource_id: 21, value: 1 }] },
		{ id: 13, text: "Eval harness",       parent: 100, start_date: "2026-07-07", duration: 3, progress: 0.5, resources: [{ resource_id: 12, value: 5 }, { resource_id: 20, value: 1 }] },
		{ id: 14, text: "Data validation checks", parent: 100, start_date: "2026-07-06", duration: 1, progress: 0.8, resources: [{ resource_id: 12, value: 3 }, { resource_id: 22, value: 2 }] },

		{ id: 21, text: "Eval: ranking",      parent: 100, start_date: "2026-07-13", duration: 3, progress: 0.2, resources: [{ resource_id: 10, value: 5 }, { resource_id: 21, value: 1 }] },
		{ id: 22, text: "Eval: safety",       parent: 100, start_date: "2026-07-13", duration: 3, progress: 0.2, resources: [{ resource_id: 11, value: 5 }, { resource_id: 21, value: 1 }] },
		{ id: 23, text: "Regression eval",    parent: 100, start_date: "2026-07-13", duration: 3, progress: 0.1, resources: [{ resource_id: 12, value: 6 }, { resource_id: 21, value: 1 }] },
		{ id: 24, text: "Perf benchmark",     parent: 100, start_date: "2026-07-13", duration: 3, progress: 0.1, resources: [{ resource_id: 11, value: 3 }, { resource_id: 20, value: 2 }] },
		{ id: 25, text: "Latency profiling",  parent: 100, start_date: "2026-07-16", duration: 2, progress: 0, resources: [{ resource_id: 10, value: 4 }, { resource_id: 20, value: 1 }] },
		{ id: 26, text: "Eval report review", parent: 100, start_date: "2026-07-13", duration: 2, progress: 0, resources: [{ resource_id: 12, value: 4 }, { resource_id: 23, value: 1 }] },

		{ id: 31, text: "Device QA sweep",    parent: 100, start_date: "2026-07-20", duration: 4, progress: 0, resources: [{ resource_id: 12, value: 6 }, { resource_id: 22, value: 4 }] },
		{ id: 32, text: "Beta on staging",    parent: 100, start_date: "2026-07-20", duration: 3, progress: 0, resources: [{ resource_id: 11, value: 5 }, { resource_id: 23, value: 3 }] },
		{ id: 33, text: "Bug bash fixes",     parent: 100, start_date: "2026-07-23", duration: 2, progress: 0, resources: [{ resource_id: 11, value: 3 }, { resource_id: 20, value: 2 }] },
		{ id: 34, text: "Localization QA",    parent: 100, start_date: "2026-07-22", duration: 3, progress: 0, resources: [{ resource_id: 10, value: 4 }, { resource_id: 22, value: 2 }] },
		{ id: 36, text: "Release notes prep", parent: 100, start_date: "2026-07-24", duration: 2, progress: 0, resources: [{ resource_id: 12, value: 4 }, { resource_id: 20, value: 1 }] },
		{ id: 37, text: "Post-launch monitoring setup", parent: 100, start_date: "2026-07-27", duration: 2, progress: 0, resources: [{ resource_id: 11, value: 4 }, { resource_id: 20, value: 1 }] },
		{ id: 35, text: "Ship AI Search 4.0", parent: 100, start_date: "2026-07-27", type: "milestone" }
	],
	links: [
		{ id: 1, source: 11, target: 13, type: "0" },
		{ id: 2, source: 12, target: 21, type: "0" },
		{ id: 3, source: 12, target: 22, type: "0" },
		{ id: 4, source: 23, target: 31, type: "0" },
		{ id: 5, source: 31, target: 35, type: "0" },
		{ id: 6, source: 32, target: 35, type: "0" },
		{ id: 7, source: 33, target: 37, type: "0" }
	],
	resources: RESOURCES
});

/* ---------------------------------------------------------------------------
 *  LIVE CAPACITY STATUS: recompute the bottleneck as the manager reschedules
 * ------------------------------------------------------------------------ */
function currentBottleneck() {
	let worst = null;
	getResourceStore().eachItem(function (resource) {
		if (getResourceStore().hasChild(resource.id)) return;
		let over = peakLoad(resource) - ceiling(resource);
		if (over > 0 && (!worst || over > worst.over)) worst = { resource: resource, over: over, peak: peakLoad(resource) };
	});
	return worst;
}
function updateStatus() {
	let worst = currentBottleneck(), el = document.getElementById("footer_status");
	if (worst) {
		el.className = "status status--over";
		el.innerHTML = "Over capacity: <b>" + worst.resource.text + "</b>, " + worst.peak + " needed, only " + worst.resource.capacity + " in the pool";
	} else {
		el.className = "status status--ok";
		el.innerHTML = "&#10003; All resources within capacity";
	}
}

/* Keep every assignment aligned with the task while its dates are changing. */
function syncAssignmentDates(task) {
	if (!task || !isAssignable(task)) return;

	var start = new Date(task.start_date);
	var end = new Date(task.end_date);
	var duration = gantt.calculateDuration(start, end);
	var assignmentStore = gantt.getDatastore(gantt.config.resource_assignment_store);
	var assignments = gantt.getTaskAssignments(task.id);

	assignments.forEach(function (assignment) {
		assignment.start_date = new Date(start);
		assignment.end_date = new Date(end);
		assignment.delay = 0;
		assignment.duration = duration;
		assignmentStore.updateItem(assignment.id, assignment);
	});

	/* Store changes are copied back to the task's resource property. */
	gantt.updateTaskAssignments(task.id);
}

/* Refresh the assignment data and the complete lower resource panel. */
var resourceRefreshFrame = null;

function refreshResourcePanel() {
	if (resourceRefreshFrame != null) return;

	resourceRefreshFrame = requestAnimationFrame(function () {
		resourceRefreshFrame = null;

		var assignmentStore = gantt.getDatastore(gantt.config.resource_assignment_store);
		var resourceStore = gantt.getDatastore(gantt.config.resource_store);

		assignmentStore.refresh();
		resourceStore.refresh();

		["resourceGrid", "resourceTimeline"].forEach(function (viewName) {
			var view = gantt.getLayoutView(viewName);
			if (view && typeof view.render === "function") {
				view.render();
			}
		});

		updateStatus();
	});
}

gantt.attachEvent("onTaskDrag", function (id, mode, task) {
	syncAssignmentDates(task);
	refreshResourcePanel();
	return true;
});

gantt.attachEvent("onAfterTaskDrag", function (id) {
	if (gantt.isTaskExists(id)) {
		syncAssignmentDates(gantt.getTask(id));
	}
	refreshResourcePanel();
});

gantt.attachEvent("onAfterTaskUpdate", refreshResourcePanel);

updateStatus();

function toggleFocus() {
	let on = document.body.classList.toggle("focus_over");
	DHX.toggle.setById("focus_switch", on);
}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Work &amp; material resources: dhtmlxGantt</title>
	<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
	<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
	<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
	<script src="../common/demo-controls/dhx_controls.js"></script>
	<link rel="preconnect" href="https://fonts.googleapis.com">
	<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
	<style>
		body { display: flex; flex-direction: column; }
		#gantt_here { flex: 1 1 auto; min-height: 0; width: 100%; }

		.gantt_task_line { border: none; border-radius: 6px; box-shadow: 0 1px 2px rgba(16,24,40,.16); }
		.gantt_task_line .gantt_task_progress { background: rgba(0,0,0,.18); border-radius: 6px 0 0 6px; }
		.gantt_task_cell.week_end { background: rgba(15,23,42,.04); }
		.gantt_task_line.no_owner { background: #94a3b8; }

		.chip { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; margin: 0 1px; color: #fff; font: 700 10px var(--dhx-font); vertical-align: middle; }
		.pill { display: inline-flex; align-items: center; height: 18px; padding: 0 7px; margin: 0 1px; border-radius: 9px; background: #e8edf3; color: #475569; font: 600 10px var(--dhx-font); vertical-align: middle; }
		.muted { color: var(--dhx-muted); }

		.resource_marker { text-align: center; }
		.resource_marker div {
			height: 26px; min-width: 26px; margin: auto; padding: 0 2px;
			display: inline-flex; align-items: center; justify-content: center;
			border-radius: 50%; color: #fff; font: 700 11px var(--dhx-font);
		}
		.resource_marker.res_material div { border-radius: 8px; padding: 0 7px; }
		.resource_marker .cap { font-weight: 600; opacity: .78; }
		.resource_marker.workday_ok div   { background: #3aa757; }
		.resource_marker.workday_over div { background: #e5484d; }
		body.focus_over .resource_marker.workday_ok div { opacity: .22; }

		.gantt_grid_data .group_row, .group_row { font-weight: 600; }
		.gantt_grid_data .group_row { background: #f1f5fb; }

		:root[data-gantt-theme="dark"] .pill { background: #333c48; color: #c3ccd8; }
		:root[data-gantt-theme="dark"] .gantt_grid_data .group_row { background: #232a34; }
		:root[data-gantt-theme="dark"] .resource_marker.workday_ok div { background: #1d3326; color: #69d391; }
		.res_peak { font: 700 11px var(--dhx-font); color: var(--dhx-ink-2); }
		.res_peak.res_peak--over { color: #e5484d; }
		.res_kind { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
		.res_kind--work { background: #6366f1; }
		.res_kind--material { background: #0ea5e9; }

		.legend { display: inline-flex; gap: 14px; }
		.legend span { display: inline-flex; align-items: center; gap: 6px; font: 500 12px var(--dhx-font); color: var(--dhx-ink-2); }
		.legend i { width: 12px; height: 12px; border-radius: 4px; }
		.hint { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
		.hint b { color: var(--dhx-ink-2); font-weight: 600; }
		.status { font: 600 12px var(--dhx-font); }
		.status--over, .status--over b { color: #e5484d; }
		.status--ok { color: #3aa757; }
	</style>
</head>
<body>

<div class="dhx_header">
	<div class="dhx_brand dhx_brand--static">
		<span class="dhx_brand__btn">
			<span class="dhx_brand__logo" style="background:linear-gradient(140deg,#0ea5e9,#6366f1)">RP</span>
			<span class="dhx_brand__titles">
				<span class="dhx_brand__title-row"><span class="dhx_brand__title">Resource Planner</span></span>
				<span class="dhx_brand__meta"><span class="dhx_brand__subtitle">People and shared equipment in one capacity view</span></span>
			</span>
		</span>
	</div>
	<div class="dhx_header__center">
		<label class="dhx_toggle" onclick="toggleFocus()">
			<span id="focus_label">Focus bottlenecks</span><span class="dhx_toggle__track" id="focus_switch"></span>
		</label>
	</div>
	<div class="legend">
		<span><i style="background:#6366f1"></i> People: hours/day</span>
		<span><i style="background:#0ea5e9"></i> Equipment: shared pool</span>
		<span><i style="background:#e5484d"></i> Over capacity</span>
	</div>
</div>

<div id="gantt_here"></div>

<div class="dhx_footer">
	<span class="hint">Double-click a task to add, change or remove its people and equipment. The lower panel sums each day: <b>people</b> in hours vs 8h/day, <b>equipment</b> as <b>used/pool</b> units.</span>
	<div class="dhx_footer__right"><span id="footer_status"></span></div>
</div>


</body>
</html>