Job-Match Resume Starter one target job to editable draft
unsaved
`; const blob = new Blob([full], {type:'text/html'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `${name}-Resume.html`; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 200); } function plainResumeText() { updateStarterInputs(); const div = document.createElement('div'); div.innerHTML = renderResumeHTML(); return div.innerText.replace(/\n{3,}/g,'\n\n').trim(); } function selectedToolsList(limit = 8) { const tools = []; Object.values(state.skills || {}).forEach(group => { (group || []).forEach(tool => { if (tool && !tools.includes(tool)) tools.push(tool); }); }); return tools.slice(0, limit); } function firstProofBullet() { const bullets = (state.experience || []).flatMap(e => e.bullets || []); const proof = bullets.find(b => b && b.trim() && !b.includes('[')); return proof ? proof.trim() : ''; } function targetRoleLabel() { return state.target.jobTitle || state.personal.headline || 'remote role'; } function availabilityLine() { const parts = []; if (state.personal.hoursPerWeek) parts.push(`${state.personal.hoursPerWeek} hours per week`); if (state.personal.internetSpeed) parts.push(`${state.personal.internetSpeed} Mbps internet`); if (state.personal.backup === 'yes') parts.push('backup internet available'); return parts.join(', '); } function cleanResumeAngle(angle) { return String(angle || '').replace(/^Position your resume around\s+/i, '').replace(/[.]+$/g, '').trim(); } function sentenceCase(text) { const value = String(text || '').trim(); if (!value) return ''; return (value.charAt(0).toUpperCase() + value.slice(1)) .replace(/\bSeo\b/g, 'SEO') .replace(/\bSmm\b/g, 'SMM') .replace(/\bCrm\b/g, 'CRM') .replace(/\bVa\b/g, 'VA') .replace(/\bBpo\b/g, 'BPO'); } function applicationContext() { updateStarterInputs(); const catalogRole = currentCatalogRole(); const selectedTools = selectedToolsList(7); const tools = catalogRole && catalogRole.tools ? catalogRole.tools.slice(0, 5) : selectedTools; const role = targetRoleLabel(); const company = state.target.company || 'your team'; const angle = cleanResumeAngle(state.target.selectedJobResumeAngle); const proof = firstProofBullet() || angle || 'I can show a small work sample aligned with this role.'; const summary = state.summary || SUMMARY_TEMPLATES[currentArchetype()] || `I am preparing for ${role} work with honest proof, tool practice, and clear remote-work basics.`; const archetypeName = (ARCHETYPES[currentArchetype()].name || '').replace(/^The\s+/i, ''); return { name: state.personal.name || '[Your name]', role, company, summary, proof, tools: tools.length ? tools.join(', ') : '[tools from the job post]', availability: availabilityLine() || '[availability and internet speed]', email: state.personal.email || '[email]', archetype: archetypeName, jobSource: state.target.selectedJobSource || '', jobUrl: state.target.selectedJobUrl || '', resumeAngle: angle }; } function buildLinkedInAbout() { const c = applicationContext(); return [ `I help teams with ${c.role} work by combining my ${c.archetype.toLowerCase()} background with practical remote-work tools.`, '', c.summary, '', `Tools and strengths: ${c.tools}.`, `Proof point: ${c.proof}`, '', `Current target: ${c.role}.`, `Availability: ${c.availability}.`, '', 'Open to remote roles where clear communication, reliable follow-through, and honest skill growth matter.' ].join('\n'); } function buildOnlineJobsProfile() { const c = applicationContext(); return [ `Hi, I am ${c.name}. I am applying for ${c.role} work and I am positioning my background for practical remote support, digital marketing, and client operations tasks.`, '', `What I can help with: ${c.tools}.`, `Why I fit: ${c.proof}`, `Availability: ${c.availability}.`, '', 'I am comfortable following SOPs, documenting work, asking clear questions, and improving through feedback. I prefer honest expectations and measurable tasks over vague job promises.', '', `Best contact: ${c.email}.` ].join('\n'); } function buildEmailPitch() { const c = applicationContext(); const sourceLine = c.jobSource ? `I found the role through ${c.jobSource} and tailored this note around the responsibilities in that post.` : `I saw your opening for ${c.role} and wanted to send a focused application.`; const angleLine = c.resumeAngle ? `The main angle I would bring is ${c.resumeAngle}.` : ''; return [ `Subject: Application for ${c.role}${c.company && c.company !== 'your team' ? ` at ${c.company}` : ''}`, '', `Hi ${c.company},`, '', sourceLine, angleLine, `My background fits this role because ${c.proof}`, '', `The tools and responsibilities I can support include ${c.tools}. I am available for ${c.availability}, and I can also prepare a short work sample if you want to see how I would approach the role.`, '', 'I attached my resume for reference. Thank you for considering my application.', '', `Best,`, c.name ].join('\n'); } function buildShortProposal() { const c = applicationContext(); const source = c.jobSource ? `I saw this ${c.role} role on ${c.jobSource}.` : `I saw your ${c.role} post.`; const angle = c.resumeAngle ? `My strongest angle is ${sentenceCase(c.resumeAngle)}.` : `My strongest angle is reliable execution, clear updates, and honest proof of work.`; const proof = sentenceCase(c.proof); return [ `Hi ${c.company && c.company !== 'your team' ? c.company : 'there'},`, '', `${source} ${angle}`, '', `I can help with ${c.tools}.`, `Relevant proof: ${proof}`, '', `Availability: ${c.availability}.`, '', 'If helpful, I can send a small work sample matched to this role before the next step.', '', c.name ].join('\n'); } function copyText(label, text) { navigator.clipboard.writeText(text).then(() => { alert(`${label} copied. Review every bracket before sending.`); }).catch(() => { alert(`Could not copy ${label}. Select the resume text manually and try again.`); }); } function copyPlain() { copyText('Plain text resume', plainResumeText()); } function copyLinkedInAbout() { copyText('LinkedIn About', buildLinkedInAbout()); } function copyOnlineJobsProfile() { copyText('OnlineJobs.ph profile', buildOnlineJobsProfile()); } function copyEmailPitch() { copyText('Email pitch', buildEmailPitch()); } function copyShortProposal() { copyText('Short proposal', buildShortProposal()); } function updateProofDraft() { state.proofDraft = state.proofDraft || {}; const projectName = document.getElementById('proofProjectName'); const output = document.getElementById('proofOutput'); const metric = document.getElementById('proofMetric'); const notes = document.getElementById('proofNotes'); if (projectName) state.proofDraft.projectName = projectName.value.trim(); if (output) state.proofDraft.output = output.value.trim(); if (metric) state.proofDraft.metric = metric.value.trim(); if (notes) state.proofDraft.notes = notes.value.trim(); } function saveProofProject() { updateProofDraft(); const role = currentCatalogRole(); const draft = state.proofDraft || {}; const template = role && role.proofTemplate ? role.proofTemplate : null; const checklist = template && template.checklist ? template.checklist : (role && role.proofSteps ? role.proofSteps : []); const completedSteps = checklist.filter((step, index) => draft.checked && draft.checked[index]); const descriptionParts = []; if (draft.notes) descriptionParts.push(draft.notes); if (completedSteps.length) descriptionParts.push(`Completed checklist: ${completedSteps.join('; ')}`); const project = { name: draft.projectName || (template ? template.projectName : (role ? role.proofSample : 'Role-matched work sample')), description: descriptionParts.join('\n') || proofDraftBullet(role), scale: draft.metric || '', tools: role ? role.tools.join(', ') : selectedToolsList(4).join(', '), source: 'proof-builder' }; state.projects = state.projects || []; const existingIndex = state.projects.findIndex(item => item.source === 'proof-builder'); if (existingIndex >= 0) state.projects[existingIndex] = Object.assign({}, state.projects[existingIndex], project); else state.projects.unshift(project); if (window.BFF) window.BFF.writeState({ proofStatus: 'started', lastNextAction: 'use-proof-in-applications' }); try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch(e) {} saveState(); render(); } function copyProofBullet() { updateProofDraft(); copyText('Proof bullet', proofDraftBullet(currentCatalogRole())); } /* ========================================================= HANDLERS / BINDINGS ========================================================= */ function bindHandlers() { const liveJobSelect = document.getElementById('liveJobSelect'); if (liveJobSelect) { liveJobSelect.addEventListener('change', () => { state.target.selectedJobId = liveJobSelect.value; saveState(); if (liveJobSelect.value) pickLiveJob(liveJobSelect.value); else render(); }); } ['targetTitle','targetCompany','targetJD','p_name','p_email','p_speed','p_hours'].forEach(id => { const el = document.getElementById(id); if (!el) return; el.addEventListener('input', () => { updateStarterInputs(); saveState(); refreshStarterPreview(); }); }); const summaryInput = document.getElementById('summaryInput'); if (summaryInput) { summaryInput.addEventListener('input', () => { state.summary = summaryInput.value.trim(); saveState(); refreshStarterPreview(); }); } const existingResumeInput = document.getElementById('existingResumeInput'); if (existingResumeInput) { existingResumeInput.addEventListener('input', () => { state.existingResume = existingResumeInput.value; saveState(); }); } ['proofProjectName','proofOutput','proofMetric','proofNotes'].forEach(id => { const el = document.getElementById(id); if (!el) return; el.addEventListener('input', () => { updateProofDraft(); const preview = document.getElementById('proofBulletPreview'); if (preview) preview.textContent = proofDraftBullet(currentCatalogRole()); saveState(); }); }); document.querySelectorAll('[data-proof-step]').forEach(el => { el.addEventListener('change', () => { state.proofDraft = state.proofDraft || {}; state.proofDraft.checked = state.proofDraft.checked || {}; state.proofDraft.checked[el.dataset.proofStep] = el.checked; saveState(); }); }); // radios (Step 1 single-choice questions: q1, q4, q5, q6_remote, q6_corporate) document.querySelectorAll('input[type=radio]').forEach(el => { el.addEventListener('change', () => { const name = el.name, val = el.value; if (name in state.quiz) { state.quiz[name] = val; if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); render(); } }); }); // Q2 — per-tool savviness select (Confident/Expert auto-sync to Step 5; Basic stays in Q2 only) document.querySelectorAll('[data-q2-tool]').forEach(el => { el.addEventListener('change', () => { const tool = el.dataset.q2Tool; const lvl = el.value; state.quiz.q2_levels = state.quiz.q2_levels || {}; if (lvl) state.quiz.q2_levels[tool] = lvl; else delete state.quiz.q2_levels[tool]; // Sync to Step 5 skills: Confident + Expert = resume-listable; Basic doesn't sync const cat = TOOL_TO_CATEGORY[tool]; if (cat) { state.skills[cat] = state.skills[cat] || []; const listable = lvl === 'confident' || lvl === 'expert'; if (listable) { if (!state.skills[cat].includes(tool)) state.skills[cat].push(tool); } else { state.skills[cat] = state.skills[cat].filter(t => t !== tool); } } if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); render(); }); }); // Q3 — creative-tool savviness selects (Confident/Expert auto-syncs into Step 5) document.querySelectorAll('[data-q3-tool]').forEach(el => { el.addEventListener('change', () => { const tool = el.dataset.q3Tool; state.quiz.q3_creative = state.quiz.q3_creative || {}; const val = el.value; if (val) state.quiz.q3_creative[tool] = val; else delete state.quiz.q3_creative[tool]; // Confident+/Expert auto-add to Step 5; Basic or None removes (unless also picked in Q2) const cat = TOOL_TO_CATEGORY[tool]; if (cat) { state.skills[cat] = state.skills[cat] || []; const isConfident = val === 'confident' || val === 'expert'; const q2Level = (state.quiz.q2_levels||{})[tool]; const inQ2Listable = q2Level === 'confident' || q2Level === 'expert'; if (isConfident && !state.skills[cat].includes(tool)) { state.skills[cat].push(tool); } else if (!isConfident && !inQ2Listable) { state.skills[cat] = state.skills[cat].filter(t => t !== tool); } } if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); render(); }); }); // Q2 — per-category "None of these" toggle (clears all levels + disables inputs) document.querySelectorAll('[data-q2-none]').forEach(el => { el.addEventListener('change', () => { const catKey = el.dataset.q2None; state.quiz.q2_none = state.quiz.q2_none || {}; state.quiz.q2_levels = state.quiz.q2_levels || {}; const group = QUICK_TOOLS_GROUPED.find(g => g.key === catKey); const extras = parseExtras((state.quiz.q2_other||{})[catKey]||''); if (el.checked) { state.quiz.q2_none[catKey] = true; // clear all levels for canonical + extra tools in this category const allCatTools = [...(group?group.tools:[]), ...extras]; allCatTools.forEach(t => { delete state.quiz.q2_levels[t]; }); if (state.skills[catKey]) { state.skills[catKey] = state.skills[catKey].filter(t => !allCatTools.includes(t)); } if (state.quiz.q2_other) delete state.quiz.q2_other[catKey]; } else { delete state.quiz.q2_none[catKey]; } if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); render(); }); }); // Q2: per-category custom tool input with an explicit level. document.querySelectorAll('[data-q2-other]').forEach(el => { let typingTimer; const levelEl = document.querySelector(`[data-q2-other-level="${el.dataset.q2Other}"]`); const applyExtras = (rerender) => { const catKey = el.dataset.q2Other; const newExtras = parseExtras(el.value); const chosenLevel = levelEl ? levelEl.value : 'confident'; state.quiz.q2_other = state.quiz.q2_other || {}; state.quiz.q2_levels = state.quiz.q2_levels || {}; // remove previously-recorded extras from levels/skills const prevExtras = parseExtras(state.quiz.q2_other[catKey]||''); prevExtras.forEach(t => { if (!newExtras.includes(t)) { delete state.quiz.q2_levels[t]; if (state.skills[catKey]) { state.skills[catKey] = state.skills[catKey].filter(x => x !== t); } } }); // New extras use the level the candidate chose. Basic tools stay out of the resume skills list. newExtras.forEach(t => { if (!prevExtras.includes(t) || !state.quiz.q2_levels[t]) state.quiz.q2_levels[t] = chosenLevel; state.skills[catKey] = state.skills[catKey] || []; const listable = ['confident','expert'].includes(state.quiz.q2_levels[t]); if (listable && !state.skills[catKey].includes(t)) state.skills[catKey].push(t); if (!listable) state.skills[catKey] = state.skills[catKey].filter(x => x !== t); }); state.quiz.q2_other[catKey] = el.value; if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); if (rerender) render(); }; // live tally without focus loss el.addEventListener('input', () => { clearTimeout(typingTimer); typingTimer = setTimeout(() => applyExtras(false), 250); // update tally only, without taking focus away from the custom field const catKey = el.dataset.q2Other; const prevExtras = parseExtras((state.quiz.q2_other||{})[catKey]||''); const newExtras = parseExtras(el.value); const chosenLevel = levelEl ? levelEl.value : 'confident'; const tempLevels = Object.assign({}, state.quiz.q2_levels||{}); prevExtras.forEach(t => { if (!newExtras.includes(t)) delete tempLevels[t]; }); newExtras.forEach(t => { if (!tempLevels[t]) tempLevels[t] = chosenLevel; }); const eff = q2EffectiveCount(tempLevels); const t = q2TalliedCount(tempLevels); const tallyNum = document.querySelector('.q2-tally .num'); const tallyLbl = document.querySelector('.q2-tally .lbl'); if (tallyNum) tallyNum.textContent = Math.round(eff*10)/10; if (tallyLbl) tallyLbl.textContent = q2Bucket(eff); const chips = document.querySelectorAll('.q2-tally .breakdown .b-chip'); if (chips.length >= 3) { chips[0].textContent = `Expert: ${t.expert}`; chips[1].textContent = `Confident: ${t.confident}`; chips[2].textContent = `Basic: ${t.basic}`; } }); el.addEventListener('blur', event => { clearTimeout(typingTimer); const movingToLevel = event.relatedTarget && event.relatedTarget.dataset && event.relatedTarget.dataset.q2OtherLevel; applyExtras(!movingToLevel); }); if (levelEl) { levelEl.addEventListener('change', () => { const catKey = el.dataset.q2Other; const extras = parseExtras(el.value); state.quiz.q2_levels = state.quiz.q2_levels || {}; state.skills[catKey] = state.skills[catKey] || []; extras.forEach(t => { state.quiz.q2_levels[t] = levelEl.value; const listable = ['confident','expert'].includes(levelEl.value); if (listable && !state.skills[catKey].includes(t)) state.skills[catKey].push(t); if (!listable) state.skills[catKey] = state.skills[catKey].filter(x => x !== t); }); saveState(); render(); }); } }); // Q3 — per-group "None" toggle document.querySelectorAll('[data-q3-none]').forEach(el => { el.addEventListener('change', () => { const groupKey = el.dataset.q3None; state.quiz.q3_none = state.quiz.q3_none || {}; const group = CREATIVE_TOOLS_BY_GROUP.find(g => g.group.replace(/[^a-z]/gi,'').toLowerCase() === groupKey); if (el.checked) { state.quiz.q3_none[groupKey] = true; // clear ratings for this group's tools if (group) { group.tools.forEach(t => { if (state.quiz.q3_creative) delete state.quiz.q3_creative[t]; // remove from skills unless in Q2 const cat = TOOL_TO_CATEGORY[t]; const q2Level = (state.quiz.q2_levels||{})[t]; const inQ2Listable = q2Level === 'confident' || q2Level === 'expert'; if (cat && state.skills[cat] && !inQ2Listable) { state.skills[cat] = state.skills[cat].filter(x => x !== t); } }); } } else { delete state.quiz.q3_none[groupKey]; } if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); render(); }); }); // Q3 — "Other creative tools" textarea (one per line: "ToolName — level") const q3OtherEl = document.querySelector('[data-q3-other]'); if (q3OtherEl) { let q3Timer; const applyQ3Other = (rerender) => { const raw = q3OtherEl.value; const prev = parseQ3OtherEntries(state.quiz.q3_other||''); // remove previously-added entries from q3_creative + skills prev.forEach(({tool}) => { if (state.quiz.q3_creative) delete state.quiz.q3_creative[tool]; // strip from skills if not in Q2 elsewhere Object.keys(state.skills||{}).forEach(catKey => { const q2Level = (state.quiz.q2_levels||{})[tool]; const inQ2Listable = q2Level === 'confident' || q2Level === 'expert'; if (state.skills[catKey] && !inQ2Listable) { state.skills[catKey] = state.skills[catKey].filter(x => x !== tool); } }); }); // parse + add new entries const entries = parseQ3OtherEntries(raw); entries.forEach(({tool, level}) => { state.quiz.q3_creative = state.quiz.q3_creative || {}; state.quiz.q3_creative[tool] = level; if (level === 'confident' || level === 'expert') { // default custom creative tools to design category state.skills.design = state.skills.design || []; if (!state.skills.design.includes(tool)) state.skills.design.push(tool); } }); state.quiz.q3_other = raw; if (quizFilled(state.quiz)) state.archetype = scoreArchetype(state.quiz).winner; saveState(); if (rerender) render(); }; q3OtherEl.addEventListener('input', () => { clearTimeout(q3Timer); q3Timer = setTimeout(() => applyQ3Other(false), 350); }); q3OtherEl.addEventListener('blur', () => { clearTimeout(q3Timer); applyQ3Other(true); }); } // archetype override const archSel = document.getElementById('archOverride'); if (archSel) { archSel.addEventListener('change', () => { const v = archSel.value; if (v) { state.archetypeOverride = v; } else { state.archetypeOverride = null; } saveState(); render(); }); } // data-bind inputs (experience/education/etc nested paths) document.querySelectorAll('[data-bind]').forEach(el => { el.addEventListener('input', () => { const path = el.dataset.bind.split('.'); // e.g. experience.0.title const [collection, idx, field] = path; state[collection][idx][field] = el.value; saveState(); refreshStarterPreview(); }); // Fields marked data-rerender re-render the step on commit (datalist pick OR blur) // so suggested-bullets panels appear without typing-time focus loss. if (el.dataset.rerender) { el.addEventListener('change', () => render()); } }); // bullet textareas document.querySelectorAll('[data-bullet]').forEach(el => { el.addEventListener('input', () => { const [i, bi] = el.dataset.bullet.split('.').map(Number); state.experience[i].bullets[bi] = el.value; saveState(); refreshStarterPreview(); }); }); // Step 5 soft-skill chips document.querySelectorAll('[data-softskill]').forEach(el => { el.addEventListener('change', () => toggleSoftSkill(el.dataset.softskill)); }); // tool checkboxes document.querySelectorAll('[data-tool]').forEach(el => { el.addEventListener('change', () => { const [cat, tool] = el.dataset.tool.split('|'); const category = TOOL_CATEGORIES.find(item => item.key === cat); const isCustom = !category || !category.tools.includes(tool); state.skills[cat] = state.skills[cat] || []; if (el.checked) { if (!state.skills[cat].includes(tool)) state.skills[cat].push(tool); if (isCustom) { state.quiz.q2_levels = state.quiz.q2_levels || {}; state.quiz.q2_levels[tool] = state.quiz.q2_levels[tool] || 'confident'; } } else { state.skills[cat] = state.skills[cat].filter(t => t !== tool); if (isCustom) { if (state.quiz.q2_levels) delete state.quiz.q2_levels[tool]; const extras = parseExtras((state.quiz.q2_other||{})[cat]||'').filter(t => t !== tool); if (state.quiz.q2_other) { if (extras.length) state.quiz.q2_other[cat] = extras.join(', '); else delete state.quiz.q2_other[cat]; } } } saveState(); refreshStarterPreview(); // re-render audit only const audit = document.querySelector('.note.good, .note.warn, .note.alert'); if (audit && state.step === 5) { audit.outerHTML = renderSkillsAudit(); } }); }); } function refreshStarterPreview() { const preview = document.getElementById('preview'); if (preview) preview.innerHTML = renderResumeHTML(); } function addEntry(coll) { const blanks = { experience: { title:'', company:'', location:'', startDate:'', endDate:'', bullets:[''] }, projects: { name:'', description:'', scale:'', tools:'' }, education: { degree:'', school:'', location:'', year:'', notes:'' }, certifications: { name:'', issuer:'', year:'' }, volunteer: { role:'', org:'', period:'', bullets:'' } }; if (!state[coll]) state[coll] = []; state[coll].push(blanks[coll]); saveState(); render(); } function removeEntry(coll, i) { state[coll].splice(i, 1); saveState(); render(); } function addBullet(i) { state.experience[i].bullets = state.experience[i].bullets || []; state.experience[i].bullets.push(''); saveState(); render(); } function removeBullet(i, bi) { state.experience[i].bullets.splice(bi, 1); saveState(); render(); } function togglePanel(id) { document.getElementById(id).classList.toggle('open'); } function currentArchetype() { return state.archetypeOverride || state.archetype || 'generalist'; } function esc(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } /* ========================================================= BOOT ========================================================= */ render();