Job Board — Application Form
A multi-section job application form for an applicant tracking flow. Pick a saved resume or drag-and-drop a new file with live upload preview, edit autofilled contact details, answer screening questions with radio pills and selects, write an optional cover letter with a character counter, then review every answer and submit to a confirmation screen with a reference number. Includes inline validation, a stepper, toasts, save-draft and bookmark controls.
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 24px rgba(15, 23, 42, 0.08);
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; }
a { color: inherit; }
.link { color: var(--brand); font-weight: 600; cursor: pointer; }
.link:hover { color: var(--brand-d); text-decoration: underline; }
.muted { color: var(--muted); }
.opt { color: var(--muted); font-weight: 400; }
/* Topbar */
.topbar {
background: var(--surface);
border-bottom: 1px solid var(--line);
position: sticky;
top: 0;
z-index: 20;
}
.topbar-inner { display: flex; align-items: center; gap: 20px; height: 60px; }
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; font-weight: 800; }
.brand-mark {
width: 30px; height: 30px; border-radius: 9px;
background: linear-gradient(135deg, var(--brand), var(--brand-d));
color: #fff; display: grid; place-items: center; font-weight: 800;
}
.brand-name { color: var(--ink); letter-spacing: -0.01em; }
.topnav { display: flex; gap: 6px; margin-left: 8px; }
.topnav a {
text-decoration: none; color: var(--ink-2); font-weight: 600; font-size: 14px;
padding: 8px 12px; border-radius: var(--r-sm);
}
.topnav a:hover { background: var(--bg); color: var(--ink); }
.topnav-active { color: var(--brand) !important; background: var(--brand-50); }
.ghost-btn {
margin-left: auto;
background: var(--surface); color: var(--ink); font-weight: 600; font-size: 14px;
border: 1px solid var(--line-2); border-radius: var(--r-sm);
padding: 9px 16px; cursor: pointer; font-family: inherit;
}
.ghost-btn:hover { background: var(--bg); border-color: var(--muted); }
.panel-actions .ghost-btn { margin-left: 0; }
/* Layout */
.layout {
display: grid;
grid-template-columns: 1fr 320px;
gap: 22px;
align-items: start;
padding-top: 28px;
padding-bottom: 56px;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
}
.form-card { padding: 28px; }
/* Job head */
.job-head { display: flex; gap: 16px; align-items: flex-start; }
.logo {
width: 56px; height: 56px; flex: none; border-radius: var(--r-md);
background: linear-gradient(135deg, #1e293b, #334155);
color: #fff; display: grid; place-items: center; font-weight: 800; font-size: 18px;
}
.job-title { margin: 0; font-size: 22px; letter-spacing: -0.02em; }
.job-sub { margin: 2px 0 10px; color: var(--ink-2); font-weight: 500; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
display: inline-flex; align-items: center; gap: 5px;
background: var(--bg); border: 1px solid var(--line);
color: var(--ink-2); font-size: 12.5px; font-weight: 600;
padding: 5px 10px; border-radius: 999px;
}
.chip svg { width: 13px; height: 13px; fill: currentColor; }
.chip-remote { background: #ecfdf3; border-color: #abefc6; color: #067647; }
/* Steps */
.steps {
list-style: none; display: flex; gap: 6px;
margin: 24px 0 4px; padding: 0;
}
.step { flex: 1; display: flex; align-items: center; gap: 8px; min-width: 0; }
.step-dot {
width: 26px; height: 26px; flex: none; border-radius: 50%;
display: grid; place-items: center; font-size: 13px; font-weight: 700;
background: var(--bg); color: var(--muted); border: 1px solid var(--line-2);
transition: 0.2s;
}
.step-label { font-size: 13px; font-weight: 600; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.step.is-active .step-dot { background: var(--brand); color: #fff; border-color: var(--brand); }
.step.is-active .step-label { color: var(--ink); }
.step.is-done .step-dot { background: var(--ok); color: #fff; border-color: var(--ok); }
.step.is-done .step-label { color: var(--ink-2); }
/* Panels */
.panel { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 24px; animation: fade 0.3s ease; }
.panel[hidden] { display: none; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.panel-title { margin: 0 0 4px; font-size: 18px; letter-spacing: -0.01em; }
.panel-help { margin: 0 0 18px; color: var(--ink-2); font-size: 14.5px; }
/* Fields */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.field > label, .field legend, fieldset.field legend { font-weight: 600; font-size: 14px; color: var(--ink); padding: 0; }
fieldset.field { border: 0; padding: 0; margin: 0 0 18px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 16px; }
input[type="text"], input[type="email"], input[type="tel"], input[type="url"], select, textarea {
font-family: inherit; font-size: 14.5px; color: var(--ink);
background: var(--surface); border: 1px solid var(--line-2);
border-radius: var(--r-sm); padding: 11px 12px; width: 100%;
transition: border-color 0.15s, box-shadow 0.15s;
}
textarea { resize: vertical; min-height: 96px; }
input:focus, select:focus, textarea:focus {
outline: none; border-color: var(--brand);
box-shadow: 0 0 0 3px var(--brand-50);
}
.field.invalid input, .field.invalid select { border-color: var(--danger); }
.field.invalid input:focus, .field.invalid select:focus { box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.12); }
.err { color: var(--danger); font-size: 12.5px; font-weight: 600; min-height: 0; }
.field.invalid .err::before, .err.show::before { content: ""; }
.count { color: var(--muted); font-size: 12px; align-self: flex-end; }
/* Radio pills */
.radio-row { display: flex; gap: 10px; }
.radio-pill { position: relative; cursor: pointer; }
.radio-pill input { position: absolute; opacity: 0; inset: 0; }
.radio-pill span {
display: inline-block; padding: 9px 18px; border-radius: 999px;
border: 1px solid var(--line-2); font-weight: 600; font-size: 14px; color: var(--ink-2);
transition: 0.15s;
}
.radio-pill:hover span { border-color: var(--muted); }
.radio-pill input:checked + span { background: var(--brand-50); border-color: var(--brand); color: var(--brand-d); }
.radio-pill input:focus-visible + span { box-shadow: 0 0 0 3px var(--brand-50); }
/* Saved resumes */
.saved-resumes { display: grid; gap: 10px; margin-bottom: 16px; }
.resume-pick { cursor: pointer; }
.resume-pick input { position: absolute; opacity: 0; }
.resume-pick-body {
display: flex; align-items: center; gap: 12px;
border: 1px solid var(--line-2); border-radius: var(--r-md); padding: 12px 14px;
transition: 0.15s;
}
.resume-pick:hover .resume-pick-body { border-color: var(--muted); }
.resume-pick input:checked + .resume-pick-body { border-color: var(--brand); background: var(--brand-50); box-shadow: 0 0 0 1px var(--brand) inset; }
.resume-pick input:focus-visible + .resume-pick-body { box-shadow: 0 0 0 3px var(--brand-50); }
.resume-ic {
width: 40px; height: 40px; flex: none; border-radius: 9px;
background: #fef2f2; color: var(--danger); font-size: 11px; font-weight: 800;
display: grid; place-items: center;
}
.resume-info { display: flex; flex-direction: column; gap: 1px; font-size: 14px; }
.resume-info .muted { font-size: 12.5px; }
/* Dropzone */
.dropzone {
border: 1.5px dashed var(--line-2); border-radius: var(--r-md);
padding: 26px; text-align: center; cursor: pointer; margin-bottom: 8px;
transition: 0.15s; background: var(--bg);
}
.dropzone:hover, .dropzone:focus-visible { border-color: var(--brand); background: var(--brand-50); outline: none; }
.dropzone.drag { border-color: var(--brand); background: var(--brand-50); }
.dz-ic { width: 26px; height: 26px; stroke: var(--brand); fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.dz-main { margin: 8px 0 2px; font-weight: 600; font-size: 14.5px; }
.dz-sub { margin: 0; color: var(--muted); font-size: 12.5px; }
.upload-preview {
display: flex; align-items: center; gap: 12px; margin: 12px 0 8px;
border: 1px solid var(--line); border-radius: var(--r-md); padding: 12px 14px;
background: var(--surface); animation: fade 0.25s ease;
}
.upload-preview .resume-ic { background: #ecfdf3; color: var(--ok); }
.upload-preview .up-info { flex: 1; min-width: 0; }
.upload-preview strong { display: block; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.up-remove {
border: 0; background: transparent; color: var(--danger); font-weight: 600;
font-size: 13px; cursor: pointer; padding: 6px 8px; border-radius: var(--r-sm); font-family: inherit;
}
.up-remove:hover { background: #fef2f2; }
/* Buttons */
.panel-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }
.primary-btn {
background: var(--brand); color: #fff; border: 0;
font-weight: 700; font-size: 14.5px; font-family: inherit;
padding: 11px 22px; border-radius: var(--r-sm); cursor: pointer;
transition: 0.15s; box-shadow: var(--sh-1);
}
.primary-btn:hover { background: var(--brand-d); }
.primary-btn:active { transform: translateY(1px); }
.primary-btn:disabled { opacity: 0.6; cursor: progress; }
/* Review */
.review { display: grid; gap: 2px; border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden; }
.review-group { padding: 14px 16px; }
.review-group + .review-group { border-top: 1px solid var(--line); }
.review-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.review-h h4 { margin: 0; font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.review-edit { background: none; border: 0; color: var(--brand); font-weight: 600; font-size: 13px; cursor: pointer; font-family: inherit; }
.review-edit:hover { text-decoration: underline; }
.review-rows { display: grid; gap: 6px; }
.review-row { display: flex; justify-content: space-between; gap: 14px; font-size: 14px; }
.review-row span:first-child { color: var(--ink-2); }
.review-row span:last-child { font-weight: 600; text-align: right; }
.consent {
display: flex; gap: 10px; align-items: flex-start; margin: 18px 0 4px;
font-size: 14px; color: var(--ink-2);
}
.consent input { margin-top: 3px; width: 16px; height: 16px; accent-color: var(--brand); flex: none; }
/* Success */
.panel.success { text-align: center; border-top: 0; padding-top: 12px; }
.success-ic {
width: 64px; height: 64px; margin: 8px auto 16px; border-radius: 50%;
background: #ecfdf3; display: grid; place-items: center;
animation: pop 0.4s cubic-bezier(0.2, 0.9, 0.3, 1.3);
}
.success-ic svg { width: 30px; height: 30px; stroke: var(--ok); fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
@keyframes pop { from { transform: scale(0.5); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.panel.success .panel-help { max-width: 420px; margin: 0 auto 12px; }
.ref { font-size: 14px; color: var(--ink-2); margin: 4px 0 18px; }
.ref strong { font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; color: var(--ink); }
.panel.success .ghost-btn { margin: 0 auto; display: inline-block; }
/* Aside */
.aside { padding: 22px; position: sticky; top: 80px; }
.aside-logo {
width: 44px; height: 44px; border-radius: var(--r-md);
background: linear-gradient(135deg, #1e293b, #334155); color: #fff;
display: grid; place-items: center; font-weight: 800; margin-bottom: 12px;
}
.aside-title { margin: 0; font-size: 16px; letter-spacing: -0.01em; }
.aside-co { margin: 1px 0 16px; color: var(--ink-2); font-weight: 500; font-size: 14px; }
.aside-list { list-style: none; margin: 0 0 16px; padding: 0; display: grid; gap: 10px; }
.aside-list li { display: flex; justify-content: space-between; gap: 12px; font-size: 14px; }
.aside-list li span:last-child { font-weight: 600; text-align: right; }
.aside-note { font-size: 13px; color: var(--ink-2); background: var(--bg); border-radius: var(--r-sm); padding: 10px 12px; margin: 0 0 14px; }
.bookmark {
width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;
background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--r-sm);
padding: 10px; font-weight: 600; font-size: 14px; cursor: pointer; font-family: inherit;
color: var(--ink); transition: 0.15s;
}
.bookmark svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linejoin: round; }
.bookmark:hover { border-color: var(--muted); }
.bookmark[aria-pressed="true"] { background: var(--brand-50); border-color: var(--brand); color: var(--brand-d); }
.bookmark[aria-pressed="true"] svg { fill: var(--brand-d); stroke: var(--brand-d); }
/* Toast */
.toast-host { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 50; display: grid; gap: 8px; }
.toast {
background: var(--ink); color: #fff; font-size: 14px; font-weight: 500;
padding: 11px 16px; border-radius: var(--r-sm); box-shadow: var(--sh-2);
display: flex; align-items: center; gap: 8px;
animation: toastIn 0.25s ease, toastOut 0.3s ease forwards 2.6s;
}
.toast.ok { background: #065f46; }
.toast.warn { background: #92400e; }
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(10px); } }
/* Responsive */
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
.aside { position: static; order: -1; }
}
@media (max-width: 520px) {
.wrap { padding: 0 14px; }
.form-card { padding: 18px; }
.topnav { display: none; }
.ghost-btn { margin-left: auto; }
.grid-2 { grid-template-columns: 1fr; }
.job-title { font-size: 19px; }
.step-label { display: none; }
.step { flex: 0; }
.steps { gap: 10px; justify-content: flex-start; }
.panel-actions { flex-direction: column-reverse; }
.panel-actions button { width: 100%; }
}(function () {
"use strict";
var form = document.getElementById("applyForm");
var panels = Array.prototype.slice.call(form.querySelectorAll(".panel"));
var steps = Array.prototype.slice.call(document.querySelectorAll(".step"));
var current = 0;
/* ---------- Toast ---------- */
var host = document.getElementById("toastHost");
function toast(msg, kind) {
var el = document.createElement("div");
el.className = "toast" + (kind ? " " + kind : "");
el.textContent = msg;
host.appendChild(el);
setTimeout(function () { el.remove(); }, 3000);
}
/* ---------- Panel navigation ---------- */
function showPanel(i) {
panels.forEach(function (p) {
var idx = p.getAttribute("data-panel");
p.hidden = String(i) !== idx;
});
steps.forEach(function (s, si) {
s.classList.toggle("is-active", si === i);
s.classList.toggle("is-done", si < i);
var dot = s.querySelector(".step-dot");
dot.textContent = si < i ? "✓" : String(si + 1);
});
current = i;
form.scrollIntoView({ behavior: "smooth", block: "start" });
}
/* ---------- Validation ---------- */
function setError(field, msg) {
if (!field) return;
field.classList.toggle("invalid", !!msg);
var err = field.querySelector("[data-err]");
if (err) err.textContent = msg || "";
}
function fieldOf(input) { return input.closest(".field"); }
function validatePanel(i) {
var panel = panels[i];
var ok = true;
var firstBad = null;
// required text/email/tel/select
panel.querySelectorAll("input[required], select[required], textarea[required]").forEach(function (input) {
if (input.type === "radio" || input.type === "checkbox") return;
var val = (input.value || "").trim();
var msg = "";
if (!val) msg = "This field is required.";
else if (input.type === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) msg = "Enter a valid email address.";
setError(fieldOf(input), msg);
if (msg) { ok = false; firstBad = firstBad || input; }
});
// required radio groups
var seen = {};
panel.querySelectorAll('input[type="radio"][required]').forEach(function (r) {
if (seen[r.name]) return;
seen[r.name] = true;
var checked = panel.querySelector('input[name="' + r.name + '"]:checked');
var fs = r.closest(".field");
var err = fs ? fs.querySelector("[data-err]") : null;
if (!checked) {
ok = false; firstBad = firstBad || r;
if (err) err.textContent = "Please choose an option.";
} else if (err) err.textContent = "";
});
if (!ok && firstBad) {
toast("Please fix the highlighted fields.", "warn");
firstBad.focus();
}
return ok;
}
/* ---------- Buttons ---------- */
form.addEventListener("click", function (e) {
var nextBtn = e.target.closest("[data-next]");
var prevBtn = e.target.closest("[data-prev]");
if (nextBtn) {
if (validatePanel(current)) {
var to = current + 1;
if (to === 3) buildReview();
showPanel(to);
}
} else if (prevBtn) {
showPanel(current - 1);
}
});
/* ---------- Resume selection / upload ---------- */
var dropzone = document.getElementById("dropzone");
var fileInput = document.getElementById("fileInput");
var preview = document.getElementById("uploadPreview");
var uploaded = null;
function chosenResume() {
if (uploaded) return uploaded.name;
var picked = form.querySelector('input[name="resumePick"]:checked');
return picked ? picked.value : "—";
}
function fmtSize(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1048576) return Math.round(bytes / 1024) + " KB";
return (bytes / 1048576).toFixed(1) + " MB";
}
function acceptFile(file) {
if (!file) return;
if (file.size > 5 * 1024 * 1024) { toast("File is larger than 5 MB.", "warn"); return; }
if (!/\.(pdf|docx?|DOC)$/i.test(file.name)) { toast("Use a PDF, DOC or DOCX file.", "warn"); return; }
uploaded = file;
preview.hidden = false;
preview.innerHTML =
'<span class="resume-ic" aria-hidden="true">NEW</span>' +
'<span class="up-info"><strong>' + escapeHtml(file.name) + "</strong>" +
'<span class="muted">' + fmtSize(file.size) + " · just now</span></span>" +
'<button type="button" class="up-remove" id="upRemove">Remove</button>';
// deselect saved resumes
form.querySelectorAll('input[name="resumePick"]').forEach(function (r) { r.checked = false; });
toast("Resume attached.", "ok");
document.getElementById("upRemove").addEventListener("click", function () {
uploaded = null; preview.hidden = true; preview.innerHTML = "";
var first = form.querySelector('input[name="resumePick"]');
if (first) first.checked = true;
});
}
dropzone.addEventListener("click", function () { fileInput.click(); });
dropzone.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fileInput.click(); }
});
fileInput.addEventListener("change", function () { acceptFile(fileInput.files[0]); });
["dragover", "dragenter"].forEach(function (ev) {
dropzone.addEventListener(ev, function (e) { e.preventDefault(); dropzone.classList.add("drag"); });
});
["dragleave", "drop"].forEach(function (ev) {
dropzone.addEventListener(ev, function (e) { e.preventDefault(); dropzone.classList.remove("drag"); });
});
dropzone.addEventListener("drop", function (e) {
if (e.dataTransfer && e.dataTransfer.files.length) acceptFile(e.dataTransfer.files[0]);
});
/* ---------- Cover letter counter ---------- */
var cover = document.getElementById("cover");
var coverCount = document.getElementById("coverCount");
cover.addEventListener("input", function () { coverCount.textContent = cover.value.length; });
/* clear field errors on input */
form.addEventListener("input", function (e) {
var f = e.target.closest(".field");
if (f && f.classList.contains("invalid")) setError(f, "");
});
/* ---------- Review builder ---------- */
function val(id) { var el = document.getElementById(id); return el ? (el.value || "").trim() : ""; }
function radioVal(name) {
var r = form.querySelector('input[name="' + name + '"]:checked');
return r ? r.value : "—";
}
function cap(s) { return s ? s.charAt(0).toUpperCase() + s.slice(1) : "—"; }
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
function row(k, v) {
return '<div class="review-row"><span>' + k + "</span><span>" + escapeHtml(v || "—") + "</span></div>";
}
function buildReview() {
var rl = document.getElementById("reviewList");
var coverTxt = val("cover");
rl.innerHTML =
group("Resume", 0,
row("Resume", chosenResume()) +
row("Portfolio", val("portfolio") || "Not provided")) +
group("Contact", 1,
row("Name", val("firstName") + " " + val("lastName")) +
row("Email", val("email")) +
row("Phone", val("phone")) +
row("Location", val("location"))) +
group("Screening", 2,
row("Work authorized", cap(radioVal("workAuth"))) +
row("Needs sponsorship", cap(radioVal("sponsor"))) +
row("Experience", val("experience")) +
row("Start date", val("start")) +
row("Cover letter", coverTxt ? coverTxt.length + " characters" : "Not provided"));
rl.querySelectorAll(".review-edit").forEach(function (b) {
b.addEventListener("click", function () { showPanel(parseInt(b.getAttribute("data-go"), 10)); });
});
}
function group(title, goTo, rows) {
return '<div class="review-group"><div class="review-h"><h4>' + title +
'</h4><button type="button" class="review-edit" data-go="' + goTo + '">Edit</button></div>' +
'<div class="review-rows">' + rows + "</div></div>";
}
/* ---------- Submit ---------- */
var consentErr = document.getElementById("consentErr");
form.addEventListener("submit", function (e) {
e.preventDefault();
var consent = document.getElementById("consent");
if (!consent.checked) {
consentErr.textContent = "You must confirm before submitting.";
consent.focus();
toast("Please confirm the statement.", "warn");
return;
}
consentErr.textContent = "";
var btn = document.getElementById("submitBtn");
btn.disabled = true;
btn.textContent = "Submitting…";
setTimeout(function () {
var ref = "NB-" + Math.floor(100000 + Math.random() * 899999);
document.getElementById("refId").textContent = ref;
steps.forEach(function (s) {
s.classList.add("is-done");
s.classList.remove("is-active");
s.querySelector(".step-dot").textContent = "✓";
});
panels.forEach(function (p) { p.hidden = p.getAttribute("data-panel") !== "done"; });
window.scrollTo({ top: 0, behavior: "smooth" });
toast("Application submitted!", "ok");
}, 1100);
});
document.getElementById("restartBtn").addEventListener("click", function () {
form.reset();
uploaded = null; preview.hidden = true; preview.innerHTML = "";
coverCount.textContent = "0";
var btn = document.getElementById("submitBtn");
btn.disabled = false; btn.textContent = "Submit application";
var first = form.querySelector('input[name="resumePick"]'); if (first) first.checked = true;
showPanel(0);
});
/* ---------- Misc ---------- */
document.getElementById("saveDraft").addEventListener("click", function () {
toast("Draft saved to your profile.", "ok");
});
var bm = document.getElementById("bookmark");
bm.addEventListener("click", function () {
var on = bm.getAttribute("aria-pressed") === "true";
bm.setAttribute("aria-pressed", String(!on));
bm.querySelector("span").textContent = on ? "Save job" : "Saved";
toast(on ? "Removed from saved jobs." : "Job saved.", on ? "" : "ok");
});
showPanel(0);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Apply — Senior Frontend Engineer · Northbeam</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="wrap topbar-inner">
<a class="brand" href="#" aria-label="Northbeam home">
<span class="brand-mark" aria-hidden="true">N</span>
<span class="brand-name">Northbeam</span>
</a>
<nav class="topnav" aria-label="Primary">
<a href="#">Jobs</a>
<a href="#">Companies</a>
<a href="#" class="topnav-active" aria-current="page">Applications</a>
</nav>
<button class="ghost-btn" type="button" id="saveDraft">Save draft</button>
</div>
</header>
<main class="wrap layout">
<!-- Application form -->
<form class="card form-card" id="applyForm" novalidate>
<div class="job-head">
<div class="logo" aria-hidden="true">NB</div>
<div class="job-meta">
<h1 class="job-title">Senior Frontend Engineer</h1>
<p class="job-sub">Northbeam · Engineering</p>
<div class="chips">
<span class="chip"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a7 7 0 0 0-7 7c0 5 7 13 7 13s7-8 7-13a7 7 0 0 0-7-7Zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5Z"/></svg>Austin, TX</span>
<span class="chip chip-remote">Remote OK</span>
<span class="chip">$160k – $195k</span>
<span class="chip">Full-time</span>
</div>
</div>
</div>
<!-- Progress -->
<ol class="steps" id="steps" aria-label="Application progress">
<li class="step is-active" data-step="0"><span class="step-dot">1</span><span class="step-label">Resume</span></li>
<li class="step" data-step="1"><span class="step-dot">2</span><span class="step-label">Contact</span></li>
<li class="step" data-step="2"><span class="step-dot">3</span><span class="step-label">Screening</span></li>
<li class="step" data-step="3"><span class="step-dot">4</span><span class="step-label">Review</span></li>
</ol>
<!-- SECTION 1: RESUME -->
<section class="panel is-active" data-panel="0" aria-labelledby="h-resume">
<h2 id="h-resume" class="panel-title">Resume & portfolio</h2>
<p class="panel-help">Upload a new resume or pick one saved to your profile.</p>
<div class="saved-resumes" role="radiogroup" aria-label="Saved resumes">
<label class="resume-pick">
<input type="radio" name="resumePick" value="Avery_Lin_Resume_2026.pdf" checked />
<span class="resume-pick-body">
<span class="resume-ic" aria-hidden="true">PDF</span>
<span class="resume-info"><strong>Avery_Lin_Resume_2026.pdf</strong><span class="muted">Updated Apr 2026 · 248 KB</span></span>
</span>
</label>
<label class="resume-pick">
<input type="radio" name="resumePick" value="Avery_Lin_Frontend.pdf" />
<span class="resume-pick-body">
<span class="resume-ic" aria-hidden="true">PDF</span>
<span class="resume-info"><strong>Avery_Lin_Frontend.pdf</strong><span class="muted">Updated Jan 2026 · 201 KB</span></span>
</span>
</label>
</div>
<div class="dropzone" id="dropzone" tabindex="0" role="button" aria-label="Upload a new resume file">
<input type="file" id="fileInput" accept=".pdf,.doc,.docx" hidden />
<svg class="dz-ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 16V4m0 0 4 4m-4-4-4 4M5 20h14"/></svg>
<p class="dz-main">Drag & drop or <span class="link">browse</span></p>
<p class="dz-sub">PDF, DOC, DOCX · up to 5 MB</p>
</div>
<div class="upload-preview" id="uploadPreview" hidden></div>
<div class="field">
<label for="portfolio">Portfolio / website <span class="opt">(optional)</span></label>
<input type="url" id="portfolio" name="portfolio" placeholder="https://averylin.design" autocomplete="url" />
</div>
<div class="panel-actions">
<button type="button" class="primary-btn" data-next>Continue</button>
</div>
</section>
<!-- SECTION 2: CONTACT -->
<section class="panel" data-panel="1" aria-labelledby="h-contact" hidden>
<h2 id="h-contact" class="panel-title">Contact information</h2>
<p class="panel-help">We autofilled this from your profile — edit anything that's out of date.</p>
<div class="grid-2">
<div class="field">
<label for="firstName">First name</label>
<input type="text" id="firstName" name="firstName" value="Avery" required autocomplete="given-name" />
<span class="err" data-err></span>
</div>
<div class="field">
<label for="lastName">Last name</label>
<input type="text" id="lastName" name="lastName" value="Lin" required autocomplete="family-name" />
<span class="err" data-err></span>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="avery.lin@example.com" required autocomplete="email" />
<span class="err" data-err></span>
</div>
<div class="field">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" value="(512) 555-0148" required autocomplete="tel" />
<span class="err" data-err></span>
</div>
<div class="field">
<label for="location">Current location</label>
<input type="text" id="location" name="location" value="Austin, TX" autocomplete="address-level2" />
</div>
<div class="field">
<label for="linkedin">LinkedIn <span class="opt">(optional)</span></label>
<input type="url" id="linkedin" name="linkedin" placeholder="linkedin.com/in/…" />
</div>
</div>
<div class="panel-actions">
<button type="button" class="ghost-btn" data-prev>Back</button>
<button type="button" class="primary-btn" data-next>Continue</button>
</div>
</section>
<!-- SECTION 3: SCREENING -->
<section class="panel" data-panel="2" aria-labelledby="h-screen" hidden>
<h2 id="h-screen" class="panel-title">Screening questions</h2>
<p class="panel-help">A few quick questions from the hiring team.</p>
<fieldset class="field">
<legend>Are you authorized to work in the United States?</legend>
<div class="radio-row">
<label class="radio-pill"><input type="radio" name="workAuth" value="yes" required /><span>Yes</span></label>
<label class="radio-pill"><input type="radio" name="workAuth" value="no" /><span>No</span></label>
</div>
<span class="err" data-err></span>
</fieldset>
<fieldset class="field">
<legend>Will you now or in the future require visa sponsorship?</legend>
<div class="radio-row">
<label class="radio-pill"><input type="radio" name="sponsor" value="no" required /><span>No</span></label>
<label class="radio-pill"><input type="radio" name="sponsor" value="yes" /><span>Yes</span></label>
</div>
<span class="err" data-err></span>
</fieldset>
<div class="field">
<label for="experience">Years of professional frontend experience</label>
<select id="experience" name="experience" required>
<option value="">Select…</option>
<option>Less than 2 years</option>
<option selected>2 – 4 years</option>
<option>5 – 7 years</option>
<option>8+ years</option>
</select>
<span class="err" data-err></span>
</div>
<div class="field">
<label for="start">Earliest start date</label>
<select id="start" name="start" required>
<option value="">Select…</option>
<option selected>Immediately</option>
<option>Within 2 weeks</option>
<option>Within 1 month</option>
<option>More than 1 month</option>
</select>
<span class="err" data-err></span>
</div>
<div class="field">
<label for="cover">Cover letter <span class="opt">(optional)</span></label>
<textarea id="cover" name="cover" rows="5" maxlength="1200" placeholder="What makes you a great fit for this role?"></textarea>
<span class="count"><span id="coverCount">0</span> / 1200</span>
</div>
<div class="panel-actions">
<button type="button" class="ghost-btn" data-prev>Back</button>
<button type="button" class="primary-btn" data-next>Review</button>
</div>
</section>
<!-- SECTION 4: REVIEW -->
<section class="panel" data-panel="3" aria-labelledby="h-review" hidden>
<h2 id="h-review" class="panel-title">Review & submit</h2>
<p class="panel-help">Double-check everything before sending your application.</p>
<div class="review" id="reviewList"></div>
<label class="consent">
<input type="checkbox" id="consent" name="consent" required />
<span>I confirm the information above is accurate and agree to Northbeam's <a href="#" class="link">candidate privacy policy</a>.</span>
</label>
<span class="err" data-err id="consentErr"></span>
<div class="panel-actions">
<button type="button" class="ghost-btn" data-prev>Back</button>
<button type="submit" class="primary-btn" id="submitBtn">Submit application</button>
</div>
</section>
<!-- SUCCESS -->
<section class="panel success" data-panel="done" aria-labelledby="h-done" hidden>
<div class="success-ic" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="m4 12 5 5L20 6"/></svg>
</div>
<h2 id="h-done" class="panel-title">Application sent</h2>
<p class="panel-help">Thanks, Avery. Your application for <strong>Senior Frontend Engineer</strong> at Northbeam is in. You'll get a confirmation email at <strong>avery.lin@example.com</strong>.</p>
<p class="ref">Reference <strong id="refId">NB-000000</strong></p>
<button type="button" class="ghost-btn" id="restartBtn">Back to jobs</button>
</section>
</form>
<!-- Aside summary -->
<aside class="card aside" aria-label="Job summary">
<div class="aside-logo" aria-hidden="true">NB</div>
<h3 class="aside-title">Senior Frontend Engineer</h3>
<p class="aside-co">Northbeam</p>
<ul class="aside-list">
<li><span class="muted">Team</span><span>Web Platform</span></li>
<li><span class="muted">Location</span><span>Austin, TX · Remote OK</span></li>
<li><span class="muted">Salary</span><span>$160k – $195k</span></li>
<li><span class="muted">Type</span><span>Full-time</span></li>
<li><span class="muted">Posted</span><span>3 days ago</span></li>
</ul>
<p class="aside-note">Northbeam typically responds within <strong>5 business days</strong>.</p>
<button type="button" class="bookmark" id="bookmark" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 4h12v16l-6-4-6 4Z"/></svg>
<span>Save job</span>
</button>
</aside>
</main>
<div class="toast-host" id="toastHost" aria-live="polite" aria-atomic="true"></div>
<script src="script.js"></script>
</body>
</html>Application Form
A four-step job application flow for a single posting — Senior Frontend Engineer at the fictional Northbeam. A progress stepper tracks Resume, Contact, Screening and Review, while a sticky sidebar keeps the salary, location and response-time summary in view. Each step validates before advancing, with required-field highlighting, email checks and required radio groups, surfacing problems through inline messages and a toast.
The resume step lets candidates pick a previously saved file or drag-and-drop a new one, with a live upload preview that validates type and size (PDF/DOC/DOCX up to 5 MB) and a remove control. Contact fields arrive autofilled from the candidate’s profile, screening uses radio pills and selects for work authorization, sponsorship, experience and start date, and the cover letter has a 1200-character live counter.
The review step renders a grouped summary with per-section Edit jumps and a consent checkbox, then a simulated submit transitions to a success state with a generated reference number. Supporting touches include a save-draft button, a save-job bookmark toggle, and a restart action — all in dependency-free vanilla JS.
Illustrative UI only — fictional jobs & companies, not a real hiring platform.