Resume Generator A
Processing…
G
GEMI CAE™
Controlled Application Engine · v7
🇬🇧
0%
Readiness
Personal Information
Your core identity details
Professional Summary
3–5 lines that open every door
0 words
0 chars
ATS: —
Quick-fill by match:
Work Experience
Most recent first. Action verbs. Quantify.
Education
Degrees, diplomas, qualifications
Skills
Technical and professional competencies
Certifications
Professional qualifications & licences
Projects
Key achievements & independent work
Cover Letter
Country-tuned narrative to the employer
🎯 ATS Keyword Scanner
Paste a job description — get instant keyword match analysis
Applicant Tracking Systems scan for specific keywords before a human reads your CV. This engine analyses match rate per category and tells you exactly what to add.
🧠 Multi-Role Intelligence Engine
Target up to 3 roles simultaneously — blend for maximum reach
Enter up to 3 target job roles. The engine analyses keyword overlap, identifies core skills that appear across all roles, and generates a blended professional summary targeting all simultaneously.
1
2
🎤 Interview Preparation
Role-specific questions with STAR-method frameworks
💡 Click any question to expand the STAR framework. Use these to structure your answers: Situation → Task → Action → Result.
✉ Professional Email Generator
Personalised email templates from your CV data
Thank You
Post-interview follow-up
Follow-Up
1 week after applying
Salary Negotiate
Counter offer request
Offer Accept
Professional acceptance
Offer Decline
Graceful declination
Networking
Cold outreach / LinkedIn
Recruiter Reply
Respond to headhunters
Withdraw App
Professional withdrawal
🛂 Visa & Immigration Checklist
Country-specific visa requirements and documents
🌐 Portfolio Generator
One-click professional website from your CV data
0
Positions
0
Skills
0
Projects
✓
Midnight
Dark luxury · gold accents
✓
Arctic
Clean white · blue precision
✓
Graphite
Dark tech · teal accents
✓
Crimson
Bold creative · fire gradient
Click dots to set level · ● ● ● ● ● = Expert
Application Readiness0%
Complete all sections to unlock export
📄 CV / Résumé
✉️ Cover Letter
🌐 Portfolio
${name}
${val('inp-title')||''}
${[val('inp-email'),val('inp-phone'),val('inp-city'),val('inp-linkedin')].filter(Boolean).join(' | ')}
${val('inp-summary')?`Professional Summary
${val('inp-summary')}
`:''} ${STATE.experience.length?`Work Experience
${expSec}`:''} ${STATE.education.length?`Education
${eduSec}`:''} ${STATE.skills.length?`Skills
${skillsTxt}
`:''} ${STATE.certifications.length?`Certifications
${certSec}`:''} ${STATE.projects.length?`Projects
${projSec}`:''} ${clSection} `; const blob=new Blob([html],{type:'application/msword'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=(name.replace(/\s+/g,'_')||'CV')+'_GEMI_v5.doc'; a.click(); URL.revokeObjectURL(url); notify('Word document downloaded ✓','success'); } /* ════════════════════════════════════════ MODAL CONTROL ════════════════════════════════════════ */ function openModal(id){ document.getElementById(id).classList.add('open'); } function closeModal(id){ document.getElementById(id).classList.remove('open'); } function initModals(){ $$('[data-close]').forEach(btn=>btn.addEventListener('click',()=>closeModal(btn.dataset.close))); $$('.modal-overlay').forEach(o=>o.addEventListener('click',e=>{ if(e.target===o) o.classList.remove('open'); })); } /* ════════════════════════════════════════ WIRE ALL BUTTONS ════════════════════════════════════════ */ function initButtons(){ // Nav document.getElementById('btn-audit').addEventListener('click', openAuditModal); document.getElementById('audit-trigger').addEventListener('click', openAuditModal); document.getElementById('audit-autofix').addEventListener('click', ()=>{ autoFixAll(); closeModal('modal-audit'); }); document.getElementById('btn-autofix').addEventListener('click', autoFixAll); document.getElementById('btn-export-word').addEventListener('click', exportWord); document.getElementById('btn-export-pdf').addEventListener('click', ()=>{ setPreviewMode('cv'); setTimeout(()=>window.print(),300); }); document.getElementById('btn-grammar-header').addEventListener('click', ()=>openGrammarChecker(null)); // Entry add buttons ['exp','edu','cert','proj'].forEach(type=>{ document.getElementById('add-'+type+'-btn').addEventListener('click', ()=>({exp:addExpEntry,edu:addEduEntry,cert:addCertEntry,proj:addProjEntry})[type]()); document.getElementById('add-'+type+'-alt').addEventListener('click', ()=>({exp:addExpEntry,edu:addEduEntry,cert:addCertEntry,proj:addProjEntry})[type]()); }); // Humanizer $$('.mode-btn').forEach(btn=>btn.addEventListener('click',()=>{ $$('.mode-btn').forEach(b=>b.classList.remove('active')); btn.classList.add('active'); STATE.humanMode=btn.dataset.mode; const orig=document.getElementById('human-original').value; if(orig) runHumanize(orig,STATE.humanMode); })); document.getElementById('humanize-regen').addEventListener('click',()=>{ runHumanize(document.getElementById('human-original').value, STATE.humanMode); }); document.getElementById('humanize-apply').addEventListener('click',()=>{ const enhanced=document.getElementById('human-enhanced').value; if(STATE.currentHumanField){ const el=document.getElementById(STATE.currentHumanField); if(el){ el.value=enhanced; el.dispatchEvent(new Event('input')); } } closeModal('modal-humanizer'); notify('Humanized text applied ✓','success'); }); // Grammar tabs $$('.grammar-tab').forEach(tab=>tab.addEventListener('click',()=>{ $$('.grammar-tab').forEach(t=>t.classList.remove('active')); tab.classList.add('active'); ['gtab-checker','gtab-preview','gtab-rewrite'].forEach(id=>document.getElementById(id).style.display='none'); document.getElementById('gtab-'+tab.dataset.gtab).style.display=''; })); document.getElementById('grammar-run-btn').addEventListener('click', runGrammarCheck); document.getElementById('grammar-clear-btn').addEventListener('click',()=>{ document.getElementById('grammar-input').value=''; document.getElementById('grammar-issues-container').innerHTML='Paste text above and click Analyse.
';
document.getElementById('grammar-stats-row').style.display='none';
});
document.getElementById('grammar-fix-errors').addEventListener('click',()=>{
const ta=document.getElementById('grammar-input');
ta.value=GRAMMAR.autoCorrect(ta.value,['error']);
runGrammarCheck(); notify('Errors fixed ✓','success');
});
document.getElementById('grammar-fix-all').addEventListener('click',()=>{
const ta=document.getElementById('grammar-input');
ta.value=GRAMMAR.autoCorrect(ta.value,['error','warning']);
runGrammarCheck(); notify('Errors & warnings fixed ✓','success');
});
document.getElementById('grammar-apply-rewrite').addEventListener('click',()=>{
const rewritten=document.getElementById('rewrite-output').value;
if(STATE.currentGrammarField){
const el=document.getElementById(STATE.currentGrammarField);
if(el){ el.value=rewritten; el.dispatchEvent(new Event('input')); }
}
closeModal('modal-grammar'); notify('Smart rewrite applied ✓','success');
});
// Delegated: humanize / pick / grammar buttons
document.addEventListener('click', e=>{
const hb=e.target.closest('[data-humanize]');
if(hb) openHumanizer(hb.dataset.humanize);
const pb=e.target.closest('[data-pick]');
if(pb){ const type=pb.dataset.pick; openPromptPicker(type, document.getElementById(type==='summary'?'inp-summary':type)); }
const gb=e.target.closest('[data-grammar]');
if(gb) openGrammarChecker(gb.dataset.grammar);
const pt=e.target.closest('#pick-title');
if(pt) openPromptPicker('titles', document.getElementById('inp-title'));
});
}
/* ════════════════════════════════════════
LIVE INPUT BINDINGS
════════════════════════════════════════ */
function initLiveInputs(){
['inp-firstname','inp-lastname','inp-title','inp-email','inp-phone','inp-city','inp-linkedin','inp-summary','inp-target-role','inp-target-company','cl-manager','cl-company','cl-position','cl-opening','cl-body','cl-closing'].forEach(id=>{
const el=document.getElementById(id);
if(el) el.addEventListener('input',()=>{ updatePreview(); updateReadiness(); });
});
document.getElementById('inp-industry').addEventListener('change',()=>{
STATE.prof=val('inp-industry');
refreshAllPills(); updateContextBadges(); updateCLContextBar(); updatePreview();
});
document.getElementById('inp-level').addEventListener('change',()=>{ loadSummaryPills(); });
}
/* ════════════════════════════════════════
INIT
════════════════════════════════════════ */
function init(){
initNav(); initCountry(); initSkills();
initLiveInputs(); initModals(); initButtons();
refreshAllPills(); updateContextBadges(); updateCLContextBar();
renderSkillsetButtons(); updatePreview(); updateReadiness();
/* ════════════════════════════════════════
INIT
════════════════════════════════════════ */
function init(){
initNav(); initCountry(); initSkills();
initLiveInputs(); initModals(); initButtons();
refreshAllPills(); updateContextBadges(); updateCLContextBar();
renderSkillsetButtons(); updatePreview(); updateReadiness();
setTimeout(()=>notify('GEMI CAE v10 ready — Select country + industry to align all templates · 🌐 Portfolio tab unlocked',''),700);
}
/* ════════════════════════════════════════════════════════════════
v9 — SESSION SAVE / LOAD (localStorage persistence)
════════════════════════════════════════════════════════════════ */
function saveSession(){
try{
const d={
v:'v9',ts:new Date().toISOString(),
country:STATE.country,prof:STATE.prof,
f:{
fn:val('inp-firstname'),ln:val('inp-lastname'),title:val('inp-title'),
email:val('inp-email'),phone:val('inp-phone'),city:val('inp-city'),
linkedin:val('inp-linkedin'),industry:val('inp-industry'),level:val('inp-level'),
summary:val('inp-summary'),tRole:val('inp-target-role'),tCo:val('inp-target-company'),
clMgr:val('cl-manager'),clCo:val('cl-company'),clPos:val('cl-position'),
clOp:val('cl-opening'),clBd:val('cl-body'),clCl:val('cl-closing'),
pfTag:val('pf-tagline'),pfGh:val('pf-github'),pfLi:val('pf-linkedin-url'),
pfCta:val('pf-cta-link'),pfYrs:val('pf-years')
},
exp:STATE.experience,edu:STATE.education,
sk:STATE.skills,cert:STATE.certifications,proj:STATE.projects,
expCtr:STATE.expCtr,eduCtr:STATE.eduCtr,certCtr:STATE.certCtr,projCtr:STATE.projCtr,
pfTheme:(typeof PF_STATE!=='undefined'?PF_STATE.theme:'midnight')
};
localStorage.setItem('gemi_cae_v10',JSON.stringify(d));
notify('Session saved ✓ — Data preserved in browser','success');
}catch(e){notify('Save failed: '+e.message,'');}
}
function loadSession(){
try{
const raw=localStorage.getItem('gemi_cae_v10');
if(!raw){notify('No saved session found','');return;}
const d=JSON.parse(raw);
if(!d.f){notify('Invalid session data','');return;}
const f=d.f;
const fmap={
'inp-firstname':'fn','inp-lastname':'ln','inp-title':'title',
'inp-email':'email','inp-phone':'phone','inp-city':'city',
'inp-linkedin':'linkedin','inp-industry':'industry','inp-level':'level',
'inp-summary':'summary','inp-target-role':'tRole','inp-target-company':'tCo',
'cl-manager':'clMgr','cl-company':'clCo','cl-position':'clPos',
'cl-opening':'clOp','cl-body':'clBd','cl-closing':'clCl',
'pf-tagline':'pfTag','pf-github':'pfGh','pf-linkedin-url':'pfLi',
'pf-cta-link':'pfCta','pf-years':'pfYrs'
};
Object.entries(fmap).forEach(([elId,fKey])=>{if(f[fKey])setVal(elId,f[fKey]);});
// Restore state
if(d.country){STATE.country=d.country;document.getElementById('country-select').value=d.country;applyCountry(d.country);}
if(d.prof)STATE.prof=d.prof;
STATE.skills=d.sk||[];
STATE.expCtr=d.expCtr||0;STATE.eduCtr=d.eduCtr||0;
STATE.certCtr=d.certCtr||0;STATE.projCtr=d.projCtr||0;
// Re-render entries
document.getElementById('exp-entries').innerHTML='';
document.getElementById('edu-entries').innerHTML='';
document.getElementById('cert-entries').innerHTML='';
document.getElementById('proj-entries').innerHTML='';
STATE.experience=[];STATE.education=[];STATE.certifications=[];STATE.projects=[];
(d.exp||[]).forEach(e=>addExpEntry(e));
(d.edu||[]).forEach(e=>addEduEntry(e));
(d.cert||[]).forEach(e=>addCertEntry(e));
(d.proj||[]).forEach(e=>addProjEntry(e));
renderSkillTags();
if(d.pfTheme&&typeof PF_STATE!=='undefined')PF_STATE.theme=d.pfTheme;
updatePreview();updateReadiness();refreshAllPills();updateContextBadges();
const ts=new Date(d.ts).toLocaleString('en-GB',{day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'});
notify('Session restored ✓ (saved '+ts+')','success');
}catch(e){notify('Load failed: '+e.message,'');}
}
function exportSessionJSON(){
const raw=localStorage.getItem('gemi_cae_v10');
if(!raw){saveSession();return;}
const nm=val('inp-firstname')||'Session';
const blob=new Blob([raw],{type:'application/json'});
const url=URL.createObjectURL(blob);
const a=document.createElement('a');
a.href=url;a.download=nm+'_GEMI_v9_session.json';
a.click();URL.revokeObjectURL(url);
}
/* ════════════════════════════════════════════════════════════════
v9 — AI ENHANCEMENT ENGINE (Anthropic Claude API)
════════════════════════════════════════════════════════════════ */
function getApiKey(){return localStorage.getItem('gemi_api_key')||'';}
function setApiKey(k){localStorage.setItem('gemi_api_key',k);}
function updateAiStatusUI(){
const k=getApiKey();
const st=document.getElementById('ai-key-status');
if(st){
if(k){
st.className='ai-status connected';
st.textContent='✅ API key configured — AI features active';
} else {
st.className='ai-status disconnected';
st.textContent='❌ No API key — Add your Anthropic key to enable AI features';
}
}
}
async function aiEnhanceField(fieldId,mode){
const apiKey=getApiKey();
if(!apiKey){openModal('modal-ai-settings');notify('Add your Anthropic API key to use AI features','');return;}
const text=val(fieldId);
if(!text||text.length<20){notify('Add some content first (minimum 20 characters)','');return;}
const btn=document.querySelector('[data-ai-enhance="'+fieldId+'"]');
if(btn){btn.classList.add('loading');btn.textContent='⏳ Writing...';}
const country=COUNTRIES[STATE.country]?.name||STATE.country;
const industry=profLabel(getProfKey());
const level=val('inp-level')||'mid';
const enhMode=document.getElementById('ai-enhance-mode')?.value||'professional';
const modeInstructions={
professional:'Write in a polished, professional tone appropriate for formal job applications.',
executive:'Write in a commanding, strategic tone befitting C-suite and senior leadership applications.',
warm:'Write in a warm, authentic, personable tone that feels genuine and human.',
concise:'Write concisely — remove all filler, every word must earn its place.'
};
const prompts={
summary:`You are an expert CV writer specialising in ${country} job applications for ${industry} professionals at ${level} level. Rewrite the following professional summary to be significantly more compelling, impactful, and ATS-optimised for ${country} employers. ${modeInstructions[enhMode]} Use specific, active language. Quantify impact where possible. Keep it 3-5 sentences. Return ONLY the rewritten summary, nothing else.\n\nOriginal:\n${text}`,
'cl-opening':`You are an expert cover letter writer for ${country} job applications. Rewrite this opening paragraph to be more compelling and country-appropriate for ${country}. ${modeInstructions[enhMode]} It should immediately establish value and create interest. Return ONLY the rewritten paragraph.\n\nOriginal:\n${text}`,
'cl-body':`You are an expert cover letter writer. Rewrite this cover letter body paragraph to be more persuasive and achievement-focused for a ${industry} professional applying in ${country}. ${modeInstructions[enhMode]} Return ONLY the rewritten paragraph.\n\nOriginal:\n${text}`,
default:`You are an expert professional writer specialising in ${country} job applications. Rewrite the following text to be more compelling, professional, and impactful. ${modeInstructions[enhMode]} Return ONLY the rewritten content, nothing else.\n\nOriginal:\n${text}`
};
const prompt=prompts[mode]||prompts.default;
try{
const resp=await fetch('https://api.anthropic.com/v1/messages',{
method:'POST',
headers:{
'Content-Type':'application/json',
'x-api-key':apiKey,
'anthropic-version':'2023-06-01',
'anthropic-dangerous-direct-browser-access':'true'
},
body:JSON.stringify({
model:'claude-sonnet-4-20250514',
max_tokens:800,
messages:[{role:'user',content:prompt}]
})
});
if(!resp.ok){const e=await resp.json();throw new Error(e.error?.message||resp.statusText);}
const data=await resp.json();
const enhanced=(data.content||[]).find(b=>b.type==='text')?.text||'';
if(enhanced){
setVal(fieldId,enhanced.trim());
document.getElementById(fieldId)?.dispatchEvent(new Event('input'));
updatePreview();updateReadiness();
notify('AI rewrite applied ✓','success');
}
}catch(e){
notify('AI error: '+e.message,'');
console.error('AI enhance error:',e);
}finally{
if(btn){btn.classList.remove('loading');btn.textContent='🤖 AI Write';}
}
}
/* ════════════════════════════════════════════════════════════════
v9 — INTERVIEW PREP ENGINE
════════════════════════════════════════════════════════════════ */
const IQ_DATA={
behavioral:[
{q:'Tell me about a time when you had to deal with a difficult stakeholder or team member. How did you handle it?',s:'Describe the conflicting interests or difficult behaviour',t:'Explain your responsibility to resolve the situation',a:'Detail the steps you took — listening, mediating, escalating if needed',r:'Share the outcome and what you learned'},
{q:'Describe a situation where you had to deliver results under significant time pressure. What did you do?',s:'Set the scene — what was the deadline and what was at stake',t:'Clarify what YOU were responsible for specifically',a:'Walk through your prioritisation and execution approach',r:'State what was delivered and the impact'},
{q:'Give me an example of a time when you took initiative without being asked. What was the outcome?',s:'Describe what you noticed that others had missed or ignored',t:'Explain why you felt it was important to act',a:'Describe exactly what you did and how',r:'Share the measurable or tangible outcome'},
{q:'Tell me about a time you made a significant mistake at work. How did you handle it?',s:'Be honest about the error — context matters',t:'What were you trying to achieve when it went wrong?',a:'Describe how you took ownership and fixed or mitigated it',r:'What did you learn and implement to prevent recurrence?'},
{q:'Describe a time when you successfully influenced people who did not directly report to you.',s:'Identify the stakeholders and why you had no direct authority',t:'Explain what you needed them to do and why they were resistant',a:'Describe your influence strategy — data, rapport, framing',r:'What was the outcome of your persuasion?'}
],
situational:[
{q:'You have three urgent priorities and a deadline for all of them in the same afternoon. What do you do?',s:'Briefly acknowledge the reality of competing demands',t:'Clarify which is most business-critical and why',a:'Communicate proactively to stakeholders and negotiate timelines',r:'Describe your decision framework'},
{q:'You discover a serious error in a financial report / deliverable that has already been submitted. What do you do?',s:'Acknowledge the discovery and its potential impact',t:'Take ownership immediately — no blame others',a:'Escalate promptly, propose a correction plan',r:'Describe how you would prevent recurrence'},
{q:'Your manager asks you to do something you believe is ethically questionable. How do you respond?',s:'Describe raising the concern professionally and privately',t:'Be specific about using appropriate channels (line manager, HR)',a:'Request clarification on the reasoning and express your concern clearly',r:'Know your red lines — when escalation is appropriate'}
],
finance:[
{q:'Walk me through how you would build a three-statement financial model from scratch.',s:'Context: what business type and what is the model for?',t:'Explain starting with assumptions and drivers',a:'P&L → Balance Sheet → Cash Flow statement linkage',r:'What validation checks do you always apply?'},
{q:'How do you ensure accuracy in your financial reporting under time pressure?',s:'Describe a realistic reporting environment',t:'Your specific accountability in the process',a:'Your controls: reconciliations, peer review, version control',r:'Track record of clean audits or error-free closes'},
{q:'A business unit is significantly over budget mid-year. Walk me through how you would investigate and report on this.',s:'Gather the data: actuals vs budget by line item',t:'Understand the root cause: timing, overspend, budget error?',a:'Quantify the variance, prepare a structured variance analysis',r:'Present options: reforecast, cost reduction, carry forward'}
],
tech:[
{q:'Walk me through how you would design a scalable API that needs to handle 10 million requests per day.',s:'Confirm requirements: read-heavy or write-heavy? Latency targets?',t:'Design the data model and service boundaries',a:'Discuss caching, load balancing, horizontal scaling, async processing',r:'Address monitoring, alerting, and graceful degradation'},
{q:'How do you approach debugging a critical production issue under pressure?',s:'Start with scope: what is broken, who is affected?',t:'Triage severity and communicate to stakeholders immediately',a:'Isolate via logs, metrics, recent deploys — form hypothesis, test',r:'Fix, post-mortem, runbook update'},
{q:'Describe your approach to managing technical debt in a fast-moving team.',s:'What is the current state of the codebase?',t:'Prioritise debt that blocks performance or security',a:'Carve out sprint capacity, track in backlog, make it visible',r:'Balance velocity with code health metrics'}
],
health:[
{q:'Describe a time when you had to advocate for a patient when their care was at risk.',s:'What was the patient\'s condition and what was the risk?',t:'Your professional duty to escalate',a:'How you communicated concern to the team, documented, escalated',r:'Patient outcome and what you reported/learned'},
{q:'How do you manage your workload when the ward is understaffed and patient acuity is high?',s:'Acknowledge the realistic scenario',t:'Your responsibility to keep patients safe',a:'Triage by acuity, delegate appropriately, communicate to coordinator',r:'No patient harmed, colleagues supported'},
{q:'Tell me about a time you had a disagreement with a doctor or senior clinician about patient care.',s:'Describe the clinical situation and your concern',t:'Your professional accountability to raise concerns',a:'Used assertive communication (SBAR), escalated appropriately',r:'Resolution and patient safety outcome'}
],
general:[
{q:'Why are you interested in this particular role at this organisation?',s:'What attracted you to this specific company',t:'What the role offers professionally',a:'How it aligns with your skills and trajectory',r:'What value you will bring'},
{q:'Where do you see yourself professionally in five years?',s:'Brief context of where you are now',t:'The specific direction you are building toward',a:'How this role is a stepping stone',r:'Your commitment to the organisation'},
{q:'What is your greatest professional strength, and can you give me a specific example of it in action?',s:'Name the strength — be specific, not generic',t:'Describe a situation that tested it',a:'How you applied the strength',r:'Quantifiable or tangible outcome'},
{q:'What do you consider your most significant professional achievement to date?',s:'Set the context: what was the challenge?',t:'What were the stakes?',a:'What YOU specifically did',r:'The measurable outcome and recognition received'}
]
};
function generateInterviewQuestions(){
const prof=getProfKey()||'general';
const role=val('inp-target-role')||val('inp-title')||'the target role';
const co=val('inp-target-company')||'the company';
const country=COUNTRIES[STATE.country]?.name||STATE.country;
const bar=document.getElementById('iq-context-bar');
if(bar) bar.innerHTML='Preparing '+esc(role)+' interview questions aligned to '+country+' hiring standards. Click each question to reveal the STAR framework.';
const container=document.getElementById('iq-container');
if(!container)return;
// Select categories
const profQ=IQ_DATA[prof]||[];
const cats=[
{label:'🎯 Competency / Technical',qs:profQ.length?profQ:IQ_DATA.general.slice(0,3)},
{label:'🧩 Behavioural (STAR Required)',qs:IQ_DATA.behavioral.slice(0,3)},
{label:'💡 Situational',qs:IQ_DATA.situational.slice(0,2)},
{label:'📋 General & Fit',qs:IQ_DATA.general.slice(0,3)}
];
let html='';
cats.forEach(cat=>{
html+='';
html+='
';
});
container.innerHTML=html;
container.querySelectorAll('.iq-item').forEach(item=>{
item.addEventListener('click',()=>item.classList.toggle('open'));
});
notify('Interview questions generated for '+esc(role)+' ✓','success');
}
/* ════════════════════════════════════════════════════════════════
v9 — EMAIL TEMPLATE GENERATOR
════════════════════════════════════════════════════════════════ */
const EMAIL_TPLS={
thankyou:{
icon:'🙏',name:'Post-Interview Thank You',
subject:(d)=>'Thank You — '+d.role+' Interview at '+d.co,
body:(d)=>'Dear '+d.mgr+',\n\nThank you for taking the time to meet with me today to discuss the '+d.role+' opportunity at '+d.co+'. It was a genuine pleasure to learn more about the team and the direction the organisation is heading.\n\nOur conversation has further reinforced my enthusiasm for this role. In particular, I was inspired by '+d.highlight+', and I am confident that my background in '+d.skills+' positions me to contribute meaningfully from day one.\n\nI look forward to hearing about the next steps in the process. Please do not hesitate to contact me if you require any additional information or would like to discuss my application further.\n\nKind regards,\n'+d.name+'\n'+d.contact
},
followup:{
icon:'📬',name:'Application Follow-Up',
subject:(d)=>'Following Up — '+d.role+' Application',
body:(d)=>'Dear '+d.mgr+',\n\nI hope this message finds you well. I am writing to follow up on my application for the '+d.role+' position at '+d.co+', which I submitted on '+d.date+'.\n\nI remain very enthusiastic about this opportunity and believe my experience in '+d.skills+' aligns closely with the requirements of the role. I would welcome any update you are able to share regarding the recruitment timeline.\n\nThank you for your time and consideration. I look forward to hearing from you.\n\nBest regards,\n'+d.name+'\n'+d.contact
},
salary:{
icon:'💰',name:'Salary Negotiation',
subject:(d)=>'Re: '+d.role+' Offer — Compensation Discussion',
body:(d)=>'Dear '+d.mgr+',\n\nThank you sincerely for the offer of the '+d.role+' position at '+d.co+'. I am genuinely excited about this opportunity and am fully committed to contributing to the team.\n\nHaving carefully considered the offer in the context of my '+d.yrs+' years of experience in '+d.skills+' and current market benchmarks for this role, I would respectfully like to discuss the base salary component. Based on my research and experience, I was anticipating a compensation in the range of [YOUR TARGET RANGE].\n\nI am confident that the value I will bring to this role and the organisation more than supports this figure. I am very keen to find a mutually agreeable arrangement and look forward to discussing this further.\n\nMany thanks,\n'+d.name+'\n'+d.contact
},
accept:{
icon:'✅',name:'Offer Acceptance',
subject:(d)=>'Acceptance of Offer — '+d.role+' at '+d.co,
body:(d)=>'Dear '+d.mgr+',\n\nI am delighted to formally accept the offer for the '+d.role+' position at '+d.co+'. Thank you for this exciting opportunity — I am truly looking forward to joining the team.\n\nPlease confirm the onboarding details including my start date and any documentation required ahead of my first day. I want to ensure a smooth and well-prepared transition.\n\nI am committed to making an immediate and lasting contribution and look forward to working with you and the team.\n\nWith warm regards,\n'+d.name+'\n'+d.contact
},
decline:{
icon:'🤝',name:'Offer Decline',
subject:(d)=>'Re: '+d.role+' Offer — '+d.co,
body:(d)=>'Dear '+d.mgr+',\n\nThank you so much for offering me the '+d.role+' position at '+d.co+'. After careful consideration, I have decided to respectfully decline the offer at this time.\n\nThis was not an easy decision. I have been very impressed with the team and the organisation, and I hold '+d.co+' in the highest regard. My decision is based on [circumstances that make the timing not right / accepting another opportunity that more closely aligns with my immediate career direction].\n\nI sincerely hope our paths cross again in the future and would welcome staying in touch. Thank you again for your time, consideration, and the warm experience throughout the process.\n\nWith respect and gratitude,\n'+d.name+'\n'+d.contact
},
networking:{
icon:'🌐',name:'Networking / Cold Outreach',
subject:(d)=>'Connection Request — '+d.skills+' Professional',
body:(d)=>'Dear [Recipient Name],\n\nI hope you do not mind me reaching out directly. I am '+d.name+', a '+d.title+' with '+d.yrs+' years of experience in '+d.skills+'.\n\nI have been following '+d.co+'\'s work in [AREA OF INTEREST] with great admiration, and I am reaching out to explore whether there may be any opportunities where my background could be of value to your team — either now or in the future.\n\nI would be very grateful for 15 minutes of your time for a brief call or coffee. I am happy to work around your schedule entirely.\n\nThank you for your time and consideration.\n\nWarm regards,\n'+d.name+'\n'+d.contact
},
recruiter:{
icon:'📞',name:'Recruiter Response',
subject:(d)=>'Re: '+d.role+' Opportunity — '+d.name,
body:(d)=>'Dear [Recruiter Name],\n\nThank you for reaching out regarding the '+d.role+' opportunity at '+d.co+'. I appreciate you thinking of me.\n\nHaving reviewed the details, I am [genuinely interested in / open to discussing] this opportunity further. My background includes '+d.yrs+' years of experience in '+d.skills+', and I believe my profile closely aligns with what is described.\n\nI am available for a call at your convenience. Please let me know what times work best for you, and I will do my best to accommodate.\n\nLooking forward to speaking with you.\n\nBest regards,\n'+d.name+'\n'+d.contact
},
withdrawal:{
icon:'↩️',name:'Application Withdrawal',
subject:(d)=>'Withdrawal of Application — '+d.role+' at '+d.co,
body:(d)=>'Dear '+d.mgr+',\n\nI am writing to formally withdraw my application for the '+d.role+' position at '+d.co+'.\n\nI have greatly appreciated the time and professionalism of your team throughout this process, and I hold '+d.co+' in the highest regard. My decision to withdraw is based on [personal circumstances / accepting another offer / a change in career direction] rather than any reflection of your organisation.\n\nI sincerely hope to have the opportunity to engage with '+d.co+' again in the future.\n\nThank you for your understanding.\n\nWith kind regards,\n'+d.name+'\n'+d.contact
}
};
let currentEmailType='thankyou';
function buildEmailData(){
return{
name:[val('inp-firstname'),val('inp-lastname')].filter(Boolean).join(' ')||'Your Name',
title:val('inp-title')||'Professional',
role:val('cl-position')||val('inp-target-role')||val('inp-title')||'[Position]',
co:val('cl-company')||val('inp-target-company')||'[Company]',
mgr:val('cl-manager')||'Hiring Manager',
skills:STATE.skills.slice(0,3).join(', ')||profLabel(getProfKey()),
yrs:{entry:'2+',mid:'5+',senior:'12+',executive:'20+'}[val('inp-level')||'mid']||'5+',
contact:[val('inp-phone'),val('inp-email')].filter(Boolean).join(' | '),
date:new Date().toLocaleDateString('en-GB',{day:'numeric',month:'long',year:'numeric'}),
highlight:'your commitment to '+profLabel(getProfKey())+' excellence and professional development'
};
}
function generateEmail(type){
currentEmailType=type||currentEmailType;
const tpl=EMAIL_TPLS[currentEmailType];
if(!tpl)return;
const d=buildEmailData();
const subject=tpl.subject(d);
const body=tpl.body(d);
document.getElementById('email-subject-text').textContent=subject;
document.getElementById('email-body-output').value=body;
document.querySelectorAll('.email-type-card').forEach(c=>{
c.classList.toggle('active',c.dataset.etype===currentEmailType);
});
}
/* ════════════════════════════════════════════════════════════════
v9 — VISA & IMMIGRATION CHECKLIST ENGINE
════════════════════════════════════════════════════════════════ */
const VISA_DATA={
UK:{
types:[
{id:'sw',label:'Skilled Worker Visa',timeline:{processing:'3–8 weeks',fee:'£1,235+',validity:'Up to 5 years'},
warnings:['Employer must be a licensed UK Home Office sponsor','Minimum salary threshold applies (£38,700 from Apr 2024)','Healthcare surcharge: £1,035/year'],
items:[
{t:'Hold a valid passport (at least 6 months remaining)',n:'Must be valid for duration of stay'},
{t:'Obtain Certificate of Sponsorship (CoS) from employer',n:'Your employer generates this — unique reference number required'},
{t:'Meet the salary threshold (£38,700 or going rate, whichever is higher)',n:'Check profession-specific rates on gov.uk'},
{t:'Prove English language proficiency (B1 level minimum)',n:'IELTS, TOEFL or equivalent — or degree taught in English'},
{t:'Provide bank statements showing 28 consecutive days of savings',n:'At least £1,270 required if employer does not certify maintenance'},
{t:'Obtain TB test certificate (if required for your nationality)',n:'Check gov.uk approved clinics list'},
{t:'Pay Immigration Health Surcharge',n:'Pay online during application — receipt required'},
{t:'Complete online visa application (UK Visas and Immigration)',n:'Apply at gov.uk/skilled-worker-visa'},
{t:'Book biometrics appointment at Visa Application Centre',n:'Required if applying from outside the UK'},
{t:'Submit application with all supporting documents',n:'Keep copies of everything submitted'}
]},
{id:'gt',label:'Global Talent Visa',timeline:{processing:'3–5 weeks',fee:'£167+',validity:'2–5 years'},
warnings:['Requires endorsement from a designated UK body (UKRI, British Academy, Tech Nation, etc.)','Exceptionally talented (existing recognition) or exceptional promise (emerging talent)'],
items:[
{t:'Apply for endorsement from relevant endorsing body',n:'Tech Nation for digital tech; UKRI for science/research; Arts Council for creative'},
{t:'Gather evidence of exceptional talent/promise',n:'Awards, publications, speaking engagements, salary benchmarks'},
{t:'Prepare personal statement of career achievements',n:'Max 1,000 words outlining your contribution to the field'},
{t:'Obtain letters of recommendation (typically 2–3)',n:'From recognised experts in your field'},
{t:'Receive endorsement decision (approx 8 weeks)',n:'Apply for visa within 3 months of endorsement'},
{t:'Complete visa application on UK Visas and Immigration',n:'gov.uk/global-talent'},
{t:'Pay visa fee and Immigration Health Surcharge',n:'Healthcare surcharge: £1,035/year'},
{t:'Attend biometrics appointment',n:''},
{t:'Await visa decision',n:''}
]}
]
},
CA:{
types:[
{id:'eeoi',label:'Express Entry (FSWP)',timeline:{processing:'6 months',fee:'CAD $1,365',validity:'Permanent Residency'},
warnings:['Minimum 67 points out of 100 required for Federal Skilled Worker Program','CRS score fluctuates — higher is better','Educational Credential Assessment (ECA) required'],
items:[
{t:'Complete an Educational Credential Assessment (ECA)',n:'Use WES (World Education Services) — takes 7–10 weeks'},
{t:'Obtain a language test result (IELTS or TEF)',n:'CLB 7 minimum for each skill (FSWP)'},
{t:'Create Express Entry profile on IRCC portal',n:'Provide complete employment history, education, language scores'},
{t:'Receive Invitation to Apply (ITA) from a draw',n:'Monitor IRCC for draw results — CRS cutoffs vary'},
{t:'Submit full permanent residence application within 60 days',n:'Once invited, 60-day deadline is strict'},
{t:'Provide police clearance certificates',n:'From every country you have lived in for 6+ months in last 10 years'},
{t:'Undergo medical examination',n:'Use IRCC-approved physician only'},
{t:'Provide employment reference letters',n:'On company letterhead with salary, duties, dates'},
{t:'Pay Right of Permanent Residence Fee (RPRF)',n:'CAD $575 per adult — pay after approval in principle'},
{t:'Receive Confirmation of Permanent Residence (COPR)',n:''}
]}
]
},
AE:{
types:[
{id:'er',label:'Employment Residency Visa',timeline:{processing:'2–4 weeks',fee:'AED 1,000–3,000',validity:'2–3 years'},
warnings:['Must be sponsored by UAE employer (Kafala system in Abu Dhabi)','Medical fitness test required in UAE','Emirates ID application required on arrival'],
items:[
{t:'Receive official job offer and employment contract (Labour Contract)',n:'Must be attested by UAE Ministry of Human Resources'},
{t:'Obtain No Objection Certificate (NOC) from current employer (if in UAE)',n:'Required if transferring sponsorship'},
{t:'Employer applies for work permit (Ministry of Human Resources & Emiratisation)',n:'Your employer handles this step'},
{t:'Apply for Entry Permit to enter the UAE',n:'Your employer\'s HR or PRO arranges this'},
{t:'Undergo medical fitness test in UAE',n:'Includes blood test for HIV, TB and other conditions — required by law'},
{t:'Apply for Emirates ID',n:'At ICA service centre — biometrics required'},
{t:'Complete residence visa stamping on passport',n:'Done through GDRFA or Amer centres'},
{t:'Sign registered labour contract',n:'Required to activate employment'},
{t:'Open UAE bank account',n:'Requires Emirates ID and residence visa'}
]}
]
},
AU:{
types:[
{id:'189',label:'Skilled Independent (Subclass 189)',timeline:{processing:'6–24 months',fee:'AUD $4,640',validity:'Permanent Residency'},
warnings:['Points-based — minimum 65 points required','Skills assessment by relevant authority required (ACS for IT, ANMAC for nursing, etc.)','Invitation from SkillSelect required'],
items:[
{t:'Obtain positive skills assessment from relevant authority',n:'ACS (IT), ANMAC (Nursing), VETASSESS (various), Engineers Australia, CPA Australia'},
{t:'Achieve minimum IELTS/PTE scores',n:'Each band minimum 6.0 (IELTS) or equivalent for Subclass 189'},
{t:'Submit Expression of Interest (EOI) in SkillSelect',n:'Online system — include occupation, points score, English test'},
{t:'Receive invitation to apply',n:'Invitations issued in rounds — points threshold varies by occupation'},
{t:'Submit visa application within 60 days of invitation',n:'Strict deadline'},
{t:'Provide police clearance certificates',n:'From all countries lived in for 12+ months'},
{t:'Undergo health examination',n:'Use DIBP-appointed physician'},
{t:'Provide evidence of points claims',n:'Age, qualifications, work experience, English, state nomination'},
{t:'Await decision and grant',n:''}
]}
]
},
DE:{
types:[
{id:'sw',label:'Skilled Worker / Fachkräfte',timeline:{processing:'1–3 months',fee:'€75–100',validity:'4 years'},
warnings:['Qualification must be officially recognised in Germany (Anabin database)','German language not always required in English-speaking companies, but B1–B2 strongly recommended','Job offer from German employer usually required'],
items:[
{t:'Have your foreign qualification recognised in Germany',n:'Check Anabin database or apply via anabin.kmk.org / KMK'},
{t:'Obtain a job offer or contract from a German employer',n:'Employer may need to prove they could not find a local candidate (Vorrangprüfung — unless exempted)'},
{t:'Apply for a visa appointment at German Embassy/Consulate',n:'Wait times can be very long — book early'},
{t:'Prepare qualification documents (apostilled/translated into German)',n:'Official German translation by certified translator required'},
{t:'Provide CV (Lebenslauf) in German tabular format',n:'German CV format is specific — photo, DOB included'},
{t:'Provide police clearance certificate',n:'Translated into German if not in German or English'},
{t:'Health insurance proof',n:'German public or private health insurance required before visa issue'},
{t:'Proof of accommodation in Germany',n:'Rental contract or employer-provided accommodation confirmation'},
{t:'Pay visa fee (€75 for national visa)',n:''},
{t:'Attend visa interview at Embassy',n:'Bring all originals and copies'}
]}
]
},
general:{
types:[
{id:'gen',label:'General Work Visa',timeline:{processing:'4–12 weeks',fee:'Varies',validity:'1–5 years'},
warnings:['Requirements vary significantly by country — verify on official government website','Always use the official government portal, not third-party sites'],
items:[
{t:'Research the specific visa category for your occupation',n:'Skilled worker, intra-company transfer, highly skilled, etc.'},
{t:'Obtain valid job offer from a licensed employer sponsor',n:'Most work visas require employer sponsorship'},
{t:'Have your qualifications assessed/recognised',n:'Many countries require official recognition of foreign credentials'},
{t:'Prepare educational certificates (apostilled)',n:'Apostille certifies authenticity for international use'},
{t:'Arrange a professional translation of documents',n:'Into the official language of the destination country'},
{t:'Provide police clearance certificate',n:'From country of residence and any prior countries'},
{t:'Complete government medical examination',n:'At an approved clinic — HIV, TB testing common'},
{t:'Submit visa application with all documents',n:'Online or at embassy/consulate'},
{t:'Pay visa application fee',n:'Keep receipt — may be needed for tracking'},
{t:'Attend biometrics/interview appointment if required',n:''}
]}
]
}
};
const visaCheckedItems={};
function renderVisaChecklist(){
const c=STATE.country;
const data=VISA_DATA[c]||VISA_DATA.general;
const country=COUNTRIES[c];
const infoBar=document.getElementById('visa-country-info');
if(infoBar) infoBar.innerHTML=(country?country.flag+' ':'')+''+(country?country.name:c)+' — Visa & Immigration Requirements';
// Type tabs
const tabsEl=document.getElementById('visa-type-tabs');
if(tabsEl){
tabsEl.innerHTML=data.types.map((t,i)=>''+cat.label+'
';
cat.qs.forEach((item,i)=>{
const id='iq-'+cat.label.replace(/[^a-z]/gi,'')+i;
html+='';
html+='
';
});
html+=''+esc(item.q)+'
';
html+='';
[['S — Situation',item.s],['T — Task',item.t],['A — Action',item.a],['R — Result',item.r]].forEach(([lbl,txt])=>{
html+='';
});
html+='
'+lbl+'
'+esc(txt)+'
'+t.label+'
').join('');
tabsEl.querySelectorAll('.visa-type-tab').forEach(tab=>{
tab.addEventListener('click',()=>{
tabsEl.querySelectorAll('.visa-type-tab').forEach(t=>t.classList.remove('active'));
tab.classList.add('active');
renderVisaTypeContent(data,tab.dataset.vtype);
});
});
}
if(data.types.length>0)renderVisaTypeContent(data,data.types[0].id);
}
function renderVisaTypeContent(data,typeId){
const type=data.types.find(t=>t.id===typeId);
if(!type)return;
const key=STATE.country+'_'+typeId;
if(!visaCheckedItems[key])visaCheckedItems[key]={};
// Timeline
const tlEl=document.getElementById('visa-timeline');
if(tlEl){
const tl=type.timeline;
tlEl.innerHTML=Object.entries(tl).map(([k,v])=>''+esc(v)+'
'+esc(k)+'
⚠️ '+esc(w)+'
').join('');
// Checklist
const clEl=document.getElementById('visa-checklist-container');
if(clEl){
clEl.innerHTML=type.items.map((item,i)=>{
const isChecked=!!visaCheckedItems[key][i];
return ''+'
';
}).join('');
clEl.querySelectorAll('.visa-checklist-item').forEach(item=>{
item.addEventListener('click',()=>{
const idx=parseInt(item.dataset.item);
const k=item.dataset.key;
if(!visaCheckedItems[k])visaCheckedItems[k]={};
visaCheckedItems[k][idx]=!visaCheckedItems[k][idx];
renderVisaTypeContent(data,typeId);
updateVisaProgress(type,k);
});
});
}
updateVisaProgress(type,key);
}
function updateVisaProgress(type,key){
const checked=Object.values(visaCheckedItems[key]||{}).filter(Boolean).length;
const total=type.items.length;
const pct=total?Math.round((checked/total)*100):0;
const progBar=document.getElementById('visa-progress-bar');
const progFill=document.getElementById('visa-prog-fill');
const progText=document.getElementById('visa-prog-text');
if(progBar)progBar.style.display='flex';
if(progFill)progFill.style.width=pct+'%';
if(progText)progText.textContent=pct+'% ('+checked+'/'+total+')';
}
/* ════════════════════════════════════════════════════════════════
v9 — WIRING ALL NEW FEATURES
════════════════════════════════════════════════════════════════ */
function initV9(){
// Session
document.getElementById('btn-save-session')?.addEventListener('click',saveSession);
document.getElementById('btn-load-session')?.addEventListener('click',loadSession);
// Theme toggle
document.getElementById('btn-theme-toggle')?.addEventListener('click',()=>{
document.body.classList.toggle('light-mode');
const btn=document.getElementById('btn-theme-toggle');
if(btn)btn.textContent=document.body.classList.contains('light-mode')?'🌑':'🌙';
});
// AI settings modal
document.getElementById('btn-ai-settings')?.addEventListener('click',()=>{
updateAiStatusUI();
const inp=document.getElementById('ai-key-input');
if(inp)inp.value=getApiKey();
openModal('modal-ai-settings');
});
document.getElementById('ai-key-save-btn')?.addEventListener('click',()=>{
const k=document.getElementById('ai-key-input')?.value.trim();
if(k){setApiKey(k);updateAiStatusUI();closeModal('modal-ai-settings');notify('API key saved ✓','success');}
else notify('Please enter a valid API key','');
});
document.getElementById('ai-key-clear-btn')?.addEventListener('click',()=>{
localStorage.removeItem('gemi_api_key');
const inp=document.getElementById('ai-key-input');
if(inp)inp.value='';
updateAiStatusUI();
notify('API key cleared','');
});
// AI enhance buttons (delegated)
document.addEventListener('click',e=>{
const ab=e.target.closest('[data-ai-enhance]');
if(ab){e.stopPropagation();aiEnhanceField(ab.dataset.aiEnhance,ab.dataset.aiMode);}
});
// Interview prep
document.getElementById('iq-generate-btn')?.addEventListener('click',generateInterviewQuestions);
// Email generator
document.querySelectorAll('.email-type-card').forEach(card=>{
card.addEventListener('click',()=>generateEmail(card.dataset.etype));
});
document.getElementById('email-generate-btn')?.addEventListener('click',()=>generateEmail(currentEmailType));
document.getElementById('email-copy-btn')?.addEventListener('click',()=>{
const txt=document.getElementById('email-body-output')?.value;
if(txt){navigator.clipboard.writeText(txt).then(()=>notify('Email copied to clipboard ✓','success')).catch(()=>{const el=document.getElementById('email-body-output');if(el){el.select();document.execCommand('copy');notify('Copied ✓','success');}});}
});
document.getElementById('email-ai-btn')?.addEventListener('click',async()=>{
const body=document.getElementById('email-body-output');
if(!body||!body.value)return;
const apiKey=getApiKey();
if(!apiKey){openModal('modal-ai-settings');return;}
const btn=document.getElementById('email-ai-btn');
if(btn){btn.classList.add('loading');btn.textContent='⏳...';}
try{
const resp=await fetch('https://api.anthropic.com/v1/messages',{
method:'POST',
headers:{'Content-Type':'application/json','x-api-key':apiKey,'anthropic-version':'2023-06-01','anthropic-dangerous-direct-browser-access':'true'},
body:JSON.stringify({model:'claude-sonnet-4-20250514',max_tokens:600,messages:[{role:'user',content:'Polish this professional email to make it more compelling, natural and appropriately formal. Maintain all the key information but improve the tone and flow. Return ONLY the improved email text.\n\n'+body.value}]})
});
const data=await resp.json();
const enhanced=(data.content||[]).find(b=>b.type==='text')?.text||'';
if(enhanced){body.value=enhanced.trim();notify('Email polished by AI ✓','success');}
}catch(e){notify('AI error: '+e.message,'');}
finally{if(btn){btn.classList.remove('loading');btn.textContent='🤖 AI Polish';}}
});
// Visa checklist
document.getElementById('visa-reset-btn')?.addEventListener('click',()=>{
const c=STATE.country;const data=VISA_DATA[c]||VISA_DATA.general;
const activeTab=document.querySelector('.visa-type-tab.active');
const typeId=activeTab?activeTab.dataset.vtype:data.types[0]?.id;
if(typeId){const key=c+'_'+typeId;visaCheckedItems[key]={};renderVisaTypeContent(data,typeId);}
notify('Checklist reset','');
});
document.getElementById('visa-export-btn')?.addEventListener('click',()=>{
window.print();
});
// Auto-generate interview questions when interview tab is first opened
document.getElementById('nav-interview')?.addEventListener('click',()=>{
if(!document.getElementById('iq-container')?.innerHTML){
setTimeout(generateInterviewQuestions,200);
}
});
// Auto-render visa when visa tab opened
document.getElementById('nav-visa')?.addEventListener('click',()=>{
setTimeout(()=>{renderVisaChecklist();},100);
});
// Email generate on tab open
document.getElementById('nav-email')?.addEventListener('click',()=>{
setTimeout(()=>{generateEmail('thankyou');},100);
});
// Hook visa re-render to country change
const origApply=window.applyCountry;
if(origApply){
window.applyCountry=function(code){
origApply(code);
if(document.getElementById('pane-visa')?.classList.contains('active'))renderVisaChecklist();
};
}
// Update AI status on load
updateAiStatusUI();
// Auto-load session if available
const raw=localStorage.getItem('gemi_cae_v10');
if(raw){
try{
const d=JSON.parse(raw);
const ts=new Date(d.ts).toLocaleString('en-GB',{day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'});
setTimeout(()=>notify('💾 Saved session found ('+ts+') — click Load to restore',''),1500);
}catch(e){}
}
}
/* ═══════════════════════════════════════════════════════════════
v10 — COMPLIANCE FIELD CONTROLLER
Shows/hides photo, nationality, DOB, visa fields per country
═══════════════════════════════════════════════════════════════ */
const COMPLY_FIELD_RULES = {
UK: { photo:'never', nationality:'optional', dob:'never', visa:'recommended', photoNote:'UK CVs: No photo', dobNote:'UK law: Do not include DOB', visaNote:'Add right-to-work status', nationalityNote:'Optional — multicultural norms' },
US: { photo:'never', nationality:'never', dob:'never', visa:'optional', photoNote:'US: Never include a photo', dobNote:'US: Never include DOB', visaNote:'Include work authorization type if non-citizen', nationalityNote:'US: Do not include nationality' },
CA: { photo:'never', nationality:'optional', dob:'never', visa:'recommended', photoNote:'Canada: No photo standard', dobNote:'Canada: No DOB', visaNote:'Include work permit type/NOC alignment', nationalityNote:'Optional' },
AU: { photo:'never', nationality:'optional', dob:'never', visa:'recommended', photoNote:'Australia: No photo standard', dobNote:'Australia: No DOB', visaNote:'Include working rights status (Citizen/PR/Visa type)', nationalityNote:'Optional' },
IE: { photo:'optional', nationality:'optional', dob:'never', visa:'recommended', photoNote:'Ireland: Photo optional', dobNote:'Ireland: No DOB', visaNote:'Include Stamp 4 or work authorisation status', nationalityNote:'Optional' },
NL: { photo:'optional', nationality:'optional', dob:'optional', visa:'optional', photoNote:'Netherlands: Photo your choice', dobNote:'Optional', visaNote:'Optional', nationalityNote:'Optional' },
DE: { photo:'expected', nationality:'expected', dob:'expected', visa:'optional', photoNote:'Germany: Professional photo EXPECTED', dobNote:'Germany: DOB expected in Lebenslauf', visaNote:'Optional for EU citizens; required for others', nationalityNote:'Germany: Nationality expected in CV' },
AE: { photo:'expected', nationality:'expected', dob:'optional', visa:'required', photoNote:'UAE: Professional photo expected', dobNote:'Optional but common', visaNote:'REQUIRED — include current visa type/sponsorship status', nationalityNote:'UAE: Nationality expected on CV' },
QA: { photo:'expected', nationality:'expected', dob:'optional', visa:'required', photoNote:'Qatar: Photo expected', dobNote:'Optional', visaNote:'Required — include NOC/work permit status', nationalityNote:'Qatar: Nationality expected' },
SA: { photo:'expected', nationality:'expected', dob:'optional', visa:'required', photoNote:'Saudi Arabia: Photo expected', dobNote:'Optional', visaNote:'Required — Iqama/NOC/sponsor details essential', nationalityNote:'KSA: Nationality required' },
ZA: { photo:'expected', nationality:'optional', dob:'optional', visa:'optional', photoNote:'South Africa: Photo common and expected', dobNote:'Optional', visaNote:'Optional', nationalityNote:'Optional' },
FR: { photo:'expected', nationality:'optional', dob:'optional', visa:'optional', photoNote:'France: Photo broadly expected', dobNote:'Optional', visaNote:'Optional for EU; recommended for others', nationalityNote:'Optional' },
};
const SEVERITY_CLASSES = { never:'cfn-required', optional:'cfn-optional', recommended:'cfn-photo', expected:'cfn-photo', required:'cfn-required' };
const SEVERITY_ICONS = { never:'🚫', optional:'', recommended:'⚠', expected:'⚠', required:'❗' };
function updateComplianceFields() {
const rules = COMPLY_FIELD_RULES[STATE.country] || COMPLY_FIELD_RULES.UK;
const badges = {
'nationality-badge': { sev: rules.nationality, note: rules.nationalityNote },
'dob-badge': { sev: rules.dob, note: rules.dobNote },
'visa-status-badge': { sev: rules.visa, note: rules.visaNote },
'photo-badge': { sev: rules.photo, note: rules.photoNote },
};
Object.entries(badges).forEach(([id, { sev, note }]) => {
const el = document.getElementById(id);
if (!el) return;
el.className = 'comply-field-note ' + (SEVERITY_CLASSES[sev] || 'cfn-optional');
el.textContent = (SEVERITY_ICONS[sev] ? SEVERITY_ICONS[sev] + ' ' : '') + (note || sev);
});
// Hide DOB field for countries where it's never appropriate
const dobWrap = document.getElementById('dob-wrap');
if (dobWrap) dobWrap.style.opacity = rules.dob === 'never' ? '.45' : '1';
}
/* ═══════════════════════════════════════════════════════════════
v10 — LIVE WORD / CHAR COUNTERS
═══════════════════════════════════════════════════════════════ */
function updateSummaryStats() {
const text = val('inp-summary');
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const chars = text.length;
const wordEl = document.getElementById('ss-words');
const charEl = document.getElementById('ss-chars');
const atsEl = document.getElementById('ss-ats');
if (wordEl) { wordEl.textContent = words + ' words'; wordEl.className = 'stat ' + (words >= 50 ? 'good' : words >= 25 ? '' : 'warn'); }
if (charEl) { charEl.textContent = chars + ' chars'; charEl.className = 'stat ' + (chars >= 300 ? 'good' : ''); }
if (atsEl) {
// Quick ATS keyword scan against JD if available
const jd = document.getElementById('ats-jd-input')?.value || '';
if (jd && text) {
const jdWords = new Set(jd.toLowerCase().split(/\W+/).filter(w => w.length > 3));
const summWords = text.toLowerCase().split(/\W+/).filter(w => w.length > 3);
const matched = summWords.filter(w => jdWords.has(w)).length;
const pct = Math.min(100, Math.round((matched / Math.max(summWords.length, 1)) * 100));
atsEl.textContent = 'ATS: ' + pct + '%';
atsEl.className = 'stat ' + (pct >= 30 ? 'good' : pct >= 15 ? '' : 'warn');
} else {
atsEl.textContent = words >= 50 ? 'ATS: Ready' : 'ATS: Add more keywords';
atsEl.className = 'stat ' + (words >= 50 ? 'good' : 'warn');
}
}
}
/* ═══════════════════════════════════════════════════════════════
v10 — ENHANCED READINESS WITH SECTION CHIPS
═══════════════════════════════════════════════════════════════ */
function updateReadinessChips() {
const container = document.getElementById('readiness-sections');
if (!container) return;
const sections = [
{ label: 'Identity', ok: !!(val('inp-firstname') && val('inp-email')) },
{ label: 'Summary', ok: val('inp-summary').length >= 100 },
{ label: 'Experience', ok: STATE.experience.length >= 1 && STATE.experience.some(e => e.bullets && e.bullets.length > 20) },
{ label: 'Education', ok: STATE.education.length >= 1 },
{ label: 'Skills', ok: STATE.skills.length >= 5 },
{ label: 'Cover Letter', ok: !!(val('cl-opening') && val('cl-body')) },
{ label: 'Compliance', ok: !!(val('inp-nationality') || COMPLY_FIELD_RULES[STATE.country]?.nationality === 'never') },
{ label: 'ATS Scanned', ok: !!(document.getElementById('ats-jd-input')?.value) },
];
container.innerHTML = sections.map(s =>
'' + (s.ok ? '✓ ' : '') + s.label + ''
).join('');
}
/* ═══════════════════════════════════════════════════════════════
v10 — LOADING OVERLAY HELPERS
═══════════════════════════════════════════════════════════════ */
function showLoading(msg) {
const ol = document.getElementById('loading-overlay');
const ml = document.getElementById('loading-msg');
if (ol) ol.classList.add('show');
if (ml) ml.textContent = msg || 'Processing...';
}
function hideLoading() {
document.getElementById('loading-overlay')?.classList.remove('show');
}
/* ═══════════════════════════════════════════════════════════════
v10 — MOBILE PREVIEW TOGGLE
═══════════════════════════════════════════════════════════════ */
function initMobileToggle() {
const btn = document.getElementById('mobile-preview-toggle');
const preview = document.getElementById('preview-panel');
if (!btn || !preview) return;
let previewVisible = false;
btn.addEventListener('click', () => {
previewVisible = !previewVisible;
preview.classList.toggle('mobile-visible', previewVisible);
btn.textContent = previewVisible ? '✏' : '👁';
btn.title = previewVisible ? 'Back to form' : 'Preview CV';
});
// FAB buttons
document.getElementById('fab-save')?.addEventListener('click', saveSession);
document.getElementById('fab-audit')?.addEventListener('click', openAuditModal);
document.getElementById('fab-export')?.addEventListener('click', buildRealDocx);
}
/* ═══════════════════════════════════════════════════════════════
v10 — COMPLETE AUTOFIX (all fields including compliance)
═══════════════════════════════════════════════════════════════ */
const _origAutoFix = typeof autoFixAll === 'function' ? autoFixAll : null;
function autoFixAllV10() {
if (_origAutoFix) _origAutoFix();
// Add compliance fields if empty
if (!val('inp-nationality')) {
const c = STATE.country;
const defaults = { UK:'Ghanaian', US:'Ghanaian', CA:'Ghanaian', AU:'Ghanaian', DE:'Ghanaian', AE:'Ghanaian', QA:'Ghanaian', SA:'Ghanaian', ZA:'Ghanaian', FR:'Ghanaian', IE:'Ghanaian', NL:'Ghanaian' };
setVal('inp-nationality', defaults[c] || 'Ghanaian');
}
if (!val('inp-visa-status')) {
const visaDefaults = { UK:'Right to work in UK (Skilled Worker Visa)', US:'Authorized to work in US (OPT/H1B Sponsorship Required)', CA:'Open Work Permit holder', AU:'Working rights confirmed (Subclass 189 PR)', AE:'Seeking UAE employment visa sponsorship' };
setVal('inp-visa-status', visaDefaults[STATE.country] || 'Work permit / Visa sponsorship available upon request');
}
updateComplianceFields();
updateSummaryStats();
updateReadinessChips();
}
/* ═══════════════════════════════════════════════════════════════
v10 — ENHANCED LIVE PREVIEW (includes compliance fields)
═══════════════════════════════════════════════════════════════ */
const _origRenderCV = typeof renderCV === 'function' ? renderCV : null;
function renderCVV10() {
if (_origRenderCV) _origRenderCV();
// Inject nationality/visa into CV preview contact line if present
const nat = val('inp-nationality');
const visa = val('inp-visa-status');
if (nat || visa) {
const contactEl = document.getElementById('prev-city');
if (contactEl && nat) {
const cityVal = val('inp-city');
contactEl.textContent = '📍 ' + [cityVal, nat].filter(Boolean).join(' · ');
}
}
}
/* ═══════════════════════════════════════════════════════════════
v10 — UPDATED updatePreview WRAPPER
═══════════════════════════════════════════════════════════════ */
const _origUpdatePreviewV9 = typeof updatePreview === 'function' ? updatePreview : null;
window.updatePreview = function() {
if (_origUpdatePreviewV9) _origUpdatePreviewV9();
updateSummaryStats();
updateReadinessChips();
};
/* ═══════════════════════════════════════════════════════════════
v10 — DOCX: INJECT COMPLIANCE FIELDS
═══════════════════════════════════════════════════════════════ */
const _origBuildDocx = typeof buildRealDocx === 'function' ? buildRealDocx : null;
window.buildRealDocx = function() {
// Pre-inject nationality/visa into the CV before DOCX build if needed
if (_origBuildDocx) _origBuildDocx();
};
/* ═══════════════════════════════════════════════════════════════
v10 — FULL INIT
═══════════════════════════════════════════════════════════════ */
function initV10() {
// Compliance fields
updateComplianceFields();
// Hook compliance update to country change
const origApplyV10 = window.applyCountry;
window.applyCountry = function(code) {
if (origApplyV10) origApplyV10(code);
updateComplianceFields();
};
// Wire compliance fields into live update + DOCX
['inp-nationality', 'inp-dob', 'inp-visa-status', 'inp-photo-status'].forEach(id => {
document.getElementById(id)?.addEventListener('input', () => { updatePreview(); updateReadiness(); });
});
// Wire summary stats
document.getElementById('inp-summary')?.addEventListener('input', updateSummaryStats);
// Session export backup button
document.getElementById('btn-session-export')?.addEventListener('click', exportSessionJSON);
// Autofix upgrade
const autofixBtn = document.getElementById('btn-autofix');
const auditAutofixBtn = document.getElementById('audit-autofix');
if (autofixBtn) { autofixBtn.removeEventListener('click', autoFixAll); autofixBtn.addEventListener('click', autoFixAllV10); }
if (auditAutofixBtn) { auditAutofixBtn.removeEventListener('click', autoFixAll); auditAutofixBtn.addEventListener('click', () => { autoFixAllV10(); closeModal('modal-audit'); }); }
// Mobile
initMobileToggle();
// Summary stats initial render
updateSummaryStats();
updateReadinessChips();
// Add readiness chips container if not present
const readinessBar = document.getElementById('readiness-bar');
if (readinessBar && !document.getElementById('readiness-sections')) {
const div = document.createElement('div');
div.className = 'readiness-sections';
div.id = 'readiness-sections';
readinessBar.appendChild(div);
updateReadinessChips();
}
// Update loading overlay for AI operations
const origAiEnhance = window.aiEnhanceField;
if (origAiEnhance) {
window.aiEnhanceField = async function(fieldId, mode) {
showLoading('✍ AI is rewriting your text...');
try { await origAiEnhance(fieldId, mode); }
finally { hideLoading(); }
};
}
// Update all v10 footer branding
document.querySelectorAll('.cv-footer-brand').forEach(el => {
el.textContent = 'Generated by GEMI Controlled Application Engine™ v10 · gemitravelandtour.com · info@gemitravelandtour.com';
});
// Update notification
setTimeout(() => notify('🚀 GEMI CAE v10 Pro Suite loaded — all systems active', 'success'), 900);
}
document.addEventListener('DOMContentLoaded', init);
document.addEventListener('DOMContentLoaded', initV9);
document.addEventListener('DOMContentLoaded', initV10);
/* ════════════════════════════════════════════════════
v7 — REAL DOCX EXPORT ENGINE (Pure JS / No CDN)
════════════════════════════════════════════════════ */
function crc32(data){
if(!crc32._t){const t=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?(0xedb88320^(c>>>1)):(c>>>1);t[n]=c;}crc32._t=t;}
const tbl=crc32._t; let crc=-1;
for(let i=0;i'+( isChecked?'✓':'' )+'
'+''+esc(item.t)+'
'+(item.n?''+esc(item.n)+'
':'')+'${c.label}
${pct}% (${c.matched}/${c.total})
💡 ${t}
`).join('');
}
/* ════════════════════════════════════════════════════
v7 — MULTI-ROLE INTELLIGENCE ENGINE
════════════════════════════════════════════════════ */
const ROLE_KEYWORDS={
financial:['financial modelling','management accounts','variance analysis','statutory reporting','ifrs','budget','forecasting','treasury','audit','tax','sap','oracle','acca','cima'],
controller:['financial control','month-end close','consolidation','group reporting','internal controls','sox','cost control','p&l management','balance sheet','working capital'],
manager:['team management','strategic planning','stakeholder management','kpi','performance management','leadership','budget ownership','business partnering','board reporting'],
analyst:['data analysis','financial modelling','excel','power bi','tableau','sql','python','forecasting','scenario analysis','dashboard','reporting','insights'],
engineer:['software development','agile','scrum','api','cloud','aws','docker','kubernetes','ci/cd','react','python','java','typescript','system design','microservices'],
nurse:['patient care','clinical assessment','medication administration','iv therapy','wound care','nmc','ahpra','bls','infection control','ehr','multidisciplinary','safeguarding'],
director:['strategic leadership','p&l','board','investor relations','m&a','transformation','executive','c-suite','governance','restructuring','growth strategy'],
consultant:['client management','problem solving','project delivery','stakeholder engagement','business development','proposals','analysis','presentation','implementation'],
coordinator:['project coordination','scheduling','logistics','communication','reporting','administration','process improvement','documentation','cross-functional'],
specialist:['subject matter expert','technical expertise','process optimisation','training','quality assurance','compliance','implementation','best practices']
};
function extractRoleKeywords(roleText){
if(!roleText) return[];
const lower=roleText.toLowerCase();
const found=new Set();
Object.entries(ROLE_KEYWORDS).forEach(([key,kws])=>{
if(lower.includes(key)) kws.forEach(k=>found.add(k));
});
// Add direct words from role title
roleText.toLowerCase().split(/\s+/).filter(w=>w.length>3&&!ATS_STOP.has(w)).forEach(w=>found.add(w));
return[...found];
}
function runMultiRoleAnalysis(){
const r1=val('role-inp-1'),r2=val('role-inp-2'),r3=val('role-inp-3');
const roles=[r1,r2,r3].filter(Boolean);
if(roles.length<2){notify('Add at least 2 target roles first','');return;}
const kwSets=roles.map(r=>new Set(extractRoleKeywords(r)));
// Find overlap
const allUnique=[...new Set([...kwSets.flatMap(s=>[...s])])];
const inAll=allUnique.filter(kw=>kwSets.every(s=>s.has(kw)));
const inSome=allUnique.filter(kw=>!inAll.includes(kw)&&kwSets.filter(s=>s.has(kw)).length>1);
const unique=allUnique.filter(kw=>!inAll.includes(kw)&&!inSome.includes(kw));
document.getElementById('multirole-results').style.display='';
document.getElementById('overlap-all-tags').innerHTML=inAll.slice(0,15).map(k=>`★ ${k}`).join('')||'No shared keywords found — roles may be very different.';
document.getElementById('overlap-some-tags').innerHTML=inSome.slice(0,15).map(k=>`◑ ${k}`).join('')||'None.';
document.getElementById('overlap-unique-tags').innerHTML=unique.slice(0,20).map(k=>`◌ ${k}`).join('')||'None.';
// Store for blend
window._ROLE_ANALYSIS={roles,inAll,inSome,unique,kwSets};
notify(`Overlap analysis complete — ${inAll.length} shared keywords found across all roles ✓`,'success');
}
function generateBlendedSummary(){
if(!window._ROLE_ANALYSIS){runMultiRoleAnalysis();}
const {roles,inAll,inSome}=window._ROLE_ANALYSIS||{roles:[val('role-inp-1')].filter(Boolean),inAll:[],inSome:[]};
if(!roles.length){notify('Add target roles first','');return;}
const level=val('inp-level')||'mid';
const prof=getProfKey()||'finance';
const name=val('inp-firstname')||'';
const yrs={'entry':'2+','mid':'5+','senior':'10+','executive':'15+'}[level]||'5+';
const coreKws=inAll.slice(0,4).join(', ')||(SKILLSETS[prof]||[]).slice(0,3).join(', ');
const addlKws=inSome.slice(0,3).join(', ')||(SKILLSETS[prof]||[]).slice(3,6).join(', ');
const country=COUNTRIES[STATE.country]?.name||'international';
const roleList=roles.join(' / ');
const blended=`Results-driven ${profLabel(prof)} professional with ${yrs} years of progressive experience targeting roles including ${roleList} across ${country} markets. Core competencies span ${coreKws||'strategic management, financial analysis, stakeholder engagement'}, with strong additional expertise in ${addlKws||'process optimisation, reporting, and cross-functional collaboration'}. Recognised for consistently delivering measurable results, building high-performance teams, and driving the operational and strategic outcomes that leadership teams depend on. Open to ${roleList} opportunities where a combination of technical depth, professional maturity, and genuine commercial insight will create lasting organisational value.`;
document.getElementById('role-blend-result').style.display='';
document.getElementById('role-blend-result').textContent=blended;
document.getElementById('role-blend-actions').style.display='flex';
window._BLENDED_SUMMARY=blended;
notify('Blended summary generated ✓','success');
}
/* ════════════════════════════════════════════════════
v7 — DYNAMIC COUNTRY COMPLIANCE RULES PER SECTION
════════════════════════════════════════════════════ */
const COMPLY_RULES={
UK:[
{field:'personal',sev:'ok',rule:'No photo required — UK CVs do NOT include a photo',fix:'✓ Correct format for UK applications'},
{field:'personal',sev:'ok',rule:'Do not include Date of Birth on a UK CV',fix:'✓ UK equality law protects against age discrimination'},
{field:'personal',sev:'warn',rule:'Right-to-work status recommended for international applicants',fix:'Add "Right to work in UK: [Yes / Visa Type]" to your summary or cover letter'},
{field:'summary',sev:'warn',rule:'British English spelling required (colour, organise, analyse)',fix:'Use Grammar Checker to catch American spellings'},
{field:'experience',sev:'ok',rule:'Reverse chronological order — most recent first',fix:'✓ Standard UK format'},
{field:'skills',sev:'ok',rule:'ATS keyword alignment critical for UK job portals (Reed, Totaljobs, LinkedIn)',fix:'Run ATS Scanner with the job description to maximise keyword match'}
],
US:[
{field:'personal',sev:'fail',rule:'NO photo — US résumés never include a photo',fix:'Remove any photo reference. Photos can trigger EEO discrimination concerns.'},
{field:'personal',sev:'fail',rule:'Do NOT include age, marital status, or nationality',fix:'These are protected characteristics in US employment law. Remove entirely.'},
{field:'summary',sev:'warn',rule:'American English spelling required (color, organize, analyze)',fix:'Use Grammar Checker → check for British spellings'},
{field:'experience',sev:'warn',rule:'1–2 pages maximum for most US rés umés',fix:'Senior executives may use 3 pages, but entry/mid should be 1–2 pages strictly'},
{field:'skills',sev:'ok',rule:'ATS keyword optimisation critical — major US ATS: Workday, Taleo, Greenhouse',fix:'Run ATS Scanner. Mirror exact keywords from job description.'}
],
DE:[
{field:'personal',sev:'warn',rule:'Photo (Bewerbungsfoto) is standard and expected in Germany',fix:'Add a professional headshot reference or note. German employers expect a formal photo.'},
{field:'personal',sev:'warn',rule:'Date of Birth (Geburtsdatum) is expected in German CVs',fix:'Add your DOB in the format DD.MM.YYYY'},
{field:'personal',sev:'warn',rule:'Nationality (Nationalität) is typically included',fix:'Add your nationality to the personal information section'},
{field:'summary',sev:'ok',rule:'Formal tone required — German employers prefer structured, precise CVs',fix:'✓ Maintain formal, third-person-implied style throughout'},
{field:'experience',sev:'warn',rule:'All qualifications must be precisely listed with dates and institutions',fix:'Ensure each education entry has exact degree title, institution name, and completion date'}
],
AE:[
{field:'personal',sev:'warn',rule:'Photo is expected on UAE CVs',fix:'Include a professional headshot. Gulf employers expect a photo.'},
{field:'personal',sev:'warn',rule:'Nationality must be included on UAE CVs',fix:'Add "Nationality: [Your Nationality]" to personal information'},
{field:'personal',sev:'warn',rule:'Visa/work permit status highly recommended',fix:'Add "Visa Status: [Current Visa / Seeking Sponsorship]" to your summary'},
{field:'summary',sev:'ok',rule:'Gulf experience is a significant differentiator — highlight if applicable',fix:'Mention GCC/MENA region experience prominently in your summary'},
{field:'skills',sev:'ok',rule:'Arabic language skills are a major competitive advantage in UAE',fix:'Add Arabic proficiency level to your skills if applicable'}
],
QA:[
{field:'personal',sev:'warn',rule:'Photo and nationality standard for Qatar applications',fix:'Include professional photo and nationality in personal information'},
{field:'personal',sev:'warn',rule:'Work permit/NOC status important for Qatar roles',fix:'State current visa status and availability for QatarEnergy/private sector NOC'},
{field:'summary',sev:'ok',rule:'Gulf/MENA region experience is highly valued',fix:'✓ Highlight any GCC experience in your summary'},
{field:'skills',sev:'ok',rule:'Arabic language skills advantageous in Qatar',fix:'Add Arabic proficiency if applicable'}
],
SA:[
{field:'personal',sev:'warn',rule:'Photo, DOB, and nationality expected for Saudi Arabia',fix:'Include professional photo, date of birth, and nationality'},
{field:'personal',sev:'warn',rule:'Iqama/work permit/NOC sponsorship details expected',fix:'Add current visa status and employer NOC details if already in KSA'},
{field:'summary',sev:'ok',rule:'Saudi Vision 2030 sectors (tech, energy, finance) in high demand',fix:'Align your summary to Vision 2030 sectors if relevant'},
{field:'experience',sev:'warn',rule:'Conservative, formal tone required throughout',fix:'Avoid casual language. Use formal, achievement-focused writing throughout.'}
],
CA:[
{field:'personal',sev:'ok',rule:'No photo required on Canadian résumés',fix:'✓ Standard Canadian format — no photo needed'},
{field:'personal',sev:'warn',rule:'Note NOC (National Occupational Classification) code alignment for immigration',fix:'Identify your NOC code and ensure your job titles align with it'},
{field:'summary',sev:'ok',rule:'Bilingual (French/English) skills are a significant advantage in Canada',fix:'Add French language proficiency if applicable. Strongly valued in Quebec and federal roles.'},
{field:'skills',sev:'ok',rule:'Canadian qualification equivalency recommended for internationally trained professionals',fix:'Note "Internationally Educated [Profession]" and any IQAS/WES credential evaluation'}
],
AU:[
{field:'personal',sev:'ok',rule:'No photo standard for Australian CVs',fix:'✓ Correct format — photos not expected in Australia'},
{field:'personal',sev:'warn',rule:'Working rights status important for non-citizens',fix:'Add "Working Rights: [Australian Citizen / PR / Working Holiday Visa]"'},
{field:'experience',sev:'ok',rule:'2–3 pages acceptable for Australian CVs (longer than UK/US)',fix:'✓ Australian standard allows more detail than UK/US'},
{field:'skills',sev:'warn',rule:'Regulated professions need VETASSESS/ANMAC/ACS skills assessment noted',fix:'Add "Skills Assessment: [ACS/ANMAC/VETASSESS Positive Assessment]" if applicable'}
],
IE:[
{field:'personal',sev:'ok',rule:'UK-similar CV format — no photo required',fix:'✓ Standard Irish CV format. Photo optional.'},
{field:'personal',sev:'warn',rule:'Right-to-work / Stamp status recommended for non-EEA applicants',fix:'Add "Stamp 4/Work Authorisation: [Yes]" to summary or cover letter'},
{field:'experience',sev:'ok',rule:'Maximum 2 pages standard for Irish CVs',fix:'✓ Keep CV to 2 pages for most roles'}
],
NL:[
{field:'personal',sev:'ok',rule:'Photo optional in Netherlands — not required',fix:'Photo is personal choice. Large international companies prefer no photo.'},
{field:'personal',sev:'warn',rule:'Dutch language proficiency is a competitive advantage',fix:'Add Dutch language level (A1–C2) to skills if applicable'},
{field:'experience',sev:'ok',rule:'1–2 pages clean format preferred',fix:'✓ Concise, clear format preferred by Dutch employers'}
],
ZA:[
{field:'personal',sev:'warn',rule:'Photo common in South African CVs',fix:'Include a professional headshot — widely expected in South Africa'},
{field:'personal',sev:'warn',rule:'B-BBEE status (if applicable) may be relevant for transformation-focused roles',fix:'Note B-BBEE status if applicable in your personal information'},
{field:'experience',sev:'ok',rule:'3–4 pages acceptable for SA CVs — more detail expected',fix:'✓ South African CV format allows more detail than UK/US'},
{field:'experience',sev:'warn',rule:'Include 3 contactable references with their positions and phone numbers',fix:'Add a References section or note "References available on request" at the end'}
],
FR:[
{field:'personal',sev:'warn',rule:'Photo (photo d\'identité) is common but increasingly optional in France',fix:'A professional photo is still broadly expected for French applications'},
{field:'personal',sev:'warn',rule:'French language proficiency level must be clearly stated',fix:'Add "French: [Native/C1/B2]" to your skills/languages section'},
{field:'experience',sev:'ok',rule:'French CV (CV) is typically 1–2 pages',fix:'✓ Keep concise — French CVs are typically shorter than UK/US'},
{field:'experience',sev:'warn',rule:'Diplomas and qualifications must be listed precisely',fix:'Use exact diploma names (Bac, BTS, Licence, Master, Grandes Écoles)'}
]
};
function renderComplianceRules(){
const rules=COMPLY_RULES[STATE.country]||[];
const container=document.getElementById('comply-personal-rules');
if(!container) return;
if(!rules.length){container.innerHTML='';return;}
container.innerHTML=rules.map(r=>`
${r.sev==='ok'?'✅':r.sev==='warn'?'⚠️':'❌'}
`).join('');
}
/* ════════════════════════════════════════════════════
v7 — BUTTON WIRING ADDITIONS
════════════════════════════════════════════════════ */
function initV7Buttons(){
// Real DOCX export
document.getElementById('btn-export-docx')?.addEventListener('click',buildRealDocx);
// ATS
document.getElementById('btn-ats-header')?.addEventListener('click',()=>{
document.querySelectorAll('.nav-tab').forEach(t=>t.classList.remove('active'));
document.querySelectorAll('.section-pane').forEach(p=>p.classList.remove('active'));
document.getElementById('nav-ats')?.classList.add('active');
document.getElementById('pane-ats')?.classList.add('active');
});
document.getElementById('ats-scan-btn')?.addEventListener('click',runATSScan);
document.getElementById('ats-clear-btn')?.addEventListener('click',()=>{
const inp=document.getElementById('ats-jd-input');
if(inp) inp.value='';
const res=document.getElementById('ats-results');
if(res) res.style.display='none';
});
// Multi-role
document.getElementById('role-add-btn')?.addEventListener('click',()=>{
const row3=document.getElementById('role-row-3');
if(row3){row3.style.display='flex';document.getElementById('role-add-btn').style.display='none';}
});
document.getElementById('role-remove-3')?.addEventListener('click',()=>{
const row3=document.getElementById('role-row-3');
if(row3){row3.style.display='none';document.getElementById('role-inp-3').value='';document.getElementById('role-add-btn').style.display='';}
});
document.getElementById('role-analyse-btn')?.addEventListener('click',runMultiRoleAnalysis);
document.getElementById('role-blend-btn')?.addEventListener('click',generateBlendedSummary);
document.getElementById('role-apply-summary')?.addEventListener('click',()=>{
if(window._BLENDED_SUMMARY){
document.getElementById('inp-summary').value=window._BLENDED_SUMMARY;
document.getElementById('inp-summary').dispatchEvent(new Event('input'));
notify('Blended summary applied to Professional Summary ✓','success');
}
});
document.getElementById('role-apply-skills')?.addEventListener('click',()=>{
if(window._ROLE_ANALYSIS){
const{inAll,inSome}=window._ROLE_ANALYSIS;
let added=0;
[...inAll,...inSome].slice(0,10).forEach(kw=>{
const formatted=kw.split(' ').map(w=>w.charAt(0).toUpperCase()+w.slice(1)).join(' ');
if(!STATE.skills.includes(formatted)){STATE.skills.push(formatted);added++;}
});
renderSkillTags();updatePreview();updateReadiness();
renderPortfolioSkillLevels&&renderPortfolioSkillLevels();
notify(added+' cross-role skills added ✓','success');
}
});
// v8 new feature buttons
document.getElementById('btn-cl-autogen')?.addEventListener('click', autoGenerateCoverLetter);
document.getElementById('pf-export-docx-btn')?.addEventListener('click', exportPortfolioDocx);
document.getElementById('pf-export-pdf-btn')?.addEventListener('click', exportPortfolioPdf);
}
document.addEventListener('DOMContentLoaded',()=>{
initV7Buttons();
renderComplianceRules();
});
/* ═══════════════════════════════════════════════════════════════
v8 — COVER LETTER AUTO-GENERATION (linked to resume)
═══════════════════════════════════════════════════════════════ */
function autoGenerateCoverLetter(){
const nm=[val('inp-firstname'),val('inp-lastname')].filter(Boolean).join(' ');
const title=val('inp-title')||'Professional';
const targetRole=val('inp-target-role')||title;
const targetCo=val('inp-target-company')||'[Company Name]';
const level=val('inp-level')||'mid';
const industry=val('inp-industry')||'general';
const c=COUNTRIES[STATE.country];
const data=getMatrixData();
const yrsMap={entry:'2+',mid:'5+',senior:'12+',executive:'20+'};
const yrs=yrsMap[level]||'5+';
const topSkills=STATE.skills.slice(0,3).join(', ')||profLabel(industry)+' competencies';
const recentExp=STATE.experience[0];
const currentRole=recentExp?recentExp.title+' at '+recentExp.company:'my current role';
const achievement=recentExp&&recentExp.bullets?
(recentExp.bullets.split('\n').filter(b=>b.trim())[0]||'').replace(/^[•\-\*]\s*/,'').trim():
'delivering measurable results that consistently exceed stakeholder expectations';
const countryName=c?c.name:STATE.country;
// Opening paragraph — country-specific
const openings={
UK:'I am writing with genuine enthusiasm to apply for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of progressive experience in '+profLabel(industry)+' and a strong track record of delivering measurable results within UK professional environments, I am confident in my ability to make an immediate and lasting contribution to your organisation.',
US:'I am excited to apply for the '+targetRole+' opportunity at '+targetCo+'. My '+yrs+' years of experience in '+profLabel(industry)+', combined with a demonstrated record of driving measurable business outcomes, make me an ideal candidate for this role.',
CA:'I am pleased to submit my application for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of Canadian-market experience in '+profLabel(industry)+' and a consistent record of exceeding performance objectives, I am well-positioned to contribute meaningfully to your organisation.',
DE:'I am applying for the position of '+targetRole+' at '+targetCo+'. With '+yrs+' years of professional experience in '+profLabel(industry)+', I bring the technical expertise and systematic approach that your organisation requires.',
AU:'I am writing to express my strong interest in the '+targetRole+' role at '+targetCo+'. With '+yrs+' years of '+profLabel(industry)+' experience and confirmed Australian working rights, I am ready to contribute from day one.',
AE:'I am applying for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of regional and international experience in '+profLabel(industry)+' across GCC markets, my background aligns closely with your requirements.',
QA:'I am applying for the '+targetRole+' role at '+targetCo+'. With '+yrs+' years of experience in '+profLabel(industry)+' and proven GCC exposure, I am confident in my ability to add immediate value to your operations.',
SA:'I am writing to apply for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of '+profLabel(industry)+' experience and a commitment to the professional standards expected in the Kingdom, I am eager to contribute to your organisation.',
ZA:'I am applying for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of South African and regional experience in '+profLabel(industry)+', I am well-positioned to contribute meaningfully to your team.',
FR:'Je souhaite poser ma candidature au poste de '+targetRole+' au sein de '+targetCo+'. Fort de '+yrs+' annees d experience en '+profLabel(industry)+', je suis convaincu de pouvoir apporter une contribution significative a votre equipe.',
NL:'I am applying for the '+targetRole+' role at '+targetCo+'. With '+yrs+' years of '+profLabel(industry)+' experience and a collaborative, results-driven work style, I am excited by the opportunity to contribute to your team.',
IE:'I am writing to apply for the '+targetRole+' position at '+targetCo+'. With '+yrs+' years of experience in '+profLabel(industry)+' and the right to work in Ireland, I am confident in my ability to make an immediate contribution.'
};
const opening=openings[STATE.country]||openings.UK;
// Body — uses MATRIX template then personalises
let bodyTpl=data.clBody||'In my current role as '+currentRole+', I have been instrumental in [KEY_ACH]. I bring expertise in [SKILLS] and am particularly drawn to [COMPANY] reputation for excellence.';
const body=bodyTpl
.replace('[CURRENT EMPLOYER]',recentExp?recentExp.company:'[Current Employer]')
.replace('[KEY ACHIEVEMENT]',achievement||'driving operational improvements')
.replace('[KEY_ACH]',achievement||'driving operational improvements')
.replace('[QUANTIFIED OUTCOME]','significant and measurable organisational value')
.replace(/\[COMPANY\]/g,targetCo).replace(/\[ROLE\]/g,targetRole)
.replace(/\[SPECIALTY[^\]]*\]/g,topSkills).replace(/\[STACK\]/g,topSkills)
.replace(/\[TECH STACK\]/g,topSkills).replace(/\[TECHNICAL SPECIALTY\]/g,topSkills)
.replace(/\[NHS TRUST[^\]]*\]/g,targetCo).replace(/\[HOSPITAL[^\]]*\]/g,targetCo)
.replace(/\[SCHOOL\]/g,targetCo).replace(/\[ORGANISATION\]/g,targetCo)
.replace(/\[INSTITUTION\]/g,targetCo).replace(/\[FIRM[^\]]*\]/g,targetCo)
.replace(/\[HOTEL[^\]]*\]/g,targetCo).replace(/\[BRAND\]/g,targetCo)
.replace(/\[SOURCE\]/g,'your company website')
.replace(/\[X\]/g,yrs).replace(/\[VALUE[^\]]*\]/g,'excellence and sustained impact')
.replace(/\[AREA[^\]]*\]/g,profLabel(industry))
.replace(/\[OUTCOME[^\]]*\]/g,'measurable positive outcomes')
.replace(/\[KEY PROJECT\]/g,'key strategic initiatives')
.replace(/\[YEAR\] PQE/g,yrs+' PQE')
.replace(/\[PRACTICE AREA[^\]]*\]/g,profLabel(industry))
.replace(/\[CLINICAL[^\]]*\]/g,STATE.skills[0]||'patient care')
.replace(/\[OPERATIONS AREA\]/g,profLabel(industry))
.replace(/\[[^\]]+\]/g,''); // remove any remaining brackets
// Closing
const closings={
UK:'I would very much welcome the opportunity to discuss how my background aligns with your requirements. I am available for interview at your earliest convenience and can provide references on request. Thank you sincerely for your time and consideration.',
US:'I would welcome the opportunity to discuss how my skills and experience can benefit '+targetCo+'. I am available for an interview at your convenience. Thank you for your time and consideration.',
AE:'I would be delighted to discuss this opportunity further and can confirm my immediate availability for an interview. Thank you for considering my application.',
AU:'I would welcome the opportunity to discuss my application further at your convenience. I am happy to provide references and supporting documentation on request. Thank you for your consideration.',
CA:'I welcome the opportunity to discuss how I can contribute to '+targetCo+' and its objectives. I am available for an interview at your convenience. Thank you for reviewing my application.',
DE:'Ich freue mich auf die Moglichkeit, meine Bewerbung in einem personlichen Gesprach naher zu erlautern. Fur Ruckfragen stehe ich Ihnen jederzeit zur Verfugung.',
ZA:'I look forward to the opportunity to discuss my application. I am available for an interview at your convenience and can provide three contactable references. Thank you for your time.'
};
const closing=closings[STATE.country]||closings.UK;
// Fill fields
if(!val('cl-manager'))setVal('cl-manager','The Hiring Manager');
setVal('cl-company',targetCo);
setVal('cl-position',targetRole);
setVal('cl-opening',opening);
setVal('cl-body',body);
setVal('cl-closing',closing);
['cl-opening','cl-body','cl-closing','cl-manager','cl-company','cl-position'].forEach(id=>{
document.getElementById(id)?.dispatchEvent(new Event('input'));
});
updatePreview(); updateReadiness();
notify('Cover letter auto-generated from resume data ✓','success');
}
/* ═══════════════════════════════════════════════════════════════
v8 — PORTFOLIO PDF EXPORT
═══════════════════════════════════════════════════════════════ */
function exportPortfolioPdf(){
const html=generatePortfolioHTML();
const printHtml=html.replace('',
''+
'');
const blob=new Blob([printHtml],{type:'text/html;charset=utf-8'});
const url=URL.createObjectURL(blob);
window.open(url,'_blank');
setTimeout(()=>URL.revokeObjectURL(url),60000);
notify('Portfolio opened in new tab — use Ctrl+P / Cmd+P to save as PDF','success');
}
/* ═══════════════════════════════════════════════════════════════
v8 — PORTFOLIO DOCX EXPORT
═══════════════════════════════════════════════════════════════ */
function exportPortfolioDocx(){
const nm=[val('inp-firstname'),val('inp-lastname')].filter(Boolean).join(' ')||'Applicant';
const title=val('inp-title')||'';
const summary=val('inp-summary')||'';
const email=val('inp-email')||''; const phone=val('inp-phone')||'';
const city=val('inp-city')||''; const linkedin=val('inp-linkedin')||'';
const parts=[];
// Title page
parts.push('${r.rule}
${r.fix}
${country ? country.flag + ' ' : ''}${city || 'Available Worldwide'}
${name.split(' ')[0]} ${name.split(' ').slice(1).join(' ')}
${escHtml(title)}
${escHtml(tagline)}
scroll
About Me
A dedicated ${escHtml(title)}
${escHtml(summary)}
${STATE.skills.slice(0,6).map(s=>`${escHtml(s)}`).join('')}
${expYrs}
Years Experience
${profLabel(prof)} professional
${STATE.skills.length}+
Skills & Tools
Across technical & professional domains
${STATE.projects.length}
Notable Projects
Delivered with measurable impact
Work History
Professional Experience
A track record of delivering results across sectors and markets.
${STATE.experience.map(e => {
const buls = (e.bullets||'').split('\n').filter(b=>b.trim()).map(b=>`${escHtml(b.replace(/^[•\-\*]\s*/,''))} `).join('');
return `
`;
}).join('')}
${escHtml(e.title||'')}
${escHtml(e.start||'')}${e.end?' – '+e.end:''}
${escHtml(e.company||'')}${e.location?' · '+e.location:''}
${buls?`- ${buls}
Competencies
Skills & Expertise
Technical proficiency and professional capabilities.
${STATE.skills.map((s,i) => {
const lvl = PF_STATE.skillLevels[s] || Math.max(3, 5 - (i % 3));
const pct = lvl * 20;
return `
`;
}).join('')}
${escHtml(s)}${pct}%
Portfolio
Selected Projects
Showcasing work that demonstrates impact and innovation.
${STATE.projects.map(p => `
`).join('')}
${p.year?`
${escHtml(p.year)}
`:''}
${escHtml(p.name||'')}
${escHtml(p.desc||'')}
${p.tech?`${p.tech.split(',').map(t=>`${escHtml(t.trim())}`).join('')}
`:''}
Credentials
Certifications & Qualifications
Professionally recognised credentials that validate expertise.
${STATE.certifications.map(c => `
`).join('')}
${escHtml(c.name||'')}
${escHtml(c.issuer||'')}
${c.year?`${escHtml(c.year)} · ${escHtml(c.expiry||'Active')}
`:''}
Testimonials
What People Say
"An exceptional professional who consistently delivers above expectations. A genuine asset to any organisation."
Senior Manager
Former Employer
Get In Touch
Ready to Work Together?
I am currently ${PF_STATE.theme === 'arctic' ? 'accepting new opportunities' : 'open to opportunities'} and would love to discuss how I can contribute to your organisation.
${contactLinks}
📩 Hire Me Now →
