CodeAntidote's picture
searchJobs Function
9015f75 verified
Raw History Blame Contribute Delete
62.3 kB
document.addEventListener("DOMContentLoaded", function () {
const searchButton = document.getElementById("searchJobsBtn");
const keywordInput = document.getElementById("jobKeyword");
const locationInput = document.getElementById("jobLocation");
const jobSource = document.getElementById("jobSource");
const resultsBox = document.getElementById("jobResults");
const selectedJobCard = document.getElementById("selectedJobCard");
const selectedJobTitle = document.getElementById("selectedJobTitle");
const selectedCompany = document.getElementById("selectedCompany");
const selectedLocation = document.getElementById("selectedLocation");
const jobDescription = document.getElementById("jobDescription");
const jobUrlInput = document.getElementById("jobUrl");
const openOriginalJobBtn = document.getElementById("openOriginalJobBtn");
const resumeFile = document.getElementById("resumeFile");
const resumeText = document.getElementById("resumeText");
const saveResumeBtn = document.getElementById("saveResumeBtn");
const resumeStatus = document.getElementById("resumeStatus");
const analyzeJobBtn = document.getElementById("analyzeJobBtn");
const atsScore = document.getElementById("atsScore");
const keywordScore = document.getElementById("keywordScore");
const matchedSkills = document.getElementById("matchedSkills");
const missingSkills = document.getElementById("missingSkills");
const analysisResults = document.getElementById("analysisResults");
const generateResumeBtn = document.getElementById("generateResumeBtn");
const copyResumeBtn = document.getElementById("copyResumeBtn");
const downloadResumeBtn = document.getElementById("downloadResumeBtn");
const generationStatus = document.getElementById("generationStatus");
const resumeOutput = document.getElementById("resumeOutput");
const interviewCheck = document.getElementById("interviewCheck");
let currentMatches = [];
const US_STATES = {
alabama: "AL",
alaska: "AK",
arizona: "AZ",
arkansas: "AR",
california: "CA",
colorado: "CO",
connecticut: "CT",
delaware: "DE",
florida: "FL",
georgia: "GA",
hawaii: "HI",
idaho: "ID",
illinois: "IL",
indiana: "IN",
iowa: "IA",
kansas: "KS",
kentucky: "KY",
louisiana: "LA",
maine: "ME",
maryland: "MD",
massachusetts: "MA",
michigan: "MI",
minnesota: "MN",
mississippi: "MS",
missouri: "MO",
montana: "MT",
nebraska: "NE",
nevada: "NV",
"new hampshire": "NH",
"new jersey": "NJ",
"new mexico": "NM",
"new york": "NY",
"north carolina": "NC",
"north dakota": "ND",
ohio: "OH",
oklahoma: "OK",
oregon: "OR",
pennsylvania: "PA",
"rhode island": "RI",
"south carolina": "SC",
"south dakota": "SD",
tennessee: "TN",
texas: "TX",
utah: "UT",
vermont: "VT",
virginia: "VA",
washington: "WA",
"west virginia": "WV",
wisconsin: "WI",
wyoming: "WY"
};
const STATE_NAMES_BY_CODE = Object.fromEntries(
Object.entries(US_STATES).map(([name, code]) => [code, name])
);
const savedResume = localStorage.getItem("jobalign_master_resume");
if (savedResume) {
resumeText.value = savedResume;
setResumeStatus(
"Master resume loaded from this browser",
"success"
);
}
searchButton.addEventListener(
"click",
searchJobs
);
keywordInput.addEventListener(
"keydown",
function (event) {
if (event.key === "Enter") {
searchJobs();
}
}
);
locationInput.addEventListener(
"keydown",
function (event) {
if (event.key === "Enter") {
searchJobs();
}
}
);
async function searchJobs() {
const keyword =
keywordInput.value.trim();
const location =
locationInput.value.trim();
if (!keyword) {
alert(
"Enter a job title or keyword."
);
keywordInput.focus();
return;
}
if (!location) {
alert(
"Enter a location such as San Jose, CA, San Francisco, CA, Utah, Texas, Germany, or Remote."
);
locationInput.focus();
return;
}
searchButton.disabled =
true;
searchButton.textContent =
"Searching...";
resultsBox.innerHTML = `
<div class="empty-state">
<div>
<strong>
Searching live jobs...
</strong>
<br><br>
${escapeHTML(keyword)}
<br>
${escapeHTML(location)}
</div>
</div>
`;
try {
const endpoint =
new URL(
"https://jobalign-adzuna-proxy.darbymjohn.workers.dev/search"
);
endpoint.searchParams.set(
"q",
keyword
);
endpoint.searchParams.set(
"location",
location
);
const response =
await fetch(
endpoint.toString(),
{
method:
"GET",
headers: {
"Accept":
"application/json"
}
}
);
const data =
await response.json();
if (!response.ok) {
throw new Error(
data.error ||
"Job search failed."
);
}
if (
!data.ok ||
!Array.isArray(
data.jobs
)
) {
throw new Error(
"The job service returned an invalid response."
);
}
currentMatches =
data.jobs
.map(
function (job) {
return {
title:
job.title ||
"",
company_name:
job.company ||
"Company not listed",
location:
job.location ||
location,
description:
job.description ||
"",
url:
job.url ||
"",
remote:
normalizeSimple(
job.location ||
""
).includes(
"remote"
),
tags:
[
job.category,
job.contractType,
job.contractTime
].filter(
Boolean
),
source:
"Adzuna",
salaryMin:
job.salaryMin,
salaryMax:
job.salaryMax,
created:
job.created
};
}
);
currentMatches =
currentMatches.filter(
function (job) {
return calculateRoleScore(
job,
keyword
) > 0;
}
);
renderJobs(
keyword,
location
);
} catch (error) {
console.error(
"JobAlign search error:",
error
);
resultsBox.innerHTML = `
<div
class="empty-state"
style="color:#ff7777;"
>
<div>
<strong>
Job search failed.
</strong>
<br><br>
${escapeHTML(
error.message
)}
</div>
</div>
`;
} finally {
searchButton.disabled =
false;
searchButton.textContent =
"Search Jobs";
}
}
async function fetchMuseJobs(
location,
remoteSearch
) {
const queries =
remoteSearch
? [
"Flexible / Remote"
]
: buildMuseLocationQueries(
location
);
const jobs =
[];
const seen =
new Set();
for (
const queryLocation
of queries
) {
for (
let page = 1;
page <= 5;
page++
) {
const url =
new URL(
"https://www.themuse.com/api/public/jobs"
);
url.searchParams.set(
"page",
String(page)
);
if (
queryLocation
) {
url.searchParams.append(
"location",
queryLocation
);
}
const response =
await fetch(
url.toString()
);
if (
!response.ok
) {
break;
}
const data =
await response.json();
if (
!Array.isArray(
data.results
) ||
data.results.length === 0
) {
break;
}
for (
const raw
of data.results
) {
const job =
normalizeMuseJob(
raw
);
const key =
job.url ||
(
job.company_name +
"|" +
job.title +
"|" +
job.location
);
if (
!seen.has(
key
)
) {
seen.add(
key
);
jobs.push(
job
);
}
}
if (
data.page_count &&
page >=
Number(
data.page_count
)
) {
break;
}
}
}
return jobs;
}
function buildMuseLocationQueries(
location
) {
const values =
[
location.trim()
];
const stateCode =
detectUSStateCode(
location
);
const stateName =
stateCode
? STATE_NAMES_BY_CODE[
stateCode
]
: "";
if (
stateName &&
!values.includes(
titleCase(
stateName
)
)
) {
values.push(
titleCase(
stateName
)
);
}
if (
stateCode &&
!values.includes(
stateCode
)
) {
values.push(
stateCode
);
}
return [
...new Set(
values
)
];
}
function normalizeMuseJob(
job
) {
const locations =
Array.isArray(
job.locations
)
? job.locations
.map(
function (item) {
return item.name;
}
)
.filter(
Boolean
)
: [];
const tags =
[];
if (
Array.isArray(
job.categories
)
) {
tags.push(
...job.categories
.map(
function (item) {
return item.name;
}
)
.filter(
Boolean
)
);
}
if (
Array.isArray(
job.levels
)
) {
tags.push(
...job.levels
.map(
function (item) {
return item.name;
}
)
.filter(
Boolean
)
);
}
return {
title:
job.name ||
"",
company_name:
job.company &&
job.company.name
? job.company.name
: "",
location:
locations.join(
" · "
),
description:
job.contents ||
"",
url:
job.refs &&
job.refs.landing_page
? job.refs.landing_page
: "",
remote:
locations.some(
function (value) {
return normalizeSimple(
value
).includes(
"remote"
);
}
),
tags:
tags,
source:
"The Muse"
};
}
async function fetchJobicyJobs(
keyword,
geo
) {
const url =
new URL(
"https://jobicy.com/api/v2/remote-jobs"
);
url.searchParams.set(
"count",
"200"
);
url.searchParams.set(
"tag",
keyword
);
if (
geo
) {
url.searchParams.set(
"geo",
geo
);
}
const response =
await fetch(
url.toString()
);
if (
!response.ok
) {
throw new Error(
"Jobicy returned HTTP " +
response.status
);
}
const data =
await response.json();
const jobs =
Array.isArray(
data.jobs
)
? data.jobs
: [];
return jobs.map(
function (job) {
return {
title:
job.jobTitle ||
"",
company_name:
job.companyName ||
"",
location:
job.jobGeo ||
"Remote",
description:
job.jobDescription ||
job.jobExcerpt ||
"",
url:
job.url ||
"",
remote:
true,
tags:
[]
.concat(
job.jobIndustry ||
[]
)
.concat(
job.jobType ||
[]
)
.concat(
job.jobLevel
? [
job.jobLevel
]
: []
),
source:
"Jobicy"
};
}
);
}
async function fetchRemoteOKJobs() {
const response =
await fetch(
"https://remoteok.com/api"
);
if (
!response.ok
) {
throw new Error(
"Remote OK returned HTTP " +
response.status
);
}
const data =
await response.json();
if (
!Array.isArray(
data
)
) {
return [];
}
return data
.filter(
function (job) {
return (
job &&
(
job.position ||
job.company
)
);
}
)
.map(
function (job) {
return {
title:
job.position ||
"",
company_name:
job.company ||
"",
location:
job.location ||
"Remote",
description:
job.description ||
"",
url:
job.url ||
"",
remote:
true,
tags:
Array.isArray(
job.tags
)
? job.tags
: [],
source:
"Remote OK"
};
}
);
}
function calculateRoleScore(
job,
keyword
) {
const title =
normalizeSimple(
job.title
);
const description =
normalizeSimple(
stripHTML(
job.description ||
""
)
);
const search =
normalizeSimple(
keyword
);
const ignored =
new Set([
"and",
"the",
"for",
"with",
"remote",
"hybrid"
]);
const words =
search
.split(" ")
.filter(
function (word) {
return (
word.length > 2 &&
!ignored.has(
word
)
);
}
);
if (
!words.length
) {
return 0;
}
let score =
0;
let titleMatches =
0;
if (
title.includes(
search
)
) {
score +=
180;
}
for (
const word
of words
) {
if (
title.includes(
word
)
) {
titleMatches +=
1;
score +=
55;
}
if (
description.includes(
word
)
) {
score +=
3;
}
}
if (
words.length === 1 &&
titleMatches === 0
) {
return 0;
}
if (
words.length === 2 &&
titleMatches < 2
) {
return 0;
}
if (
words.length >= 3
) {
const requiredMatches =
Math.ceil(
words.length *
0.6
);
if (
titleMatches <
requiredMatches
) {
return 0;
}
}
return score;
}
function locationMatches(
job,
requestedLocation
) {
const requested =
normalizeLocation(
requestedLocation
);
const actual =
normalizeLocation(
job.location ||
""
);
if (
isRemoteQuery(
requestedLocation
)
) {
return Boolean(
job.remote
);
}
if (
isBroadUS(
requestedLocation
)
) {
return looksLikeUSJobLocation(
job.location
);
}
if (
isBroadEurope(
requestedLocation
)
) {
return looksLikeEuropeLocation(
job.location
);
}
const stateCode =
detectUSStateCode(
requestedLocation
);
const city =
extractRequestedCity(
requestedLocation
);
if (
stateCode
) {
const stateName =
STATE_NAMES_BY_CODE[
stateCode
] ||
"";
const stateMatches =
locationContainsState(
job.location,
stateCode
) ||
actual.includes(
normalizeLocation(
stateName
)
);
if (
!stateMatches
) {
return false;
}
if (
city
) {
return actual.includes(
normalizeLocation(
city
)
);
}
return true;
}
if (
!requested ||
!actual
) {
return false;
}
if (
actual.includes(
requested
)
) {
return true;
}
const tokens =
getLocationTokens(
requested
);
return (
tokens.length > 0 &&
tokens.every(
function (token) {
return actual.includes(
token
);
}
)
);
}
function calculateLocationScore(
job,
requestedLocation
) {
if (
!locationMatches(
job,
requestedLocation
)
) {
return 0;
}
const requested =
normalizeLocation(
requestedLocation
);
const actual =
normalizeLocation(
job.location ||
""
);
if (
actual ===
requested
) {
return 300;
}
if (
requested &&
actual.includes(
requested
)
) {
return 250;
}
if (
job.remote
) {
return 180;
}
return 200;
}
function renderJobs(
keyword,
location
) {
if (
!currentMatches.length
) {
resultsBox.innerHTML = `
<div class="empty-state">
<div>
No matching
<strong>
${escapeHTML(keyword)}
</strong>
jobs were found for
<strong>
${escapeHTML(location)}
</strong>.
<br><br>
JobAlign will not substitute
jobs from an unrelated location.
</div>
</div>
`;
return;
}
const cards =
currentMatches
.map(
function (
job,
index
) {
return createJobCard(
job,
index
);
}
)
.join("");
resultsBox.innerHTML = `
<div style="margin-bottom:18px;">
<strong style="font-size:18px;">
${currentMatches.length}
matching jobs
</strong>
<div style="
color:#777;
margin-top:6px;
font-size:12px;
">
${escapeHTML(keyword)}
·
${escapeHTML(location)}
</div>
</div>
<div style="
display:grid;
gap:12px;
">
${cards}
</div>
`;
resultsBox
.querySelectorAll(
".select-job-btn"
)
.forEach(
function (button) {
button.addEventListener(
"click",
function () {
const index =
Number(
button.dataset.index
);
fillSelectedJob(
currentMatches[
index
]
);
}
);
}
);
}
function createJobCard(
job,
index
) {
const description =
stripHTML(
job.description ||
""
);
const excerpt =
description.length > 300
? description.slice(
0,
300
) +
"..."
: description;
const tags =
Array.isArray(
job.tags
)
? job.tags.slice(
0,
5
)
: [];
const tagHTML =
tags
.map(
function (tag) {
return `
<span style="
border:1px solid #292929;
border-radius:999px;
padding:5px 9px;
font-size:11px;
color:#aaa;
">
${escapeHTML(tag)}
</span>
`;
}
)
.join("");
return `
<div style="
padding:20px;
border:1px solid #2b2b2b;
border-radius:16px;
background:#070707;
">
<div style="
display:flex;
justify-content:space-between;
align-items:flex-start;
gap:20px;
flex-wrap:wrap;
">
<div style="
flex:1;
min-width:260px;
">
<div style="
color:#777;
font-size:11px;
text-transform:uppercase;
letter-spacing:1px;
margin-bottom:7px;
">
${escapeHTML(
job.company_name ||
"Company"
)}
</div>
<strong style="
display:block;
font-size:18px;
margin-bottom:8px;
">
${escapeHTML(
job.title ||
"Untitled role"
)}
</strong>
<div style="
color:#999;
font-size:13px;
margin-bottom:12px;
">
${escapeHTML(
job.location ||
(
job.remote
? "Remote"
: "Location not listed"
)
)}
</div>
<p style="
color:#888;
line-height:1.6;
font-size:13px;
">
${escapeHTML(excerpt)}
</p>
${
tags.length
? `
<div style="
display:flex;
flex-wrap:wrap;
gap:6px;
margin-top:14px;
">
${tagHTML}
</div>
`
: ""
}
<div style="
color:#555;
font-size:10px;
margin-top:13px;
">
Source:
${escapeHTML(
job.source ||
"Public feed"
)}
</div>
</div>
<button
class="primary-btn select-job-btn"
data-index="${index}"
type="button"
>
Select Job
</button>
</div>
</div>
`;
}
function fillSelectedJob(
job
) {
if (
!job
) {
alert(
"Unable to load this job."
);
return;
}
selectedJobTitle.value =
job.title ||
"";
selectedCompany.value =
job.company_name ||
"";
selectedLocation.value =
job.location ||
(
job.remote
? "Remote"
: ""
);
jobDescription.value =
stripHTML(
job.description ||
""
);
jobUrlInput.value =
job.url ||
"";
resetAnalysis();
generationStatus.innerHTML = `
<div class="status-dot"></div>
<span class="success-text">
Selected:
${escapeHTML(
job.title ||
""
)}
at
${escapeHTML(
job.company_name ||
""
)}
</span>
`;
analysisResults.innerHTML = `
<div class="empty-state">
<div>
Job loaded successfully.
<br><br>
Add your master resume
and click
<strong>
Analyze Selected Job
</strong>.
</div>
</div>
`;
selectedJobCard.scrollIntoView({
behavior:
"smooth",
block:
"start"
});
}
openOriginalJobBtn.addEventListener(
"click",
function () {
const url =
jobUrlInput.value.trim();
if (
!url
) {
alert(
"Select a job first."
);
return;
}
window.open(
url,
"_blank",
"noopener,noreferrer"
);
}
);
resumeFile.addEventListener(
"change",
async function () {
const file =
resumeFile.files[0];
if (
!file
) {
return;
}
const extension =
file.name
.split(".")
.pop()
.toLowerCase();
setResumeStatus(
"Reading " +
file.name +
"...",
"normal"
);
try {
if (
extension ===
"txt"
) {
const content =
await file.text();
saveExtractedResume(
content,
file.name
);
return;
}
if (
extension ===
"docx"
) {
if (
typeof mammoth ===
"undefined"
) {
throw new Error(
"DOCX reader did not load. Refresh the page and try again."
);
}
const arrayBuffer =
await file.arrayBuffer();
const result =
await mammoth.extractRawText({
arrayBuffer:
arrayBuffer
});
const content =
result.value.trim();
if (
content.length <
100
) {
throw new Error(
"Very little resume text was found in this DOCX."
);
}
saveExtractedResume(
content,
file.name
);
return;
}
if (
extension ===
"pdf"
) {
throw new Error(
"PDF reading is not enabled yet. Upload DOCX or TXT."
);
}
throw new Error(
"Use a DOCX or TXT resume."
);
} catch (error) {
console.error(
error
);
setResumeStatus(
"Could not read resume: " +
error.message,
"error"
);
}
}
);
function saveExtractedResume(
content,
fileName
) {
const clean =
String(
content ||
""
)
.replace(
/\r/g,
""
)
.replace(
/\n{3,}/g,
"\n\n"
)
.trim();
resumeText.value =
clean;
localStorage.setItem(
"jobalign_master_resume",
clean
);
localStorage.setItem(
"jobalign_master_resume_filename",
fileName
);
setResumeStatus(
fileName +
" loaded and saved successfully",
"success"
);
}
saveResumeBtn.addEventListener(
"click",
function () {
const resume =
resumeText.value.trim();
if (
resume.length <
100
) {
alert(
"Paste your complete master resume first."
);
return;
}
localStorage.setItem(
"jobalign_master_resume",
resume
);
setResumeStatus(
"Master resume saved in this browser",
"success"
);
}
);
const skills = [
"python",
"sql",
"java",
"javascript",
"typescript",
"c#",
".net",
"asp.net",
"angular",
"react",
"postgresql",
"mysql",
"mongodb",
"cassandra",
"couchbase",
"redis",
"oracle",
"sql server",
"aws",
"azure",
"gcp",
"rds",
"aurora",
"ec2",
"s3",
"lambda",
"glue",
"athena",
"terraform",
"ansible",
"docker",
"kubernetes",
"linux",
"bash",
"spark",
"pyspark",
"databricks",
"snowflake",
"dbt",
"airflow",
"kafka",
"kinesis",
"rabbitmq",
"sqs",
"etl",
"elt",
"cdc",
"data modeling",
"data warehouse",
"data lake",
"rest api",
"microservices",
"distributed systems",
"grafana",
"prometheus",
"opentelemetry",
"machine learning",
"pytorch",
"tensorflow",
"scikit-learn",
"mlflow",
"generative ai",
"genai",
"llm",
"rag",
"power bi",
"tableau",
"jira",
"confluence",
"roadmap",
"product management",
"stakeholder management",
"backlog",
"program management",
"technical program management",
"agile",
"scrum",
"high availability",
"disaster recovery",
"replication",
"performance tuning",
"query optimization",
"indexing"
];
analyzeJobBtn.addEventListener(
"click",
function () {
const jd =
jobDescription.value.toLowerCase();
const resume =
resumeText.value.toLowerCase();
if (
jd.length <
100
) {
alert(
"Search and select a job first."
);
return;
}
if (
resume.length <
100
) {
alert(
"Add your master resume first."
);
return;
}
const required =
skills.filter(
function (skill) {
return jd.includes(
skill
);
}
);
const matched =
required.filter(
function (skill) {
return resume.includes(
skill
);
}
);
const gaps =
required.filter(
function (skill) {
return !resume.includes(
skill
);
}
);
const coverage =
required.length
? Math.round(
(
matched.length /
required.length
) *
100
)
: 50;
const calculatedAts =
Math.min(
97,
Math.max(
25,
coverage
)
);
atsScore.textContent =
calculatedAts +
"%";
keywordScore.textContent =
coverage +
"%";
matchedSkills.textContent =
matched.length;
missingSkills.textContent =
gaps.length;
analysisResults.innerHTML = `
<div>
<strong>
Matching experience
</strong>
<p style="
color:#aaa;
margin-top:8px;
line-height:1.7;
">
${
matched.length
? escapeHTML(
matched.join(
", "
)
)
: "No major keyword matches detected."
}
</p>
</div>
<div style="margin-top:20px;">
<strong>
Job priorities not currently visible
</strong>
<p style="
color:#aaa;
margin-top:8px;
line-height:1.7;
">
${
gaps.length
? escapeHTML(
gaps.join(
", "
)
)
: "No obvious technical keyword gaps detected."
}
</p>
</div>
`;
}
);
generateResumeBtn.addEventListener(
"click",
async function () {
const jd =
jobDescription.value.trim();
const resume =
resumeText.value.trim();
const jobTitle =
selectedJobTitle.value.trim();
const company =
selectedCompany.value.trim();
const location =
selectedLocation.value.trim();
if (
jd.length <
100
) {
alert(
"Search for and select a job first."
);
return;
}
if (
resume.length <
100
) {
alert(
"Add your master resume first."
);
return;
}
generateResumeBtn.disabled =
true;
generateResumeBtn.textContent =
"Generating Resume...";
resumeOutput.textContent =
`Preparing JobAlign AI...
Target Role:
${jobTitle}
Company:
${company}`;
try {
let attempts =
0;
while (
!window.JobAlignAI &&
attempts <
150
) {
await new Promise(
function (resolve) {
setTimeout(
resolve,
100
);
}
);
attempts +=
1;
}
if (
!window.JobAlignAI
) {
throw new Error(
"The local AI engine did not load. Refresh the page and try again."
);
}
const rewrittenResume =
await window.JobAlignAI
.generateResume({
resume:
resume,
jobDescription:
jd,
jobTitle:
jobTitle,
company:
company,
location:
location
});
resumeOutput.textContent =
rewrittenResume;
localStorage.setItem(
"jobalign_last_generated_resume",
rewrittenResume
);
generationStatus.innerHTML = `
<div class="status-dot"></div>
<span class="success-text">
Resume generated for
${escapeHTML(
jobTitle
)}
at
${escapeHTML(
company
)}
</span>
`;
interviewCheck.innerHTML = `
<div>
<strong>
Targeted resume generated.
</strong>
<p style="
color:#aaa;
margin-top:10px;
line-height:1.7;
">
Review every technical claim
before submitting.
</p>
</div>
`;
} catch (error) {
console.error(
"JobAlign generation error:",
error
);
generationStatus.innerHTML = `
<div class="status-dot"></div>
<span class="danger-text">
Resume generation failed:
${escapeHTML(
error.message
)}
</span>
`;
resumeOutput.textContent =
`JobAlign could not generate the resume.
Error:
${error.message}`;
} finally {
generateResumeBtn.disabled =
false;
generateResumeBtn.textContent =
"Generate Resume For This Job";
}
}
);
copyResumeBtn.addEventListener(
"click",
async function () {
const text =
resumeOutput.innerText.trim();
if (
!text ||
text ===
"Your job-specific resume will appear here."
) {
alert(
"Generate a resume first."
);
return;
}
try {
await navigator
.clipboard
.writeText(
text
);
copyResumeBtn.textContent =
"Copied";
setTimeout(
function () {
copyResumeBtn.textContent =
"Copy Resume";
},
1500
);
} catch (error) {
alert(
"Your browser blocked clipboard access."
);
}
}
);
downloadResumeBtn.addEventListener(
"click",
function () {
const text =
resumeOutput.innerText.trim();
if (
!text ||
text ===
"Your job-specific resume will appear here."
) {
alert(
"Generate a resume first."
);
return;
}
const blob =
new Blob(
[
text
],
{
type:
"text/plain;charset=utf-8"
}
);
const url =
URL.createObjectURL(
blob
);
const link =
document.createElement(
"a"
);
link.href =
url;
link.download =
safeFileName(
selectedCompany.value ||
"Company"
) +
"_" +
safeFileName(
selectedJobTitle.value ||
"Resume"
) +
"_Resume.txt";
link.click();
URL.revokeObjectURL(
url
);
}
);
function isRemoteQuery(
location
) {
return normalizeSimple(
location
).includes(
"remote"
);
}
function isBroadUS(
location
) {
return [
"us",
"usa",
"united states",
"united states of america"
].includes(
normalizeSimple(
location
)
);
}
function isBroadEurope(
location
) {
return (
normalizeSimple(
location
) ===
"europe"
);
}
function looksLikeEuropeLocation(
location
) {
const value =
normalizeSimple(
location
);
const terms = [
"europe",
"germany",
"france",
"netherlands",
"sweden",
"switzerland",
"ireland",
"spain",
"italy",
"portugal",
"poland",
"denmark",
"norway",
"finland",
"austria",
"belgium",
"united kingdom",
"uk"
];
return terms.some(
function (term) {
return value.includes(
term
);
}
);
}
function looksLikeUSJobLocation(
location
) {
const stateCode =
detectUSStateCode(
location
);
if (
stateCode
) {
return true;
}
const value =
normalizeSimple(
location
);
return (
value.includes(
"united states"
) ||
value.includes(
"usa"
)
);
}
function detectUSStateCode(
location
) {
const normalized =
normalizeSimple(
location
);
for (
const [
name,
code
]
of Object.entries(
US_STATES
)
) {
if (
normalized ===
name ||
normalized.includes(
name
)
) {
return code;
}
}
const upper =
String(
location ||
""
).toUpperCase();
for (
const code
of Object.values(
US_STATES
)
) {
const pattern =
new RegExp(
"(^|[,\\s])" +
code +
"($|[,\\s])"
);
if (
pattern.test(
upper
)
) {
return code;
}
}
return "";
}
function extractRequestedCity(
location
) {
const code =
detectUSStateCode(
location
);
const stateName =
code
? STATE_NAMES_BY_CODE[
code
]
: "";
let city =
String(
location ||
""
)
.replace(
new RegExp(
"\\b" +
escapeRegExp(
code
) +
"\\b",
"i"
),
""
)
.replace(
new RegExp(
"\\b" +
escapeRegExp(
stateName
) +
"\\b",
"i"
),
""
)
.replace(
/united states|usa|u\.s\.a\.|u\.s\./gi,
""
)
.replace(
/,/g,
" "
)
.replace(
/\s+/g,
" "
)
.trim();
if (
!city ||
normalizeSimple(
city
) ===
normalizeSimple(
stateName
)
) {
return "";
}
return city;
}
function locationContainsState(
location,
code
) {
const upper =
String(
location ||
""
).toUpperCase();
const pattern =
new RegExp(
"(^|[,\\s])" +
code +
"($|[,\\s])"
);
return pattern.test(
upper
);
}
function normalizeLocation(
value
) {
return String(
value ||
""
)
.toLowerCase()
.replace(
/\busa\b/g,
"united states"
)
.replace(
/\bu\.s\.a\.\b/g,
"united states"
)
.replace(
/\bu\.s\.\b/g,
"united states"
)
.replace(
/[^a-z0-9\s]/g,
" "
)
.replace(
/\s+/g,
" "
)
.trim();
}
function getLocationTokens(
location
) {
const ignored =
new Set([
"the",
"of",
"area",
"metro",
"region"
]);
return String(
location ||
""
)
.split(" ")
.filter(
function (word) {
return (
word.length > 1 &&
!ignored.has(
word
)
);
}
);
}
function dedupeJobs(
jobs
) {
const seen =
new Set();
return jobs.filter(
function (job) {
const key =
[
normalizeSimple(
job.title
),
normalizeSimple(
job.company_name
),
normalizeSimple(
job.location
)
].join(
"|"
);
if (
seen.has(
key
)
) {
return false;
}
seen.add(
key
);
return true;
}
);
}
function stripHTML(
html
) {
const element =
document.createElement(
"div"
);
element.innerHTML =
String(
html ||
""
);
return (
element.textContent ||
element.innerText ||
""
)
.replace(
/\s+/g,
" "
)
.trim();
}
function normalizeSimple(
value
) {
return String(
value ||
""
)
.toLowerCase()
.replace(
/[^a-z0-9+#.]+/g,
" "
)
.replace(
/\s+/g,
" "
)
.trim();
}
function escapeHTML(
value
) {
const element =
document.createElement(
"div"
);
element.textContent =
String(
value ||
""
);
return element.innerHTML;
}
function escapeRegExp(
value
) {
return String(
value ||
""
).replace(
/[.*+?^${}()|[\]\\]/g,
"\\$&"
);
}
function safeFileName(
value
) {
return String(
value ||
""
)
.replace(
/[^a-z0-9]+/gi,
"_"
)
.replace(
/^_+|_+$/g,
""
);
}
function titleCase(
value
) {
return String(
value ||
""
)
.split(
" "
)
.map(
function (word) {
return word
? word[0]
.toUpperCase() +
word.slice(
1
)
: "";
}
)
.join(
" "
);
}
function setResumeStatus(
message,
type
) {
const className =
type ===
"success"
? "success-text"
: type ===
"error"
? "danger-text"
: type ===
"warning"
? "warning-text"
: "";
resumeStatus.innerHTML = `
<div class="status-dot"></div>
<span class="${className}">
${escapeHTML(message)}
</span>
`;
}
function resetAnalysis() {
atsScore.textContent =
"--";
keywordScore.textContent =
"--";
matchedSkills.textContent =
"--";
missingSkills.textContent =
"--";
}
});