Job Board — Job Search
A polished job search page with a sticky filter rail for role, location, remote, salary range, type and experience, a live search bar, and a sortable list of scannable job cards. Each card shows a company logo, title, salary and location chips, tags, posted date, plus a save toggle. Results count, relevance and salary sorting, and pagination all update instantly with vanilla JavaScript and no dependencies.
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-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
--sh-md: 0 6px 20px rgba(15, 23, 42, 0.08);
--sh-lg: 0 14px 40px rgba(15, 23, 42, 0.12);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
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;
}
button { font-family: inherit; cursor: pointer; }
/* ---------- top bar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
background: rgba(255, 255, 255, 0.86);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
max-width: 1180px;
margin: 0 auto;
padding: 14px 22px;
display: flex;
align-items: center;
gap: 22px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
text-decoration: none;
color: var(--ink);
font-weight: 800;
letter-spacing: -0.02em;
}
.brand__mark {
width: 32px;
height: 32px;
border-radius: 9px;
background: linear-gradient(135deg, var(--brand), #4f46e5);
color: #fff;
display: grid;
place-items: center;
font-size: 18px;
box-shadow: var(--sh-sm);
}
.brand__name span { color: var(--brand); }
.topnav { display: flex; gap: 4px; margin-left: 8px; }
.topnav__link {
text-decoration: none;
color: var(--ink-2);
font-weight: 500;
font-size: 14px;
padding: 8px 12px;
border-radius: var(--r-sm);
}
.topnav__link:hover { background: var(--brand-50); color: var(--brand-d); }
.topnav__link.is-active { color: var(--brand-d); font-weight: 600; }
.topbar__right { margin-left: auto; display: flex; gap: 10px; }
.btn {
border: 1px solid transparent;
border-radius: var(--r-sm);
font-weight: 600;
font-size: 14px;
padding: 9px 16px;
transition: background .15s, border-color .15s, transform .05s, box-shadow .15s;
}
.btn:active { transform: translateY(1px); }
.btn--ghost { background: transparent; border-color: var(--line-2); color: var(--ink); }
.btn--ghost:hover { background: var(--surface); border-color: var(--line); box-shadow: var(--sh-sm); }
.btn--solid { background: var(--brand); color: #fff; box-shadow: var(--sh-sm); }
.btn--solid:hover { background: var(--brand-d); }
/* ---------- layout ---------- */
.layout {
max-width: 1180px;
margin: 0 auto;
padding: 26px 22px 60px;
display: grid;
grid-template-columns: 272px 1fr;
gap: 26px;
align-items: start;
}
/* ---------- filter rail ---------- */
.rail {
position: sticky;
top: 84px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-sm);
padding: 18px;
}
.rail__head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.rail__title { font-size: 16px; font-weight: 700; margin: 0; }
.rail__clear {
background: none; border: none; color: var(--brand);
font-size: 13px; font-weight: 600; padding: 4px;
}
.rail__clear:hover { text-decoration: underline; }
.rail__form { display: flex; flex-direction: column; }
.filter {
border: none;
margin: 0;
padding: 14px 0;
border-top: 1px solid var(--line);
}
.filter__legend {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
padding: 0;
margin-bottom: 10px;
}
.select-wrap { position: relative; }
.select-wrap::after {
content: "";
position: absolute;
right: 12px; top: 50%;
width: 8px; height: 8px;
border-right: 2px solid var(--muted);
border-bottom: 2px solid var(--muted);
transform: translateY(-65%) rotate(45deg);
pointer-events: none;
}
.select {
width: 100%;
appearance: none;
-webkit-appearance: none;
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 9px 30px 9px 12px;
font-size: 14px;
color: var(--ink);
font-family: inherit;
}
.select:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand); }
.select-wrap--sm .select { padding-block: 7px; font-size: 13px; }
/* toggle switch */
.switch {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-size: 14px;
color: var(--ink-2);
}
.switch input { position: absolute; opacity: 0; pointer-events: none; }
.switch__track {
width: 40px; height: 23px;
border-radius: 999px;
background: var(--line-2);
display: inline-flex;
align-items: center;
padding: 2px;
transition: background .18s;
flex: none;
}
.switch__thumb {
width: 19px; height: 19px;
border-radius: 50%;
background: #fff;
box-shadow: var(--sh-sm);
transition: transform .18s;
}
.switch input:checked + .switch__track { background: var(--brand); }
.switch input:checked + .switch__track .switch__thumb { transform: translateX(17px); }
.switch input:focus-visible + .switch__track { outline: 2px solid var(--brand); outline-offset: 2px; }
/* range */
.range { display: flex; flex-direction: column; gap: 8px; }
.range input[type="range"] { width: 100%; accent-color: var(--brand); }
.range__out {
font-size: 13px;
font-weight: 600;
color: var(--ink);
}
/* chips */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { position: relative; }
.chip input { position: absolute; opacity: 0; pointer-events: none; }
.chip span {
display: inline-block;
padding: 6px 11px;
font-size: 13px;
font-weight: 500;
border-radius: 999px;
border: 1px solid var(--line-2);
color: var(--ink-2);
background: var(--surface);
transition: background .15s, border-color .15s, color .15s;
}
.chip span:hover { border-color: var(--brand); color: var(--brand-d); }
.chip input:checked + span {
background: var(--brand-50);
border-color: var(--brand);
color: var(--brand-d);
}
.chip input:focus-visible + span { outline: 2px solid var(--brand); outline-offset: 2px; }
/* ---------- results ---------- */
.searchbar { display: flex; gap: 10px; margin-bottom: 16px; }
.searchbar__field {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 0 14px;
box-shadow: var(--sh-sm);
}
.searchbar__field:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-50); }
.searchbar__icon { width: 20px; height: 20px; color: var(--muted); flex: none; }
.searchbar__input {
flex: 1;
border: none;
background: none;
padding: 13px 0;
font-size: 15px;
font-family: inherit;
color: var(--ink);
}
.searchbar__input:focus { outline: none; }
.searchbar__btn { padding-inline: 22px; border-radius: var(--r-md); }
.resultsbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.resultsbar__count { margin: 0; font-size: 14px; color: var(--ink-2); }
.resultsbar__count strong { color: var(--ink); font-weight: 700; }
.sortwrap { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
/* job cards */
.joblist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.jobcard {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 18px;
box-shadow: var(--sh-sm);
display: grid;
grid-template-columns: 52px 1fr auto;
gap: 14px;
transition: border-color .15s, box-shadow .15s, transform .12s;
animation: rise .35s ease both;
}
.jobcard:hover { border-color: var(--line-2); box-shadow: var(--sh-md); transform: translateY(-2px); }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }
.logo {
width: 52px; height: 52px;
border-radius: 13px;
display: grid;
place-items: center;
font-weight: 800;
font-size: 20px;
color: #fff;
letter-spacing: -0.03em;
}
.jobcard__body { min-width: 0; }
.jobcard__top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.jobcard__title {
margin: 0;
font-size: 16px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--ink);
}
.jobcard__company { font-size: 14px; color: var(--ink-2); font-weight: 500; margin: 2px 0 0; }
.jobcard__company b { color: var(--ink); font-weight: 600; }
.badge {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 3px 8px;
border-radius: 999px;
line-height: 1.4;
}
.badge--new { background: var(--brand-50); color: var(--brand-d); }
.badge--remote { background: #e7f8ef; color: var(--ok); }
.jobcard__meta { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 11px; }
.metachip {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 12.5px;
font-weight: 500;
color: var(--ink-2);
background: var(--bg);
border: 1px solid var(--line);
border-radius: 999px;
padding: 4px 10px;
}
.metachip--salary { color: var(--ok); font-weight: 600; }
.metachip__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .6; }
.jobcard__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.tag {
font-size: 12px;
font-weight: 500;
color: var(--ink-2);
background: var(--surface);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 3px 9px;
}
.jobcard__side {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
gap: 10px;
}
.jobcard__posted { font-size: 12px; color: var(--muted); white-space: nowrap; }
.save {
width: 38px; height: 38px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--muted);
display: grid;
place-items: center;
transition: background .15s, color .15s, border-color .15s, transform .12s;
}
.save:hover { border-color: var(--brand); color: var(--brand); }
.save svg { width: 19px; height: 19px; }
.save.is-saved { background: var(--brand); border-color: var(--brand); color: #fff; }
.save.is-saved svg { fill: currentColor; }
.save.is-saved { animation: pop .25s ease; }
@keyframes pop { 50% { transform: scale(1.18); } }
/* empty + pager */
.empty {
text-align: center;
padding: 60px 20px;
background: var(--surface);
border: 1px dashed var(--line-2);
border-radius: var(--r-lg);
}
.empty__icon { font-size: 42px; color: var(--muted); line-height: 1; }
.empty h3 { margin: 14px 0 6px; font-size: 18px; }
.empty p { margin: 0 0 16px; color: var(--ink-2); }
.pager { display: flex; justify-content: center; gap: 6px; margin-top: 22px; flex-wrap: wrap; }
.pager:empty { display: none; }
.pager button {
min-width: 38px;
height: 38px;
padding: 0 12px;
border-radius: var(--r-sm);
border: 1px solid var(--line-2);
background: var(--surface);
color: var(--ink-2);
font-weight: 600;
font-size: 14px;
}
.pager button:hover:not(:disabled) { border-color: var(--brand); color: var(--brand-d); }
.pager button.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }
.pager button:disabled { opacity: .45; cursor: not-allowed; }
/* toast */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 20px);
background: var(--ink);
color: #fff;
font-size: 14px;
font-weight: 500;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--sh-lg);
opacity: 0;
pointer-events: none;
transition: opacity .22s, transform .22s;
z-index: 50;
}
.toast.is-show { opacity: 1; transform: translate(-50%, 0); }
/* ---------- responsive ---------- */
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
.rail { position: static; }
.topnav { display: none; }
}
@media (max-width: 520px) {
.topbar__inner { padding: 12px 16px; gap: 12px; }
.topbar__right .btn--ghost { display: none; }
.layout { padding: 18px 16px 50px; }
.jobcard { grid-template-columns: 44px 1fr; }
.logo { width: 44px; height: 44px; font-size: 17px; border-radius: 11px; }
.jobcard__side {
grid-column: 1 / -1;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-top: 1px solid var(--line);
padding-top: 12px;
}
.searchbar__btn { padding-inline: 16px; }
}(function () {
"use strict";
// ---- fictional data ----
var JOBS = [
{ id: 1, title: "Senior Frontend Engineer", company: "Lumen Labs", logo: "L", color: "#2563eb", role: "Engineering", location: "Berlin", remote: true, salaryMin: 95, salaryMax: 125, type: "Full-time", level: "Senior", posted: 1, tags: ["React", "TypeScript", "Design Systems"] },
{ id: 2, title: "Product Designer", company: "Fernweh", logo: "F", color: "#9333ea", role: "Design", location: "Lisbon", remote: true, salaryMin: 70, salaryMax: 90, type: "Full-time", level: "Mid", posted: 2, tags: ["Figma", "UX", "Prototyping"] },
{ id: 3, title: "Data Analyst", company: "Brightwater", logo: "B", color: "#0891b2", role: "Data", location: "London", remote: false, salaryMin: 55, salaryMax: 72, type: "Full-time", level: "Junior", posted: 0, tags: ["SQL", "Python", "Tableau"] },
{ id: 4, title: "Engineering Lead", company: "Northwind", logo: "N", color: "#16a34a", role: "Engineering", location: "Austin", remote: true, salaryMin: 150, salaryMax: 190, type: "Full-time", level: "Lead", posted: 5, tags: ["Go", "AWS", "Leadership"] },
{ id: 5, title: "Growth Marketing Manager", company: "Pixelpush", logo: "P", color: "#db2777", role: "Marketing", location: "Toronto", remote: false, salaryMin: 78, salaryMax: 98, type: "Full-time", level: "Mid", posted: 3, tags: ["SEO", "Lifecycle", "Analytics"] },
{ id: 6, title: "Product Manager, Payments", company: "Cohort", logo: "C", color: "#ea580c", role: "Product", location: "London", remote: true, salaryMin: 110, salaryMax: 140, type: "Full-time", level: "Senior", posted: 1, tags: ["Fintech", "Roadmaps", "B2B"] },
{ id: 7, title: "Backend Engineer (Contract)", company: "Meridian", logo: "M", color: "#4f46e5", role: "Engineering", location: "Berlin", remote: true, salaryMin: 80, salaryMax: 110, type: "Contract", level: "Mid", posted: 6, tags: ["Node.js", "Postgres", "GraphQL"] },
{ id: 8, title: "UX Research Intern", company: "Fernweh", logo: "F", color: "#9333ea", role: "Design", location: "Lisbon", remote: false, salaryMin: 40, salaryMax: 48, type: "Internship", level: "Junior", posted: 4, tags: ["Interviews", "Surveys"] },
{ id: 9, title: "Machine Learning Engineer", company: "Brightwater", logo: "B", color: "#0891b2", role: "Data", location: "Toronto", remote: true, salaryMin: 120, salaryMax: 160, type: "Full-time", level: "Senior", posted: 2, tags: ["PyTorch", "MLOps", "NLP"] },
{ id: 10, title: "Brand Designer", company: "Pixelpush", logo: "P", color: "#db2777", role: "Design", location: "Austin", remote: true, salaryMin: 65, salaryMax: 85, type: "Part-time", level: "Mid", posted: 8, tags: ["Branding", "Illustration"] },
{ id: 11, title: "Junior Frontend Developer", company: "Lumen Labs", logo: "L", color: "#2563eb", role: "Engineering", location: "Berlin", remote: false, salaryMin: 48, salaryMax: 62, type: "Full-time", level: "Junior", posted: 7, tags: ["JavaScript", "CSS", "Vue"] },
{ id: 12, title: "Director of Product", company: "Cohort", logo: "C", color: "#ea580c", role: "Product", location: "London", remote: true, salaryMin: 170, salaryMax: 200, type: "Full-time", level: "Lead", posted: 10, tags: ["Strategy", "Hiring", "OKRs"] },
{ id: 13, title: "Content Marketer", company: "Northwind", logo: "N", color: "#16a34a", role: "Marketing", location: "Lisbon", remote: true, salaryMin: 52, salaryMax: 68, type: "Full-time", level: "Mid", posted: 0, tags: ["Copywriting", "Editorial"] },
{ id: 14, title: "Platform Engineer", company: "Meridian", logo: "M", color: "#4f46e5", role: "Engineering", location: "Toronto", remote: true, salaryMin: 105, salaryMax: 135, type: "Full-time", level: "Senior", posted: 3, tags: ["Kubernetes", "Terraform", "CI/CD"] },
{ id: 15, title: "Analytics Engineer", company: "Brightwater", logo: "B", color: "#0891b2", role: "Data", location: "London", remote: false, salaryMin: 82, salaryMax: 102, type: "Full-time", level: "Mid", posted: 5, tags: ["dbt", "Snowflake", "SQL"] },
{ id: 16, title: "Associate Product Manager", company: "Cohort", logo: "C", color: "#ea580c", role: "Product", location: "Austin", remote: false, salaryMin: 70, salaryMax: 88, type: "Full-time", level: "Junior", posted: 2, tags: ["Discovery", "Metrics"] }
];
var PAGE_SIZE = 6;
var saved = {};
var currentPage = 1;
var $ = function (s, c) { return (c || document).querySelector(s); };
var $$ = function (s, c) { return Array.prototype.slice.call((c || document).querySelectorAll(s)); };
var listEl = $("#jobList");
var countEl = $("#countLabel");
var pagerEl = $("#pager");
var emptyEl = $("#emptyState");
var form = $("#filterForm");
var searchInput = $("#searchInput");
var sortSelect = $("#sortSelect");
var salaryRange = $("#fSalary");
var salaryOut = $("#salaryOut");
// ---- toast ----
var toastEl = $("#toast");
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove("is-show"); }, 2200);
}
function postedLabel(d) {
if (d === 0) return "Today";
if (d === 1) return "1 day ago";
return d + " days ago";
}
function getFilters() {
var fd = new FormData(form);
return {
role: fd.get("role") || "",
location: fd.get("location") || "",
remote: !!fd.get("remote"),
salary: parseInt(salaryRange.value, 10),
types: fd.getAll("type"),
levels: fd.getAll("level"),
q: searchInput.value.trim().toLowerCase()
};
}
function matches(job, f) {
if (f.role && job.role !== f.role) return false;
if (f.location && job.location !== f.location) return false;
if (f.remote && !job.remote) return false;
if (f.salary > 40 && job.salaryMax < f.salary) return false;
if (f.types.length && f.types.indexOf(job.type) === -1) return false;
if (f.levels.length && f.levels.indexOf(job.level) === -1) return false;
if (f.q) {
var hay = (job.title + " " + job.company + " " + job.tags.join(" ")).toLowerCase();
if (hay.indexOf(f.q) === -1) return false;
}
return true;
}
function sortJobs(arr, mode) {
var a = arr.slice();
if (mode === "newest") a.sort(function (x, y) { return x.posted - y.posted; });
else if (mode === "salary-desc") a.sort(function (x, y) { return y.salaryMax - x.salaryMax; });
else if (mode === "salary-asc") a.sort(function (x, y) { return x.salaryMin - y.salaryMin; });
return a;
}
function bookmarkIcon() {
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 4h12a1 1 0 0 1 1 1v15l-7-4-7 4V5a1 1 0 0 1 1-1z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>';
}
function cardHTML(job) {
var isSaved = !!saved[job.id];
var isNew = job.posted <= 1;
var badges = "";
if (isNew) badges += '<span class="badge badge--new">New</span>';
if (job.remote) badges += '<span class="badge badge--remote">Remote</span>';
var tags = job.tags.map(function (t) { return '<span class="tag">' + t + "</span>"; }).join("");
return (
'<li class="jobcard">' +
'<div class="logo" style="background:' + job.color + '">' + job.logo + "</div>" +
'<div class="jobcard__body">' +
'<div class="jobcard__top">' +
'<h3 class="jobcard__title">' + job.title + "</h3>" + badges +
"</div>" +
'<p class="jobcard__company"><b>' + job.company + "</b> · " + job.location + "</p>" +
'<div class="jobcard__meta">' +
'<span class="metachip metachip--salary">$' + job.salaryMin + "k–$" + job.salaryMax + "k</span>" +
'<span class="metachip">' + job.type + "</span>" +
'<span class="metachip">' + job.level + "</span>" +
'<span class="metachip">' + (job.remote ? "Remote / " : "") + job.location + "</span>" +
"</div>" +
'<div class="jobcard__tags">' + tags + "</div>" +
"</div>" +
'<div class="jobcard__side">' +
'<button class="save' + (isSaved ? " is-saved" : "") + '" type="button" data-id="' + job.id +
'" aria-pressed="' + isSaved + '" aria-label="' + (isSaved ? "Remove bookmark" : "Save job") + '">' +
bookmarkIcon() +
"</button>" +
'<span class="jobcard__posted">' + postedLabel(job.posted) + "</span>" +
"</div>" +
"</li>"
);
}
function render() {
var f = getFilters();
var filtered = JOBS.filter(function (j) { return matches(j, f); });
var sorted = sortJobs(filtered, sortSelect.value);
var total = sorted.length;
var pages = Math.max(1, Math.ceil(total / PAGE_SIZE));
if (currentPage > pages) currentPage = pages;
countEl.innerHTML = total === 0
? "No jobs found"
: "<strong>" + total + "</strong> job" + (total === 1 ? "" : "s") + " found";
if (total === 0) {
listEl.innerHTML = "";
emptyEl.hidden = false;
pagerEl.innerHTML = "";
return;
}
emptyEl.hidden = true;
var start = (currentPage - 1) * PAGE_SIZE;
var slice = sorted.slice(start, start + PAGE_SIZE);
listEl.innerHTML = slice.map(cardHTML).join("");
renderPager(pages);
}
function renderPager(pages) {
if (pages <= 1) { pagerEl.innerHTML = ""; return; }
var html = "";
html += '<button type="button" data-page="' + (currentPage - 1) + '"' + (currentPage === 1 ? " disabled" : "") + ' aria-label="Previous page">‹</button>';
for (var p = 1; p <= pages; p++) {
html += '<button type="button" data-page="' + p + '" class="' + (p === currentPage ? "is-active" : "") +
'"' + (p === currentPage ? ' aria-current="page"' : "") + ">" + p + "</button>";
}
html += '<button type="button" data-page="' + (currentPage + 1) + '"' + (currentPage === pages ? " disabled" : "") + ' aria-label="Next page">›</button>';
pagerEl.innerHTML = html;
}
// ---- events ----
function resetToFirstAndRender() { currentPage = 1; render(); }
form.addEventListener("change", resetToFirstAndRender);
salaryRange.addEventListener("input", function () {
var v = parseInt(salaryRange.value, 10);
salaryOut.textContent = v <= 40 ? "Any salary" : "$" + v + "k+";
});
var searchTimer;
searchInput.addEventListener("input", function () {
clearTimeout(searchTimer);
searchTimer = setTimeout(resetToFirstAndRender, 180);
});
$("#searchBtn").addEventListener("click", resetToFirstAndRender);
searchInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") { e.preventDefault(); resetToFirstAndRender(); }
});
sortSelect.addEventListener("change", function () {
currentPage = 1;
render();
toast("Sorted by " + sortSelect.options[sortSelect.selectedIndex].text.toLowerCase());
});
function clearAll() {
form.reset();
searchInput.value = "";
salaryOut.textContent = "Any salary";
sortSelect.value = "relevance";
currentPage = 1;
render();
}
$("#clearFilters").addEventListener("click", function () { clearAll(); toast("Filters cleared"); });
$("#emptyReset").addEventListener("click", function () { clearAll(); });
// save toggle (event delegation)
listEl.addEventListener("click", function (e) {
var btn = e.target.closest(".save");
if (!btn) return;
var id = parseInt(btn.getAttribute("data-id"), 10);
var job = JOBS.filter(function (j) { return j.id === id; })[0];
if (saved[id]) {
delete saved[id];
btn.classList.remove("is-saved");
btn.setAttribute("aria-pressed", "false");
btn.setAttribute("aria-label", "Save job");
toast("Removed from saved jobs");
} else {
saved[id] = true;
btn.classList.add("is-saved");
btn.setAttribute("aria-pressed", "true");
btn.setAttribute("aria-label", "Remove bookmark");
toast("Saved: " + (job ? job.title : "job"));
}
});
// pagination (event delegation)
pagerEl.addEventListener("click", function (e) {
var btn = e.target.closest("button[data-page]");
if (!btn || btn.disabled) return;
var p = parseInt(btn.getAttribute("data-page"), 10);
if (p >= 1) {
currentPage = p;
render();
window.scrollTo({ top: 0, behavior: "smooth" });
}
});
render();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Job Board — Job Search</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>
<header class="topbar">
<div class="topbar__inner">
<a class="brand" href="#" aria-label="Northbeam Jobs home">
<span class="brand__mark" aria-hidden="true">N</span>
<span class="brand__name">Northbeam<span>Jobs</span></span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#" class="topnav__link is-active">Find jobs</a>
<a href="#" class="topnav__link">Companies</a>
<a href="#" class="topnav__link">Salaries</a>
</nav>
<div class="topbar__right">
<button class="btn btn--ghost" type="button">Sign in</button>
<button class="btn btn--solid" type="button">Post a job</button>
</div>
</div>
</header>
<main class="layout">
<!-- Filter rail -->
<aside class="rail" aria-label="Filter jobs">
<form id="filterForm" class="rail__form">
<div class="rail__head">
<h2 class="rail__title">Filters</h2>
<button type="button" class="rail__clear" id="clearFilters">Clear all</button>
</div>
<fieldset class="filter">
<legend class="filter__legend">Role</legend>
<div class="select-wrap">
<select id="fRole" name="role" class="select">
<option value="">All roles</option>
<option value="Engineering">Engineering</option>
<option value="Design">Design</option>
<option value="Product">Product</option>
<option value="Data">Data</option>
<option value="Marketing">Marketing</option>
</select>
</div>
</fieldset>
<fieldset class="filter">
<legend class="filter__legend">Location</legend>
<div class="select-wrap">
<select id="fLocation" name="location" class="select">
<option value="">Anywhere</option>
<option value="Berlin">Berlin</option>
<option value="London">London</option>
<option value="Lisbon">Lisbon</option>
<option value="Austin">Austin</option>
<option value="Toronto">Toronto</option>
</select>
</div>
</fieldset>
<fieldset class="filter">
<legend class="filter__legend">Remote</legend>
<label class="switch">
<input type="checkbox" id="fRemote" name="remote" />
<span class="switch__track" aria-hidden="true"><span class="switch__thumb"></span></span>
<span class="switch__label">Remote only</span>
</label>
</fieldset>
<fieldset class="filter">
<legend class="filter__legend">Minimum salary</legend>
<div class="range">
<input type="range" id="fSalary" name="salary" min="40" max="200" step="10" value="40" />
<output id="salaryOut" class="range__out">Any salary</output>
</div>
</fieldset>
<fieldset class="filter">
<legend class="filter__legend">Job type</legend>
<div class="chips" role="group" aria-label="Job type">
<label class="chip"><input type="checkbox" name="type" value="Full-time" /><span>Full-time</span></label>
<label class="chip"><input type="checkbox" name="type" value="Part-time" /><span>Part-time</span></label>
<label class="chip"><input type="checkbox" name="type" value="Contract" /><span>Contract</span></label>
<label class="chip"><input type="checkbox" name="type" value="Internship" /><span>Internship</span></label>
</div>
</fieldset>
<fieldset class="filter">
<legend class="filter__legend">Experience</legend>
<div class="chips" role="group" aria-label="Experience level">
<label class="chip"><input type="checkbox" name="level" value="Junior" /><span>Junior</span></label>
<label class="chip"><input type="checkbox" name="level" value="Mid" /><span>Mid</span></label>
<label class="chip"><input type="checkbox" name="level" value="Senior" /><span>Senior</span></label>
<label class="chip"><input type="checkbox" name="level" value="Lead" /><span>Lead</span></label>
</div>
</fieldset>
</form>
</aside>
<!-- Results column -->
<section class="results" aria-label="Job results">
<div class="searchbar">
<div class="searchbar__field">
<svg class="searchbar__icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 21l-4.3-4.3M11 18a7 7 0 1 1 0-14 7 7 0 0 1 0 14z" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<input type="search" id="searchInput" class="searchbar__input" placeholder="Search title, company or tag…" aria-label="Search jobs" />
</div>
<button class="btn btn--solid searchbar__btn" type="button" id="searchBtn">Search</button>
</div>
<div class="resultsbar">
<p class="resultsbar__count" id="countLabel" aria-live="polite">Loading…</p>
<label class="sortwrap">
<span>Sort by</span>
<div class="select-wrap select-wrap--sm">
<select id="sortSelect" class="select">
<option value="relevance">Relevance</option>
<option value="newest">Newest</option>
<option value="salary-desc">Salary: high to low</option>
<option value="salary-asc">Salary: low to high</option>
</select>
</div>
</label>
</div>
<ul class="joblist" id="jobList"></ul>
<div class="empty" id="emptyState" hidden>
<div class="empty__icon" aria-hidden="true">⌕</div>
<h3>No matching roles</h3>
<p>Try removing a filter or broadening your search.</p>
<button class="btn btn--ghost" type="button" id="emptyReset">Reset filters</button>
</div>
<nav class="pager" id="pager" aria-label="Pagination"></nav>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Job Search
A clean, ATS-style job search page built around a two-column layout: a sticky filter rail on the left and a scannable results column on the right. The rail combines role and location dropdowns, a remote-only switch, a minimum-salary range slider, and pill-style multi-select chips for job type and experience level. Every control feeds the same filter pass, so the list re-renders the moment anything changes.
The results column leads with a focus-styled search bar that matches against title, company and tags as you type. A results bar reports the live count and exposes sort options for relevance, newest, and salary high-to-low or low-to-high. Each job renders as a card with a colored company logo, title, “New” and “Remote” badges, salary and location chips, skill tags, a relative posted date, and a bookmark button that toggles saved state with a small pop animation and toast confirmation.
Pagination splits the matched roles into pages and smooth-scrolls to the top on navigation, while an empty state with a reset action appears when no jobs match. The whole interaction layer is plain vanilla JavaScript — event delegation for save and pager clicks, a debounced search, and a single render function — with no frameworks or build step. It is fully responsive, collapsing to a single column and a stacked card footer down to roughly 360px.
Illustrative UI only — fictional jobs & companies, not a real hiring platform.