OpenJobs / index.html
Airmanv's picture
Upload 5 files
259f47b verified
Raw History Blame Contribute Delete
12 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Market Scanner | BOS Staffing</title>
<link rel="icon" href="bos-staffing-logo.webp">
<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=Archivo:wght@600;700;800&family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--navy:#17526c; --ink:#0e2a38; --green:#a5ca3a; --green-dk:#6f8e1f;
--paper:#f2f4f3; --surface:#ffffff; --mut:#64757e; --line:#dce3e2;
--no:#b04a32; --shadow:0 1px 2px rgba(14,42,56,.06), 0 8px 24px rgba(14,42,56,.05);
--display:"Archivo",system-ui,sans-serif; --body:"IBM Plex Sans",system-ui,sans-serif; --mono:"IBM Plex Mono",ui-monospace,monospace;
}
*{box-sizing:border-box}
html,body{margin:0}
body{background:var(--paper); color:var(--ink); font:15px/1.55 var(--body); -webkit-font-smoothing:antialiased}
a{color:var(--navy)}
/* ---------- header ---------- */
header{background:var(--surface); border-bottom:3px solid var(--green); position:sticky; top:0; z-index:20}
.hwrap{max-width:1180px; margin:0 auto; padding:14px 24px; display:flex; align-items:center; gap:18px; flex-wrap:wrap}
.brand{display:flex; align-items:center; gap:14px; min-width:0}
.brand img{height:34px; width:auto; display:block}
.brand .div{width:1px; height:26px; background:var(--line)}
.brand h1{margin:0; font:700 15px/1 var(--display); letter-spacing:.16em; text-transform:uppercase; color:var(--navy)}
.brand p{margin:4px 0 0; font-size:12px; color:var(--mut)}
#corpus{margin-left:auto; font:400 11.5px/1.5 var(--mono); color:var(--mut); text-align:right}
/* ---------- step rail ---------- */
.rail{max-width:1180px; margin:0 auto; padding:18px 24px 0; display:flex; gap:8px; flex-wrap:wrap}
.rn{display:flex; align-items:center; gap:9px; padding:7px 14px 7px 9px; border:1px solid var(--line); border-radius:2px; background:var(--surface); color:var(--mut); font:500 12.5px/1 var(--body)}
.rn b{font:500 11px/1 var(--mono); color:var(--mut); background:var(--paper); border-radius:2px; padding:5px 6px}
.rn.on{border-color:var(--navy); color:var(--navy); box-shadow:inset 3px 0 0 var(--green)}
.rn.on b{background:var(--navy); color:#fff}
.rn.done b{background:var(--green); color:var(--ink)}
main{max-width:1180px; margin:0 auto; padding:22px 24px 72px}
.step{display:none} .step.active{display:block}
h2{font:700 27px/1.2 var(--display); letter-spacing:-.015em; margin:14px 0 6px}
.lede{color:var(--mut); max-width:70ch; margin:0 0 18px}
.eyebrow{font:500 11px/1 var(--mono); letter-spacing:.14em; text-transform:uppercase; color:var(--green-dk)}
/* ---------- controls ---------- */
.panel{background:var(--surface); border:1px solid var(--line); border-radius:3px; padding:18px; box-shadow:var(--shadow)}
label.f{display:block; font:600 12px/1 var(--body); letter-spacing:.02em; color:var(--navy); margin:0 0 6px}
input[type=text],textarea{width:100%; background:var(--paper); color:var(--ink); border:1px solid var(--line); border-radius:2px; padding:10px 12px; font:15px/1.5 var(--body)}
textarea{min-height:210px; resize:vertical}
input:focus,textarea:focus{outline:2px solid var(--navy); outline-offset:1px; background:#fff}
.grid2{display:grid; grid-template-columns:1fr 1fr; gap:14px; margin-bottom:14px}
button{font:600 14px/1 var(--body); border:0; border-radius:2px; padding:12px 20px; cursor:pointer; background:var(--navy); color:#fff}
button:hover{background:#0e3d52} button:disabled{opacity:.45; cursor:default}
button.ghost{background:transparent; color:var(--navy); border:1px solid var(--line)}
button.ghost:hover{background:var(--surface); border-color:var(--navy)}
button.go{background:var(--green); color:var(--ink)} button.go:hover{background:#93b52f}
button:focus-visible,a:focus-visible{outline:2px solid var(--navy); outline-offset:2px}
.actions{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:14px}
.status{color:var(--mut); font:400 13px/1.5 var(--mono); min-height:20px; margin:10px 0}
.status.err{color:var(--no)}
/* ---------- groups ---------- */
.top{display:flex; justify-content:space-between; align-items:flex-end; gap:14px; flex-wrap:wrap; margin-bottom:6px}
.bar{height:3px; background:var(--line); border-radius:2px; overflow:hidden; margin:12px 0 4px}
.bar i{display:block; height:100%; background:var(--green); width:0; transition:width .25s}
.cards{display:grid; grid-template-columns:repeat(auto-fill,minmax(310px,1fr)); gap:14px; margin-top:16px}
.card{background:var(--surface); border:1px solid var(--line); border-top:3px solid var(--line); border-radius:3px; padding:15px; display:flex; flex-direction:column; gap:9px; box-shadow:var(--shadow)}
.card.maybe{border-top-color:var(--green)} .card.no{opacity:.4; border-top-color:var(--no)}
.card .lbl{font:600 16px/1.3 var(--display); color:var(--ink)}
.card .meta{font:400 11.5px/1.5 var(--mono); color:var(--mut)}
.card ul{margin:2px 0 0; padding:0; list-style:none; font-size:13.5px}
.card li{padding:4px 0; border-top:1px solid var(--line)}
.card li span{color:var(--mut); font-size:12.5px}
.card .btns{display:flex; gap:8px; margin-top:auto; padding-top:4px}
.card .btns button{flex:1; padding:9px}
/* ---------- results ---------- */
.toolbar{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:14px 0}
.seg{display:flex; border:1px solid var(--line); border-radius:2px; overflow:hidden; background:var(--surface)}
.seg button{background:transparent; color:var(--mut); padding:9px 16px; font-weight:500}
.seg button.on{background:var(--navy); color:#fff}
.job{background:var(--surface); border:1px solid var(--line); border-left:3px solid transparent; border-radius:3px; padding:13px 15px; margin:9px 0; display:flex; gap:16px; align-items:flex-start; cursor:pointer}
.job.pos{border-left-color:var(--green)} .job.neg{border-left-color:var(--no); opacity:.45}
.job.cur{outline:2px solid var(--navy); outline-offset:-1px}
.job .t{flex:1; min-width:0}
.job .t b{font:600 15.5px/1.35 var(--display)}
.job .co{color:var(--navy); font-weight:600; font-size:13.5px}
.job .loc{color:var(--mut); font-size:13px}
.job .jd{display:none; margin-top:10px; padding-top:10px; border-top:1px solid var(--line); color:#3d5560; font-size:13.5px; line-height:1.6; white-space:pre-wrap; max-height:340px; overflow:auto}
.job .m{width:74px; flex:none; text-align:right}
.job .m i{display:block; height:3px; background:var(--line); margin-top:5px; border-radius:2px; position:relative}
.job .m i em{position:absolute; inset:0 auto 0 0; background:var(--green); border-radius:2px}
.job .m span{font:500 12.5px/1 var(--mono); color:var(--navy)}
.comp{background:var(--surface); border:1px solid var(--line); border-radius:3px; padding:12px 15px; margin:9px 0; display:flex; gap:16px; align-items:center}
.comp .n{flex:1} .comp .n b{font:600 15px/1.3 var(--display)} .comp .n div{color:var(--mut); font-size:13px}
.comp .c{font:500 12.5px/1 var(--mono); color:var(--navy); white-space:nowrap}
.tag{display:inline-block; vertical-align:2px; margin-left:8px; padding:3px 7px; border:1px solid var(--line); border-radius:2px; background:var(--paper); font:500 10.5px/1 var(--mono); letter-spacing:.06em; text-transform:uppercase; color:var(--no)}
kbd{background:var(--paper); border:1px solid var(--line); border-bottom-width:2px; border-radius:3px; padding:2px 6px; font:500 11.5px/1.4 var(--mono)}
.keys{color:var(--mut); font-size:12.5px; display:flex; gap:10px; align-items:center; flex-wrap:wrap}
.empty{padding:34px; text-align:center; color:var(--mut); border:1px dashed var(--line); border-radius:3px}
footer{border-top:1px solid var(--line); margin-top:26px; padding:16px 24px 34px; color:var(--mut); font-size:12px; max-width:1180px; margin-left:auto; margin-right:auto}
@media (max-width:640px){
.grid2{grid-template-columns:1fr} h2{font-size:22px} #corpus{margin-left:0; text-align:left; width:100%}
.job .m{width:56px}
.comp{flex-direction:column; align-items:flex-start; gap:8px}
.comp .c{white-space:normal}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important; animation:none!important}}
</style>
</head>
<body>
<header>
<div class="hwrap">
<div class="brand">
<img src="bos-staffing-logo.webp" alt="BOS Staffing">
<div class="div"></div>
<div>
<h1>Market Scanner</h1>
<p>Who is hiring, right now, for the roles we fill</p>
</div>
</div>
<div id="corpus">index not loaded</div>
</div>
</header>
<nav class="rail" aria-label="Progress">
<div class="rn on" id="r1"><b>01</b> Describe the role</div>
<div class="rn" id="r2"><b>02</b> Pick the clusters</div>
<div class="rn" id="r3"><b>03</b> Rank and export</div>
</nav>
<main>
<section id="s1" class="step active">
<div class="eyebrow">Step one</div>
<h2>Describe the role you are tracking</h2>
<p class="lede">Paste a job order, a candidate resume, or a plain description of the work. The text is embedded once and the vector stays in this browser tab. Everything after that runs locally on your machine.</p>
<div class="panel">
<div class="grid2">
<div><label class="f" for="title">Target title (optional)</label><input id="title" type="text" placeholder="Forklift Operator, Staff Accountant, Administrative Assistant"></div>
<div><label class="f" for="loc">Location preference (optional)</label><input id="loc" type="text" placeholder="Athens GA, Atlanta metro, Remote US"></div>
</div>
<label class="f" for="resume">Job order, resume, or description</label>
<textarea id="resume" placeholder="Paste the requisition or resume here. More detail gives a sharper match than a job title alone."></textarea>
<div class="actions">
<button id="go" class="go">Scan the market</button>
<span class="status" id="st1">First scan downloads the job index, about 60 MB. It is cached after that.</span>
</div>
</div>
</section>
<section id="s2" class="step">
<div class="top">
<div>
<div class="eyebrow">Step two</div>
<h2>Nearest clusters</h2>
</div>
<div class="actions" style="margin:0">
<button class="ghost" id="more">Show more clusters</button>
<button id="next2" disabled>Rank the openings</button>
</div>
</div>
<p class="lede">Each card is a cluster of near identical postings, closest match first. <b>Keep</b> pulls that cluster down to this tab. <b>Skip</b> drops it and teaches the model what to rule out.</p>
<div class="bar"><i id="dlbar"></i></div>
<div class="status" id="dl"></div>
<div class="cards" id="cards"></div>
</section>
<section id="s3" class="step">
<div class="top">
<div>
<div class="eyebrow">Step three</div>
<h2>Ranked openings</h2>
</div>
<div class="keys"><kbd>J</kbd> keep <kbd>K</kbd> drop <kbd>Space</kbd> next <span>model refits on every keystroke</span></div>
</div>
<div class="status" id="st3"></div>
<div class="toolbar">
<div class="seg"><button id="vJobs" class="on">Openings</button><button id="vCos">Companies hiring</button></div>
<button class="ghost" id="csv">Download CSV</button>
<button class="ghost" id="more3">Show more</button>
</div>
<div id="jobs"></div>
<div id="cos" style="display:none"></div>
</section>
</main>
<footer>
BOS Staffing internal tool. Postings are aggregated from public company career pages and are refreshed daily by the open-jobs project. Resume and requisition text is sent once for embedding and is not stored by this app.
</footer>
<script src="app.js"></script>
</body>
</html>