Job Board — Applications Tracker
A polished seeker dashboard for tracking the whole job hunt in one scannable view. Pipeline rows show each application with company logo, location and salary chips, remote badges, and color-coded status pills for applied, in review, interview, offer, and rejected. Filter by status, withdraw or re-apply, resume saved jobs into the pipeline, bookmark recommended roles, and watch a profile-completeness meter climb as you act on nudges. Built with semantic HTML, CSS variables, and vanilla JavaScript.
MCP
Code
:root {
--brand: #2563eb;
--brand-d: #1d4ed8;
--brand-50: #eaf1ff;
--ink: #0f172a;
--ink-2: #475569;
--muted: #64748b;
--bg: #f6f8fb;
--surface: #ffffff;
--line: rgba(15, 23, 42, 0.1);
--line-2: rgba(15, 23, 42, 0.18);
--ok: #16a34a;
--warn: #d97706;
--danger: #dc2626;
--new: #2563eb;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.05);
--sh-2: 0 6px 18px rgba(15, 23, 42, 0.08);
--sh-3: 0 18px 48px rgba(15, 23, 42, 0.14);
}
* { box-sizing: border-box; }
html, body { margin: 0; }
body {
font-family: "Inter", system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--ink);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: inherit; }
.skip {
position: absolute;
left: -999px;
top: 0;
background: var(--brand);
color: #fff;
padding: 10px 16px;
border-radius: 0 0 var(--r-sm) 0;
z-index: 50;
}
.skip:focus { left: 0; }
button { font-family: inherit; cursor: pointer; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--r-sm); }
/* ---------- Topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 30;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: saturate(140%) blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
max-width: 1200px;
margin: 0 auto;
padding: 12px 20px;
display: flex;
align-items: center;
gap: 22px;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
width: 36px; height: 36px;
display: grid; place-items: center;
background: var(--brand-50);
color: var(--brand-d);
border-radius: 10px;
}
.brand-name { font-weight: 800; letter-spacing: -0.02em; font-size: 1.05rem; }
.brand-name span { color: var(--brand); }
.topnav { display: flex; gap: 4px; margin-left: 8px; }
.topnav a {
text-decoration: none;
color: var(--ink-2);
font-weight: 500;
font-size: .92rem;
padding: 8px 12px;
border-radius: var(--r-sm);
}
.topnav a:hover { background: var(--brand-50); color: var(--brand-d); }
.topnav a.active { color: var(--brand-d); background: var(--brand-50); font-weight: 600; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.icon-btn {
position: relative;
width: 40px; height: 40px;
display: grid; place-items: center;
border: 1px solid var(--line);
background: var(--surface);
border-radius: 50%;
color: var(--ink-2);
}
.icon-btn:hover { border-color: var(--line-2); color: var(--ink); }
.icon-btn .dot {
position: absolute; top: 8px; right: 9px;
width: 8px; height: 8px; border-radius: 50%;
background: var(--danger); border: 2px solid var(--surface);
}
.avatar {
width: 40px; height: 40px;
display: grid; place-items: center;
border-radius: 50%;
background: linear-gradient(135deg, #2563eb, #7c3aed);
color: #fff; font-weight: 700; font-size: .85rem;
letter-spacing: .02em;
}
.avatar.lg { width: 56px; height: 56px; font-size: 1.05rem; border-radius: 16px; }
/* ---------- Layout ---------- */
.layout {
max-width: 1200px;
margin: 0 auto;
padding: 28px 20px 64px;
display: grid;
grid-template-columns: 320px 1fr;
gap: 24px;
align-items: start;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-1);
padding: 18px;
}
.sidebar { display: grid; gap: 18px; }
.sidebar .card { padding: 16px; }
.card-head {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 12px;
}
.card-head h3 { margin: 0; font-size: .95rem; font-weight: 700; }
.count {
background: var(--brand-50); color: var(--brand-d);
font-size: .75rem; font-weight: 700;
padding: 2px 9px; border-radius: 999px;
}
.badge-soft {
font-size: .7rem; font-weight: 700; text-transform: uppercase;
letter-spacing: .04em;
background: #f0fdf4; color: var(--ok);
padding: 3px 8px; border-radius: 999px;
}
/* ---------- Profile ---------- */
.profile-head { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
.profile-name { margin: 0; font-size: 1.05rem; font-weight: 700; letter-spacing: -0.01em; }
.profile-role { margin: 2px 0 0; color: var(--muted); font-size: .85rem; }
.completeness-top {
display: flex; justify-content: space-between; align-items: baseline;
font-size: .82rem; color: var(--ink-2); margin-bottom: 7px;
}
.completeness-top strong { font-size: 1rem; color: var(--ink); }
.bar {
height: 8px; background: #eef2f7; border-radius: 999px; overflow: hidden;
}
.bar span {
display: block; height: 100%;
background: linear-gradient(90deg, var(--brand), #60a5fa);
border-radius: 999px;
transition: width .5s cubic-bezier(.22,.61,.36,1);
}
.nudge { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 6px; }
.nudge button {
width: 100%;
display: flex; justify-content: space-between; align-items: center;
background: #fff; border: 1px dashed var(--line-2);
border-radius: var(--r-sm);
padding: 9px 11px;
font-size: .82rem; color: var(--ink-2); font-weight: 500;
transition: .15s;
}
.nudge button:hover { border-color: var(--brand); color: var(--brand-d); background: var(--brand-50); }
.nudge button span { color: var(--ok); font-weight: 700; font-size: .75rem; }
.nudge li.done button { opacity: .5; text-decoration: line-through; border-style: solid; cursor: default; }
/* ---------- Saved & recommended ---------- */
.saved-list, .rec-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.mini {
display: grid; gap: 4px;
padding: 11px;
border: 1px solid var(--line);
border-radius: var(--r-sm);
transition: .15s;
}
.mini:hover { border-color: var(--line-2); box-shadow: var(--sh-1); }
.mini-top { display: flex; align-items: center; gap: 8px; }
.logo {
width: 30px; height: 30px; flex: 0 0 auto;
border-radius: 8px; display: grid; place-items: center;
font-weight: 800; font-size: .72rem; color: #fff;
}
.mini-title { font-weight: 600; font-size: .85rem; line-height: 1.25; }
.mini-co { color: var(--muted); font-size: .76rem; }
.mini-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.chip {
font-size: .7rem; font-weight: 600;
background: #f1f5f9; color: var(--ink-2);
padding: 2px 7px; border-radius: 999px;
display: inline-flex; align-items: center; gap: 4px;
}
.chip.remote { background: var(--brand-50); color: var(--brand-d); }
.chip.salary { background: #f0fdf4; color: var(--ok); }
.mini-actions { display: flex; gap: 8px; margin-top: 6px; }
.btn {
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--ink);
font-weight: 600; font-size: .78rem;
padding: 6px 11px; border-radius: var(--r-sm);
transition: .15s;
}
.btn:hover { border-color: var(--brand); color: var(--brand-d); background: var(--brand-50); }
.btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn.primary:hover { background: var(--brand-d); border-color: var(--brand-d); }
.btn.ghost { border-color: transparent; background: transparent; color: var(--muted); padding: 6px 8px; }
.btn.ghost:hover { color: var(--danger); background: #fef2f2; }
.btn.sm { font-size: .74rem; padding: 5px 9px; }
.bookmark {
margin-left: auto;
width: 30px; height: 30px; border-radius: 8px;
border: 1px solid var(--line);
background: var(--surface);
display: grid; place-items: center; color: var(--muted);
}
.bookmark[aria-pressed="true"] { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.bookmark:hover { border-color: var(--line-2); }
/* ---------- Content head ---------- */
.content-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 18px; flex-wrap: wrap; margin-bottom: 18px;
}
.content-head h1 { margin: 0; font-size: 1.5rem; letter-spacing: -0.02em; }
.subtitle { margin: 4px 0 0; color: var(--muted); font-size: .9rem; }
.stat-strip { display: flex; gap: 10px; flex-wrap: wrap; }
.stat {
background: var(--surface); border: 1px solid var(--line);
border-radius: var(--r-md); padding: 8px 14px; box-shadow: var(--sh-1);
min-width: 64px;
}
.stat b { display: block; font-size: 1.25rem; line-height: 1.1; }
.stat span { font-size: .72rem; color: var(--muted); font-weight: 600; }
/* ---------- Filters ---------- */
.filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.fbtn {
border: 1px solid var(--line);
background: var(--surface);
color: var(--ink-2);
font-weight: 600; font-size: .84rem;
padding: 8px 13px; border-radius: 999px;
display: inline-flex; align-items: center; gap: 7px;
transition: .15s;
}
.fbtn:hover { border-color: var(--line-2); color: var(--ink); }
.fbtn[aria-selected="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.fbtn .pill-n {
font-size: .72rem; background: rgba(15,23,42,0.08); color: var(--ink-2);
padding: 1px 7px; border-radius: 999px; font-weight: 700;
}
.fbtn[aria-selected="true"] .pill-n { background: rgba(255,255,255,0.22); color: #fff; }
/* ---------- Application cards ---------- */
.apps { display: grid; gap: 12px; }
.app {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-1);
padding: 16px 18px;
display: grid;
grid-template-columns: 46px 1fr auto;
gap: 14px;
align-items: start;
transition: box-shadow .18s, transform .18s, border-color .18s;
}
.app:hover { box-shadow: var(--sh-2); border-color: var(--line-2); }
.app.withdrawn { opacity: .55; }
.app.withdrawn .app-main { text-decoration: line-through; text-decoration-color: var(--line-2); }
.app .logo { width: 46px; height: 46px; border-radius: 12px; font-size: .9rem; }
.app-main { min-width: 0; }
.app-title { margin: 0; font-size: 1.02rem; font-weight: 700; letter-spacing: -0.01em; }
.app-co { color: var(--ink-2); font-size: .88rem; font-weight: 500; }
.app-meta { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 9px; }
.app-sub { margin-top: 9px; font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.app-sub .dotsep { width: 3px; height: 3px; border-radius: 50%; background: var(--line-2); }
.app-side { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
.status {
font-size: .76rem; font-weight: 700;
padding: 4px 11px; border-radius: 999px;
display: inline-flex; align-items: center; gap: 6px;
white-space: nowrap;
}
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.status.applied { background: var(--brand-50); color: var(--brand-d); }
.status.review { background: #fff7ed; color: var(--warn); }
.status.interview { background: #f5f3ff; color: #7c3aed; }
.status.offer { background: #f0fdf4; color: var(--ok); }
.status.rejected { background: #fef2f2; color: var(--danger); }
.app-actions { display: flex; gap: 6px; }
.empty {
text-align: center; color: var(--muted);
padding: 40px; background: var(--surface);
border: 1px dashed var(--line-2); border-radius: var(--r-md);
font-size: .92rem;
}
/* ---------- Toast ---------- */
.toast-wrap {
position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
display: grid; gap: 8px; z-index: 60; width: max-content; max-width: 90vw;
}
.toast {
background: var(--ink); color: #fff;
padding: 11px 16px; border-radius: var(--r-sm);
box-shadow: var(--sh-3);
font-size: .86rem; font-weight: 500;
display: flex; align-items: center; gap: 9px;
animation: toastIn .25s ease;
}
.toast.ok { background: #064e3b; }
.toast.warn { background: #7c2d12; }
.toast svg { flex: 0 0 auto; }
.toast.hide { animation: toastOut .25s ease forwards; }
@keyframes toastIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(12px); } }
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
.sidebar { order: 2; }
}
@media (max-width: 520px) {
.topnav { display: none; }
.topbar-inner { padding: 10px 14px; gap: 12px; }
.layout { padding: 18px 14px 48px; }
.content-head h1 { font-size: 1.3rem; }
.stat-strip { width: 100%; }
.stat { flex: 1; min-width: 0; }
.app {
grid-template-columns: 40px 1fr;
grid-template-areas: "logo main" "side side";
}
.app .logo { grid-area: logo; }
.app-main { grid-area: main; }
.app-side { grid-area: side; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; width: 100%; }
}(function () {
"use strict";
// ---------- Data (fictional) ----------
var STATUS = {
applied: { label: "Applied", cls: "applied" },
review: { label: "In review", cls: "review" },
interview: { label: "Interview", cls: "interview" },
offer: { label: "Offer", cls: "offer" },
rejected: { label: "Rejected", cls: "rejected" }
};
function logoColor(seed) {
var palette = ["#2563eb", "#7c3aed", "#0891b2", "#db2777", "#ea580c", "#16a34a", "#475569"];
var h = 0;
for (var i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) >>> 0;
return palette[h % palette.length];
}
function initials(name) {
return name.split(/\s+/).slice(0, 2).map(function (w) { return w[0]; }).join("").toUpperCase();
}
var apps = [
{ id: "a1", role: "Senior Frontend Engineer", company: "Helio Labs", location: "Berlin, DE", remote: true, salary: "€85k–105k", status: "interview", when: "Applied 9 days ago", note: "Round 2 · Tue 3:00pm" },
{ id: "a2", role: "UI Engineer, Design Systems", company: "Mapleview", location: "Remote (EU)", remote: true, salary: "€70k–90k", status: "review", when: "Applied 4 days ago", note: "Recruiter viewed your profile" },
{ id: "a3", role: "Lead Product Engineer", company: "Quill & Co.", location: "London, UK", remote: false, salary: "£90k–110k", status: "offer", when: "Applied 21 days ago", note: "Offer expires in 5 days" },
{ id: "a4", role: "Frontend Developer", company: "Brightside", location: "Amsterdam, NL", remote: true, salary: "€60k–78k", status: "applied", when: "Applied 2 days ago", note: "Application received" },
{ id: "a5", role: "React Engineer", company: "Tidepool", location: "Remote (Global)", remote: true, salary: "$95k–120k", status: "rejected", when: "Applied 18 days ago", note: "Position filled internally" },
{ id: "a6", role: "Staff Web Engineer", company: "Northstar", location: "Munich, DE", remote: false, salary: "€100k–125k", status: "review", when: "Applied 6 days ago", note: "Take-home in progress" }
];
var saved = [
{ id: "s1", role: "Frontend Architect", company: "Lumen Studio", location: "Remote (EU)", remote: true, salary: "€95k+" },
{ id: "s2", role: "Senior React Engineer", company: "Cobalt", location: "Lisbon, PT", remote: true, salary: "€72k–88k" },
{ id: "s3", role: "Design Engineer", company: "Foxglove", location: "Paris, FR", remote: false, salary: "€68k–82k" }
];
var recommended = [
{ id: "r1", role: "Principal Frontend Engineer", company: "Aerial", location: "Remote", remote: true, salary: "€110k+", match: "94% match" },
{ id: "r2", role: "UI Platform Engineer", company: "Vellum", location: "Rotterdam, NL", remote: true, salary: "€80k–96k", match: "89% match" }
];
var withdrawn = {};
var activeFilter = "all";
// ---------- Toast ----------
var toastWrap = document.getElementById("toastWrap");
function toast(msg, kind) {
var el = document.createElement("div");
el.className = "toast" + (kind ? " " + kind : "");
var ic = kind === "warn"
? '<svg viewBox="0 0 24 24" width="16" height="16" fill="none"><path d="M12 9v4m0 4h.01M10.3 4.3 2.7 18a2 2 0 0 0 1.7 3h15.2a2 2 0 0 0 1.7-3L13.7 4.3a2 2 0 0 0-3.4 0Z" stroke="#fff" stroke-width="1.8"/></svg>'
: '<svg viewBox="0 0 24 24" width="16" height="16" fill="none"><path d="m5 13 4 4L19 7" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
el.innerHTML = ic + "<span></span>";
el.querySelector("span").textContent = msg;
toastWrap.appendChild(el);
setTimeout(function () {
el.classList.add("hide");
setTimeout(function () { el.remove(); }, 250);
}, 2600);
}
// ---------- Logo helper ----------
function logoEl(company, sizeClass) {
return '<span class="logo' + (sizeClass ? " " + sizeClass : "") +
'" style="background:' + logoColor(company) + '">' + initials(company) + "</span>";
}
function chips(item) {
var out = '<span class="chip">' +
'<svg viewBox="0 0 24 24" width="11" height="11" fill="none"><path d="M12 21s-7-5.7-7-11a7 7 0 0 1 14 0c0 5.3-7 11-7 11Z" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="10" r="2.4" stroke="currentColor" stroke-width="1.8"/></svg>' +
item.location + "</span>";
if (item.remote) out += '<span class="chip remote">Remote</span>';
if (item.salary) out += '<span class="chip salary">' + item.salary + "</span>";
return out;
}
// ---------- Render application list ----------
var appList = document.getElementById("appList");
var emptyState = document.getElementById("emptyState");
function renderApps() {
appList.innerHTML = "";
var visible = apps.filter(function (a) {
var st = withdrawn[a.id] ? "withdrawn" : a.status;
if (activeFilter === "all") return true;
return st === activeFilter;
});
emptyState.hidden = visible.length > 0;
visible.forEach(function (a) {
var isW = !!withdrawn[a.id];
var st = STATUS[a.status];
var card = document.createElement("article");
card.className = "app" + (isW ? " withdrawn" : "");
var statusHtml = isW
? '<span class="status rejected">Withdrawn</span>'
: '<span class="status ' + st.cls + '">' + st.label + "</span>";
var actions = isW
? '<button class="btn sm" type="button" data-act="reapply" data-id="' + a.id + '">Re-apply</button>'
: '<button class="btn sm ghost" type="button" data-act="withdraw" data-id="' + a.id + '">Withdraw</button>';
card.innerHTML =
logoEl(a.company) +
'<div class="app-main">' +
'<h3 class="app-title">' + a.role + "</h3>" +
'<span class="app-co">' + a.company + "</span>" +
'<div class="app-meta">' + chips(a) + "</div>" +
'<div class="app-sub"><span>' + a.when + "</span>" +
'<span class="dotsep" aria-hidden="true"></span><span>' + a.note + "</span></div>" +
"</div>" +
'<div class="app-side">' + statusHtml +
'<div class="app-actions">' + actions + "</div>" +
"</div>";
appList.appendChild(card);
});
}
appList.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-act]");
if (!btn) return;
var id = btn.getAttribute("data-id");
var app = apps.find(function (a) { return a.id === id; });
if (!app) return;
if (btn.getAttribute("data-act") === "withdraw") {
withdrawn[id] = true;
toast("Withdrew application to " + app.company, "warn");
} else {
delete withdrawn[id];
toast("Re-applied to " + app.company, "ok");
}
renderApps();
renderFilters();
renderStats();
});
// ---------- Filters ----------
var filterBar = document.getElementById("filterBar");
var FILTERS = [
{ key: "all", label: "All" },
{ key: "applied", label: "Applied" },
{ key: "review", label: "In review" },
{ key: "interview", label: "Interview" },
{ key: "offer", label: "Offer" },
{ key: "rejected", label: "Rejected" },
{ key: "withdrawn", label: "Withdrawn" }
];
function countFor(key) {
return apps.filter(function (a) {
var st = withdrawn[a.id] ? "withdrawn" : a.status;
if (key === "all") return true;
return st === key;
}).length;
}
function renderFilters() {
filterBar.innerHTML = "";
FILTERS.forEach(function (f) {
var n = countFor(f.key);
if (f.key === "withdrawn" && n === 0) return;
var b = document.createElement("button");
b.className = "fbtn";
b.type = "button";
b.setAttribute("role", "tab");
b.setAttribute("aria-selected", String(activeFilter === f.key));
b.innerHTML = f.label + ' <span class="pill-n">' + n + "</span>";
b.addEventListener("click", function () {
activeFilter = f.key;
renderApps();
renderFilters();
});
filterBar.appendChild(b);
});
}
// ---------- Stats ----------
var statStrip = document.getElementById("statStrip");
var totalApps = document.getElementById("totalApps");
function renderStats() {
var active = apps.filter(function (a) { return !withdrawn[a.id]; }).length;
totalApps.textContent = active;
var data = [
{ n: countFor("interview"), l: "Interviews" },
{ n: countFor("offer"), l: "Offers" },
{ n: countFor("review"), l: "In review" }
];
statStrip.innerHTML = data.map(function (d) {
return '<div class="stat"><b>' + d.n + "</b><span>" + d.l + "</span></div>";
}).join("");
}
// ---------- Saved jobs ----------
var savedList = document.getElementById("savedList");
var savedCount = document.getElementById("savedCount");
function renderSaved() {
savedList.innerHTML = "";
savedCount.textContent = saved.length;
saved.forEach(function (s) {
var li = document.createElement("li");
li.className = "mini";
li.innerHTML =
'<div class="mini-top">' + logoEl(s.company) +
'<div><div class="mini-title">' + s.role + '</div><div class="mini-co">' + s.company + "</div></div>" +
'<button class="bookmark" type="button" aria-pressed="true" aria-label="Remove from saved" data-id="' + s.id + '">' +
'<svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1Z"/></svg>' +
"</button></div>" +
'<div class="mini-meta">' + chips(s) + "</div>" +
'<div class="mini-actions">' +
'<button class="btn primary sm" type="button" data-resume="' + s.id + '">Resume application</button>' +
"</div>";
savedList.appendChild(li);
});
}
savedList.addEventListener("click", function (e) {
var bm = e.target.closest(".bookmark");
if (bm) {
var rid = bm.getAttribute("data-id");
saved = saved.filter(function (s) { return s.id !== rid; });
renderSaved();
toast("Removed from saved jobs", "warn");
return;
}
var rb = e.target.closest("button[data-resume]");
if (rb) {
var sid = rb.getAttribute("data-resume");
var job = saved.find(function (s) { return s.id === sid; });
if (!job) return;
// Move saved -> applications as a fresh "applied"
apps.unshift({
id: "n" + Date.now(),
role: job.role, company: job.company, location: job.location,
remote: job.remote, salary: job.salary, status: "applied",
when: "Applied just now", note: "Resumed from saved jobs"
});
saved = saved.filter(function (s) { return s.id !== sid; });
renderSaved(); renderApps(); renderFilters(); renderStats();
activeFilter = "all";
renderFilters();
toast("Application submitted to " + job.company, "ok");
}
});
// ---------- Recommended ----------
var recList = document.getElementById("recList");
function renderRec() {
recList.innerHTML = "";
recommended.forEach(function (r) {
var li = document.createElement("li");
li.className = "mini";
li.innerHTML =
'<div class="mini-top">' + logoEl(r.company) +
'<div><div class="mini-title">' + r.role + '</div><div class="mini-co">' + r.company + "</div></div>" +
'<button class="bookmark" type="button" aria-pressed="false" aria-label="Save job" data-rec="' + r.id + '">' +
'<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1Z"/></svg>' +
"</button></div>" +
'<div class="mini-meta"><span class="chip salary">' + r.match + "</span>" + chips(r) + "</div>";
recList.appendChild(li);
});
}
recList.addEventListener("click", function (e) {
var bm = e.target.closest(".bookmark");
if (!bm) return;
var pressed = bm.getAttribute("aria-pressed") === "true";
if (pressed) return;
var rid = bm.getAttribute("data-rec");
var rec = recommended.find(function (r) { return r.id === rid; });
if (!rec) return;
bm.setAttribute("aria-pressed", "true");
bm.setAttribute("aria-label", "Saved");
bm.querySelector("svg").setAttribute("fill", "currentColor");
bm.querySelector("svg").removeAttribute("stroke");
saved.push({ id: "sv" + Date.now(), role: rec.role, company: rec.company, location: rec.location, remote: rec.remote, salary: rec.salary });
renderSaved();
toast("Saved " + rec.role, "ok");
});
// ---------- Profile completeness nudges ----------
var pct = 78;
var pctBar = document.getElementById("pctBar");
var pctLabel = document.getElementById("pctLabel");
var bar = pctBar.parentElement;
var nudgeList = document.getElementById("nudgeList");
var GAIN = { portfolio: 8, skills: 9, photo: 5 };
nudgeList.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-nudge]");
if (!btn) return;
var li = btn.parentElement;
if (li.classList.contains("done")) return;
var key = btn.getAttribute("data-nudge");
pct = Math.min(100, pct + (GAIN[key] || 0));
li.classList.add("done");
pctBar.style.width = pct + "%";
pctLabel.textContent = pct + "%";
bar.setAttribute("aria-valuenow", String(pct));
toast("Profile updated · " + pct + "% complete", "ok");
});
// ---------- Init ----------
renderApps();
renderFilters();
renderStats();
renderSaved();
renderRec();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Job Board — Applications Tracker</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="topbar">
<div class="topbar-inner">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none"><path d="M4 8h16v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8Z" stroke="currentColor" stroke-width="1.8"/><path d="M9 8V6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" stroke="currentColor" stroke-width="1.8"/><path d="M4 13h16" stroke="currentColor" stroke-width="1.8"/></svg>
</span>
<span class="brand-name">Northwind<span>Jobs</span></span>
</div>
<nav class="topnav" aria-label="Primary">
<a href="#" class="active">Dashboard</a>
<a href="#">Search</a>
<a href="#">Messages</a>
</nav>
<div class="topbar-right">
<button class="icon-btn" type="button" aria-label="Notifications">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none"><path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6Z" stroke="currentColor" stroke-width="1.8"/><path d="M10 20a2 2 0 0 0 4 0" stroke="currentColor" stroke-width="1.8"/></svg>
<span class="dot" aria-hidden="true"></span>
</button>
<div class="avatar" title="Maya Okonkwo">MO</div>
</div>
</div>
</header>
<main id="main" class="layout">
<!-- LEFT: profile + saved + recommended -->
<aside class="sidebar" aria-label="Profile and recommendations">
<section class="card profile-card">
<div class="profile-head">
<div class="avatar lg">MO</div>
<div>
<h2 class="profile-name">Maya Okonkwo</h2>
<p class="profile-role">Senior Frontend Engineer</p>
</div>
</div>
<div class="completeness">
<div class="completeness-top">
<span>Profile completeness</span>
<strong id="pctLabel">78%</strong>
</div>
<div class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="78" aria-label="Profile completeness">
<span id="pctBar" style="width:78%"></span>
</div>
<ul class="nudge" id="nudgeList">
<li><button type="button" data-nudge="portfolio">Add a portfolio link <span>+8%</span></button></li>
<li><button type="button" data-nudge="skills">List 3 more skills <span>+9%</span></button></li>
<li><button type="button" data-nudge="photo">Upload a profile photo <span>+5%</span></button></li>
</ul>
</div>
</section>
<section class="card">
<div class="card-head">
<h3>Saved jobs</h3>
<span class="count" id="savedCount">3</span>
</div>
<ul class="saved-list" id="savedList"></ul>
</section>
<section class="card">
<div class="card-head">
<h3>Recommended</h3>
<span class="badge-soft">For you</span>
</div>
<ul class="rec-list" id="recList"></ul>
</section>
</aside>
<!-- RIGHT: applications tracker -->
<section class="content" aria-label="Applications tracker">
<div class="content-head">
<div>
<h1>Applications tracker</h1>
<p class="subtitle"><span id="totalApps">0</span> active applications · last updated today</p>
</div>
<div class="stat-strip" id="statStrip"></div>
</div>
<div class="filters" role="tablist" aria-label="Filter applications by status" id="filterBar"></div>
<div class="apps" id="appList"></div>
<p class="empty" id="emptyState" hidden>No applications match this filter.</p>
</section>
</main>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Applications Tracker
A seeker-side job board dashboard that brings the entire hunt into one trustworthy, scannable surface. The main column is an applications pipeline where each role renders as a dense row: a generated company logo, location and salary chips, a remote badge, and a color-coded status pill spanning applied, in review, interview, offer, and rejected. A filter bar with live counts lets you narrow to any status, and a stat strip summarizes interviews, offers, and roles still in review.
Every row is interactive. Withdraw an application and it greys out with a re-apply action; re-apply and it returns to the pipeline. The left rail holds saved jobs you can resume — promoting them straight into the tracker as a fresh “applied just now” — plus recommended roles with match scores and a bookmark toggle that drops them into your saved list. A profile-completeness meter sits up top: acting on each nudge (portfolio link, more skills, a photo) animates the bar upward and fires a confirmation toast.
The layout is responsive down to roughly 360px, collapsing the sidebar below the tracker and reflowing each row, with WCAG AA contrast, focus-visible outlines, ARIA roles on the filter tabs and progress bar, and a small reusable toast() helper. No frameworks, no build step — just semantic HTML, CSS custom properties, and vanilla JavaScript.
Illustrative UI only — fictional jobs & companies, not a real hiring platform.