Job Board — Job Posting Editor
A polished applicant-tracking job posting editor with a split-pane layout: edit title, department, location, employment type, salary range and a mock rich-text description on the left, build add/remove screening questions with required toggles, and watch a candidate-facing job card update live on the right. Toggle remote-friendly, save as draft or publish, and see status pills, salary and location chips, a bookmark toggle, and friendly toasts confirming every action.
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 8px 24px rgba(15, 23, 42, 0.08), 0 2px 6px rgba(15, 23, 42, 0.05);
--sh-3: 0 18px 48px rgba(15, 23, 42, 0.14);
}
* { 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;
}
h1, h2, h3, h4, p, ul, ol { margin: 0; }
button { font-family: inherit; }
a { color: inherit; }
/* ---------- Topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
gap: 18px;
padding: 14px 24px;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: saturate(180%) blur(10px);
border-bottom: 1px solid var(--line);
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-mark {
width: 34px; height: 34px;
display: grid; place-items: center;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff; font-weight: 800; font-size: 13px;
border-radius: 10px; letter-spacing: .5px;
box-shadow: var(--sh-1);
}
.brand-name { font-weight: 700; font-size: 15px; }
.brand-name em { color: var(--brand); font-style: normal; }
.crumbs { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
.crumbs a { text-decoration: none; color: var(--muted); }
.crumbs a:hover { color: var(--ink); }
.crumbs span[aria-current] { color: var(--ink); font-weight: 600; }
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.status-pill {
font-size: 12px; font-weight: 700;
padding: 5px 11px; border-radius: 999px;
letter-spacing: .2px;
}
.status-pill[data-state="draft"] { background: #f1f5f9; color: var(--ink-2); border: 1px solid var(--line); }
.status-pill[data-state="published"] { background: #eafaf0; color: var(--ok); border: 1px solid rgba(22, 163, 74, .25); }
/* ---------- Buttons ---------- */
.btn {
border: 1px solid transparent;
border-radius: var(--r-sm);
padding: 9px 15px;
font-size: 13.5px; font-weight: 600;
cursor: pointer;
transition: transform .06s ease, background .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--brand); color: #fff; box-shadow: var(--sh-1); }
.btn-primary:hover { background: var(--brand-d); }
.btn-ghost { background: transparent; color: var(--ink-2); border-color: var(--line-2); }
.btn-ghost:hover { background: #fff; border-color: var(--line-2); box-shadow: var(--sh-1); }
.btn-soft { background: var(--brand-50); color: var(--brand-d); border-color: rgba(37, 99, 235, .18); }
.btn-soft:hover { background: #dfeaff; }
.btn-block { width: 100%; padding: 12px; font-size: 14.5px; }
/* ---------- Layout ---------- */
.layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 380px;
gap: 24px;
max-width: 1180px;
margin: 0 auto;
padding: 24px;
align-items: start;
}
.editor { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
/* ---------- Cards ---------- */
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px 22px;
box-shadow: var(--sh-1);
}
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.card-title { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.card .card-title { margin-bottom: 14px; }
.card-head .card-title { margin-bottom: 0; }
/* ---------- Fields ---------- */
.field { margin-bottom: 14px; }
.field:last-child { margin-bottom: 0; }
.field > label, .field-label, .toggle-label {
display: block;
font-size: 12.5px; font-weight: 600; color: var(--ink-2);
margin-bottom: 6px;
}
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
input[type="text"], input[type="number"], select, .rt-area {
width: 100%;
font: inherit; font-size: 14px;
color: var(--ink);
background: #fff;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 10px 12px;
transition: border-color .15s ease, box-shadow .15s ease;
}
input:focus, select:focus, .rt-area:focus {
outline: none;
border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-50);
}
select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='3'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
}
/* salary */
.salary-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.salary-input { position: relative; flex: 1 1 120px; min-width: 110px; }
.salary-input .prefix {
position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
color: var(--muted); font-size: 14px; font-weight: 600; pointer-events: none;
}
.salary-input input { padding-left: 26px; }
.salary-dash { color: var(--muted); font-weight: 600; }
.salary-row select { flex: 0 0 auto; width: auto; }
/* switch */
.switch { display: flex; align-items: center; gap: 10px; cursor: pointer; padding-top: 2px; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
position: relative;
width: 42px; height: 24px;
background: #cbd5e1; border-radius: 999px;
transition: background .2s ease;
flex: 0 0 auto;
}
.switch-track::after {
content: ""; position: absolute; top: 2px; left: 2px;
width: 20px; height: 20px; background: #fff; border-radius: 50%;
box-shadow: var(--sh-1); transition: transform .2s ease;
}
.switch input:checked + .switch-track { background: var(--brand); }
.switch input:checked + .switch-track::after { transform: translateX(18px); }
.switch input:focus-visible + .switch-track { box-shadow: 0 0 0 3px var(--brand-50); }
.switch-text { font-size: 13px; color: var(--ink-2); }
/* ---------- Rich text mock ---------- */
.rt-toolbar {
display: flex; align-items: center; gap: 4px;
padding: 6px; border: 1px solid var(--line-2); border-bottom: none;
border-radius: var(--r-sm) var(--r-sm) 0 0;
background: #fafbfd;
}
.rt-btn {
border: 1px solid transparent; background: transparent;
color: var(--ink-2); font-size: 13px; font-weight: 600;
padding: 6px 9px; border-radius: 6px; cursor: pointer; min-width: 30px;
transition: background .12s ease, color .12s ease;
}
.rt-btn:hover { background: #eef2f7; color: var(--ink); }
.rt-btn:active { background: var(--brand-50); color: var(--brand-d); }
.rt-sep { width: 1px; height: 18px; background: var(--line-2); margin: 0 4px; }
.rt-area {
border-radius: 0 0 var(--r-sm) var(--r-sm);
min-height: 150px;
overflow-y: auto; max-height: 320px;
}
.rt-area:focus { box-shadow: inset 0 0 0 1px var(--brand), 0 0 0 3px var(--brand-50); border-color: var(--brand); }
.rt-area h3 { font-size: 14px; margin: 12px 0 4px; }
.rt-area ul { padding-left: 20px; }
.rt-area p { margin-bottom: 8px; }
.hint { font-size: 12px; color: var(--muted); margin-top: 8px; }
/* ---------- Screening questions ---------- */
.q-list { list-style: none; padding: 0; margin: 12px 0 0; display: flex; flex-direction: column; gap: 12px; }
.q-empty { font-size: 13px; color: var(--muted); margin-top: 12px; font-style: italic; }
.q-item {
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 12px;
background: #fafbfd;
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-rows: auto auto;
gap: 8px 10px;
align-items: center;
animation: pop .18s ease;
}
@keyframes pop { from { opacity: 0; transform: translateY(-4px) scale(.99); } to { opacity: 1; transform: none; } }
.q-handle {
grid-row: 1 / 3;
width: 26px; height: 26px; border-radius: 7px;
display: grid; place-items: center;
background: var(--brand-50); color: var(--brand-d);
font-weight: 700; font-size: 12px;
}
.q-item input.q-text {
border: 1px solid var(--line-2); background: #fff;
font-size: 13.5px; padding: 8px 10px;
}
.q-remove {
border: 1px solid var(--line-2); background: #fff; color: var(--muted);
width: 30px; height: 30px; border-radius: 7px; cursor: pointer;
font-size: 16px; line-height: 1; transition: all .12s ease;
}
.q-remove:hover { color: var(--danger); border-color: rgba(220, 38, 38, .35); background: #fef2f2; }
.q-meta { grid-column: 2 / 4; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.q-type {
font-size: 12.5px; padding: 5px 8px; width: auto;
border: 1px solid var(--line-2); border-radius: 6px; background: #fff;
}
.q-required { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); cursor: pointer; }
.q-required input { width: 15px; height: 15px; accent-color: var(--brand); }
/* ---------- Preview ---------- */
.preview-sticky { position: sticky; top: 88px; }
.preview-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.preview-head h2 { font-size: 14px; font-weight: 700; color: var(--ink-2); text-transform: uppercase; letter-spacing: .06em; }
.preview-tag { font-size: 11px; font-weight: 700; color: var(--brand-d); background: var(--brand-50); padding: 4px 9px; border-radius: 999px; }
.job-card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 20px;
box-shadow: var(--sh-2);
}
.job-top { display: flex; align-items: flex-start; gap: 12px; }
.logo {
width: 46px; height: 46px; flex: 0 0 auto;
display: grid; place-items: center;
background: linear-gradient(135deg, #1e293b, #334155);
color: #fff; font-weight: 800; font-size: 15px;
border-radius: 12px;
}
.job-headings { flex: 1 1 auto; min-width: 0; }
.job-headings h3 { font-size: 17px; font-weight: 700; letter-spacing: -.01em; line-height: 1.3; }
.job-company { font-size: 13px; color: var(--muted); margin-top: 2px; }
.bookmark {
flex: 0 0 auto;
width: 36px; height: 36px; border-radius: 9px;
border: 1px solid var(--line-2); background: #fff; cursor: pointer;
display: grid; place-items: center;
color: var(--muted); transition: all .15s ease;
}
.bookmark svg { fill: none; stroke: currentColor; stroke-width: 1.8; }
.bookmark:hover { border-color: var(--brand); color: var(--brand); }
.bookmark[aria-pressed="true"] { color: var(--brand); border-color: var(--brand); background: var(--brand-50); }
.bookmark[aria-pressed="true"] svg { fill: currentColor; }
.chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 14px 0; }
.chip {
font-size: 12px; font-weight: 600;
padding: 5px 10px; border-radius: 999px;
background: #f1f5f9; color: var(--ink-2);
display: inline-flex; align-items: center; gap: 5px;
}
.chip svg { width: 13px; height: 13px; }
.chip.remote { background: #eafaf0; color: var(--ok); }
.chip.salary { background: var(--brand-50); color: var(--brand-d); }
.job-body { font-size: 13.5px; color: var(--ink-2); margin-bottom: 14px; border-top: 1px solid var(--line); padding-top: 14px; }
.job-body h3 { font-size: 13.5px; color: var(--ink); margin: 10px 0 4px; }
.job-body ul { padding-left: 18px; margin: 4px 0; }
.job-body li { margin-bottom: 3px; }
.job-body p { margin-bottom: 6px; }
.job-screening { margin-bottom: 14px; padding: 12px; background: #fafbfd; border: 1px solid var(--line); border-radius: var(--r-md); }
.job-screening h4 { font-size: 12.5px; color: var(--ink-2); text-transform: uppercase; letter-spacing: .05em; margin-bottom: 8px; }
.job-screening ol { padding-left: 18px; }
.job-screening li { font-size: 13px; color: var(--ink); margin-bottom: 5px; }
.job-screening li .req { color: var(--danger); font-weight: 700; }
.job-foot { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); margin-top: 12px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: 0 0 auto; }
.job-foot.live .dot { background: var(--ok); box-shadow: 0 0 0 3px rgba(22, 163, 74, .18); }
/* ---------- Toast ---------- */
.toast-wrap {
position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
display: flex; flex-direction: column; gap: 8px; z-index: 50;
width: max-content; max-width: 92vw;
}
.toast {
display: flex; align-items: center; gap: 10px;
background: var(--ink); color: #fff;
padding: 12px 16px; border-radius: var(--r-md);
font-size: 13.5px; font-weight: 500;
box-shadow: var(--sh-3);
animation: toastIn .25s cubic-bezier(.2, .8, .2, 1);
}
.toast.ok { background: linear-gradient(135deg, #16a34a, #15803d); }
.toast.out { animation: toastOut .25s ease forwards; }
.toast .t-ico { font-size: 16px; }
@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: 980px) {
.layout { grid-template-columns: 1fr; }
.preview-sticky { position: static; }
}
@media (max-width: 520px) {
.topbar { flex-wrap: wrap; gap: 10px 14px; padding: 12px 14px; }
.crumbs { order: 3; flex-basis: 100%; }
.topbar-actions { gap: 8px; }
.btn { padding: 8px 12px; font-size: 13px; }
.layout { padding: 14px; gap: 16px; }
.card { padding: 16px; border-radius: var(--r-md); }
.grid-2 { grid-template-columns: 1fr; }
.q-item { grid-template-columns: auto 1fr; }
.q-remove { grid-column: 2; justify-self: end; }
.q-meta { grid-column: 1 / 3; }
.brand-name { font-size: 14px; }
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastWrap = document.getElementById("toastWrap");
function toast(msg, kind) {
var el = document.createElement("div");
el.className = "toast" + (kind ? " " + kind : "");
el.innerHTML =
'<span class="t-ico" aria-hidden="true">' +
(kind === "ok" ? "✓" : "ℹ") +
"</span><span>" +
msg +
"</span>";
toastWrap.appendChild(el);
setTimeout(function () {
el.classList.add("out");
setTimeout(function () {
el.remove();
}, 260);
}, 2600);
}
/* ---------- Elements ---------- */
var form = document.getElementById("jobForm");
var qList = document.getElementById("qList");
var qEmpty = document.getElementById("qEmpty");
var addQBtn = document.getElementById("addQBtn");
var fTitle = document.getElementById("f-title");
var fDept = document.getElementById("f-dept");
var fType = document.getElementById("f-type");
var fLocation = document.getElementById("f-location");
var fRemote = document.getElementById("f-remote");
var fSalMin = document.getElementById("f-salmin");
var fSalMax = document.getElementById("f-salmax");
var fSalPer = document.getElementById("f-salper");
var fDesc = document.getElementById("f-desc");
var pvTitle = document.getElementById("pv-title");
var pvChips = document.getElementById("pv-chips");
var pvDesc = document.getElementById("pv-desc");
var pvScreening = document.getElementById("pv-screening");
var pvQList = document.getElementById("pv-qlist");
var pvStatus = document.getElementById("pv-status");
var pvFoot = pvStatus.closest(".job-foot");
var statusPill = document.getElementById("statusPill");
var published = false;
var qSeed = ["Why do you want to join Northbridge?", ""];
var qCounter = 0;
/* ---------- Screening questions ---------- */
function syncQEmpty() {
var has = qList.children.length > 0;
qEmpty.hidden = has;
}
function addQuestion(text, required, type) {
qCounter++;
var li = document.createElement("li");
li.className = "q-item";
var idx = qList.children.length + 1;
li.innerHTML =
'<span class="q-handle" aria-hidden="true">' + idx + "</span>" +
'<input class="q-text" type="text" value="" placeholder="Type your question…" aria-label="Question text" />' +
'<button type="button" class="q-remove" aria-label="Remove question" title="Remove">×</button>' +
'<div class="q-meta">' +
'<select class="q-type" aria-label="Answer type">' +
'<option value="short">Short answer</option>' +
'<option value="long">Paragraph</option>' +
'<option value="yesno">Yes / No</option>' +
"</select>" +
'<label class="q-required"><input type="checkbox" /> Required</label>' +
"</div>";
li.querySelector(".q-text").value = text || "";
if (required) li.querySelector(".q-required input").checked = true;
if (type) li.querySelector(".q-type").value = type;
li.querySelector(".q-remove").addEventListener("click", function () {
li.style.animation = "none";
li.remove();
renumber();
syncQEmpty();
renderPreview();
toast("Question removed");
});
qList.appendChild(li);
renumber();
syncQEmpty();
return li;
}
function renumber() {
Array.prototype.forEach.call(qList.children, function (li, i) {
li.querySelector(".q-handle").textContent = i + 1;
});
}
addQBtn.addEventListener("click", function () {
if (qList.children.length >= 8) {
toast("Up to 8 screening questions");
return;
}
var li = addQuestion("", false, "short");
li.querySelector(".q-text").focus();
renderPreview();
});
// live preview on edits inside question list
qList.addEventListener("input", renderPreview);
qList.addEventListener("change", renderPreview);
/* ---------- Rich text mock ---------- */
document.querySelectorAll(".rt-btn").forEach(function (btn) {
btn.addEventListener("mousedown", function (e) {
e.preventDefault(); // keep selection
});
btn.addEventListener("click", function () {
var cmd = btn.dataset.cmd;
var val = btn.dataset.val || null;
fDesc.focus();
try {
document.execCommand(cmd, false, val);
} catch (err) {
/* no-op */
}
renderPreview();
});
});
fDesc.addEventListener("input", renderPreview);
/* ---------- Salary formatting ---------- */
function fmtMoney(n) {
return new Intl.NumberFormat("en-US").format(n);
}
function salaryText() {
var min = parseInt(fSalMin.value, 10);
var max = parseInt(fSalMax.value, 10);
var per = fSalPer.value === "mo" ? "/mo" : "/yr";
if (isNaN(min) && isNaN(max)) return "";
if (isNaN(max) || max === min) return "€" + fmtMoney(min) + " " + per;
if (isNaN(min)) return "Up to €" + fmtMoney(max) + " " + per;
return "€" + fmtMoney(min) + "–€" + fmtMoney(max) + " " + per;
}
var icons = {
pin: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 21s-7-5.2-7-11a7 7 0 1 1 14 0c0 5.8-7 11-7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>',
type: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>',
dept: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 20V8l8-5 8 5v12"/><path d="M9 20v-6h6v6"/></svg>',
};
function chip(cls, ico, text) {
return '<span class="chip ' + cls + '">' + (ico ? icons[ico] || "" : "") + text + "</span>";
}
/* ---------- Render preview ---------- */
function renderPreview() {
pvTitle.textContent = fTitle.value.trim() || "Untitled role";
var c = "";
if (fLocation.value.trim()) c += chip("", "pin", fLocation.value.trim());
if (fRemote.checked) c += chip("remote", null, "● Remote OK");
c += chip("", "dept", fDept.value);
c += chip("", "type", fType.value);
var sal = salaryText();
if (sal) c += chip("salary", null, sal);
pvChips.innerHTML = c;
pvDesc.innerHTML = fDesc.innerHTML;
var rows = Array.prototype.map.call(qList.children, function (li) {
return {
text: li.querySelector(".q-text").value.trim(),
required: li.querySelector(".q-required input").checked,
};
}).filter(function (q) { return q.text; });
if (rows.length) {
pvScreening.hidden = false;
pvQList.innerHTML = rows.map(function (q) {
return "<li>" + escapeHtml(q.text) + (q.required ? ' <span class="req">*</span>' : "") + "</li>";
}).join("");
} else {
pvScreening.hidden = true;
pvQList.innerHTML = "";
}
}
function escapeHtml(s) {
return s.replace(/[&<>"]/g, function (ch) {
return { "&": "&", "<": "<", ">": ">", '"': """ }[ch];
});
}
// live preview on all standard inputs
[fTitle, fDept, fType, fLocation, fRemote, fSalMin, fSalMax, fSalPer].forEach(function (el) {
el.addEventListener("input", renderPreview);
el.addEventListener("change", renderPreview);
});
/* ---------- Bookmark toggle ---------- */
var bookmark = document.getElementById("pvBookmark");
bookmark.addEventListener("click", function () {
var on = bookmark.getAttribute("aria-pressed") === "true";
bookmark.setAttribute("aria-pressed", String(!on));
toast(on ? "Removed from saved" : "Saved to your jobs");
});
document.getElementById("pvApply").addEventListener("click", function () {
toast("This is a preview — applications are disabled");
});
/* ---------- Status / publish / draft ---------- */
function setStatus(isPublished) {
published = isPublished;
if (isPublished) {
statusPill.dataset.state = "published";
statusPill.textContent = "Published";
pvStatus.textContent = "Live — visible to candidates";
pvFoot.classList.add("live");
} else {
statusPill.dataset.state = "draft";
statusPill.textContent = "Draft";
pvStatus.textContent = "Draft — not visible to candidates";
pvFoot.classList.remove("live");
}
}
document.getElementById("publishBtn").addEventListener("click", function () {
if (!fTitle.value.trim()) {
toast("Add a job title before publishing");
fTitle.focus();
return;
}
setStatus(true);
toast("“" + fTitle.value.trim() + "” is now live", "ok");
});
document.getElementById("saveDraftBtn").addEventListener("click", function () {
setStatus(false);
toast("Draft saved", "ok");
});
/* ---------- Init ---------- */
qSeed.forEach(function (t, i) {
addQuestion(t, i === 0, i === 0 ? "long" : "short");
});
// remove the empty seed placeholder if blank
Array.prototype.slice.call(qList.children).forEach(function (li) {
if (!li.querySelector(".q-text").value.trim()) li.remove();
});
renumber();
syncQEmpty();
renderPreview();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Job Posting Editor — Northbridge Talent</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="brand">
<span class="brand-mark" aria-hidden="true">NB</span>
<span class="brand-name">Northbridge <em>Talent</em></span>
</div>
<nav class="crumbs" aria-label="Breadcrumb">
<a href="#">Jobs</a><span aria-hidden="true">/</span><span aria-current="page">New posting</span>
</nav>
<div class="topbar-actions">
<span class="status-pill" id="statusPill" data-state="draft">Draft</span>
<button class="btn btn-ghost" id="saveDraftBtn" type="button">Save draft</button>
<button class="btn btn-primary" id="publishBtn" type="button">Publish</button>
</div>
</header>
<main class="layout">
<!-- EDITOR -->
<section class="editor" aria-label="Job posting editor">
<form id="jobForm" novalidate>
<div class="card">
<h2 class="card-title">Role details</h2>
<div class="field">
<label for="f-title">Job title</label>
<input id="f-title" name="title" type="text" value="Senior Frontend Engineer" maxlength="80" autocomplete="off" />
</div>
<div class="grid-2">
<div class="field">
<label for="f-dept">Department</label>
<select id="f-dept" name="dept">
<option>Engineering</option>
<option>Design</option>
<option>Product</option>
<option>Marketing</option>
<option>Sales</option>
<option>Operations</option>
<option>People</option>
</select>
</div>
<div class="field">
<label for="f-type">Employment type</label>
<select id="f-type" name="type">
<option>Full-time</option>
<option>Part-time</option>
<option>Contract</option>
<option>Internship</option>
</select>
</div>
</div>
<div class="grid-2">
<div class="field">
<label for="f-location">Location</label>
<input id="f-location" name="location" type="text" value="Lisbon, Portugal" autocomplete="off" />
</div>
<div class="field">
<label class="toggle-label" for="f-remote">Remote-friendly</label>
<label class="switch">
<input id="f-remote" name="remote" type="checkbox" checked />
<span class="switch-track" aria-hidden="true"></span>
<span class="switch-text">Allow remote applicants</span>
</label>
</div>
</div>
<div class="field">
<span class="field-label">Salary range (annual)</span>
<div class="salary-row">
<div class="salary-input">
<span class="prefix">€</span>
<input id="f-salmin" name="salaryMin" type="number" value="62000" min="0" step="1000" inputmode="numeric" aria-label="Minimum salary" />
</div>
<span class="salary-dash" aria-hidden="true">–</span>
<div class="salary-input">
<span class="prefix">€</span>
<input id="f-salmax" name="salaryMax" type="number" value="84000" min="0" step="1000" inputmode="numeric" aria-label="Maximum salary" />
</div>
<select id="f-salper" name="salaryPeriod" aria-label="Salary period">
<option value="yr">/ year</option>
<option value="mo">/ month</option>
</select>
</div>
</div>
</div>
<div class="card">
<h2 class="card-title">Description</h2>
<div class="rt-toolbar" role="toolbar" aria-label="Formatting">
<button type="button" class="rt-btn" data-cmd="bold" title="Bold"><b>B</b></button>
<button type="button" class="rt-btn" data-cmd="italic" title="Italic"><i>I</i></button>
<button type="button" class="rt-btn" data-cmd="insertUnorderedList" title="Bulleted list">• List</button>
<span class="rt-sep" aria-hidden="true"></span>
<button type="button" class="rt-btn" data-cmd="formatBlock" data-val="h3" title="Heading">H</button>
</div>
<div id="f-desc" class="rt-area" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Job description">
<p>We're building the next generation of design tooling and need a frontend engineer who sweats the details.</p>
<h3>What you'll do</h3>
<ul>
<li>Ship accessible, performant interfaces in our component library.</li>
<li>Partner with design to prototype and refine new flows.</li>
<li>Mentor engineers and raise the bar on code quality.</li>
</ul>
</div>
<p class="hint">Mock rich-text editor — formatting buttons apply to the selected text.</p>
</div>
<div class="card">
<div class="card-head">
<h2 class="card-title">Screening questions</h2>
<button type="button" class="btn btn-soft" id="addQBtn">+ Add question</button>
</div>
<p class="hint">Applicants answer these before submitting. Mark any as required.</p>
<ul class="q-list" id="qList" aria-live="polite"></ul>
<p class="q-empty" id="qEmpty" hidden>No screening questions yet — applicants apply with just a resume.</p>
</div>
</form>
</section>
<!-- PREVIEW -->
<aside class="preview" aria-label="Live preview">
<div class="preview-sticky">
<div class="preview-head">
<h2>Live preview</h2>
<span class="preview-tag">Candidate view</span>
</div>
<article class="job-card" id="previewCard">
<div class="job-top">
<div class="logo" aria-hidden="true">NB</div>
<div class="job-headings">
<h3 id="pv-title">Senior Frontend Engineer</h3>
<p class="job-company">Northbridge Talent</p>
</div>
<button class="bookmark" id="pvBookmark" type="button" aria-pressed="false" aria-label="Save job">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M6 4h12a1 1 0 0 1 1 1v15l-7-4-7 4V5a1 1 0 0 1 1-1z"/></svg>
</button>
</div>
<div class="chips" id="pv-chips"></div>
<div class="job-body" id="pv-desc"></div>
<div class="job-screening" id="pv-screening" hidden>
<h4>Screening questions</h4>
<ol id="pv-qlist"></ol>
</div>
<button class="btn btn-primary btn-block" type="button" id="pvApply">Apply now</button>
<p class="job-foot"><span class="dot" id="pv-statusdot"></span><span id="pv-status">Draft — not visible to candidates</span></p>
</article>
</div>
</aside>
</main>
<div class="toast-wrap" id="toastWrap" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Job Posting Editor
A recruiter-facing editor for composing a job listing on the fictional Northbridge Talent platform. The left column is the form: role title, department and employment type, location with a remote-friendly switch, a two-input salary range with a yearly/monthly selector, and a small mock rich-text area with bold, italic, list and heading controls. Everything is wired to a candidate-view preview on the right that re-renders on every keystroke.
The screening-questions builder lets you add up to eight questions, each with an answer-type dropdown (short answer, paragraph, yes/no) and a required checkbox. Add and remove rows and the preview’s numbered question list updates instantly, marking required items with an asterisk. The candidate card shows location, remote, department, type and salary as scannable chips, plus a working bookmark toggle.
The top bar carries a status pill that flips between Draft and Published. Save draft and Publish both surface confirmation toasts, with publish guarding against an empty title. The layout collapses to a single column on tablet and stays usable down to roughly 360px, all in dependency-free vanilla JavaScript.
Illustrative UI only — fictional jobs & companies, not a real hiring platform.