Resume Generator A

GEMI Controlled Application Engine™ v10 — Pro Suite
Processing…
🇬🇧
0%
0%
Readiness
Personal Information
Your core identity details
🇬🇧Loading country guidance…
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
🛂 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
Your Full Name
Professional Title
📧 [email protected]📞 +000 000 0000📍 City, Country

${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+='
'+cat.label+'
'; cat.qs.forEach((item,i)=>{ const id='iq-'+cat.label.replace(/[^a-z]/gi,'')+i; 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+='
'+lbl+'
'+esc(txt)+'
'; }); 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)=>'
'+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)+'
').join(''); } // Warnings const wEl=document.getElementById('visa-warnings'); if(wEl) wEl.innerHTML=(type.warnings||[]).map(w=>'
⚠️ '+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 '
'+'
'+( isChecked?'✓':'' )+'
'+'
'+esc(item.t)+'
'+(item.n?'
'+esc(item.n)+'
':'')+'
'; }).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>>8)^tbl[(crc^data[i])&0xff]; return(~crc)>>>0; } function buildZip(files){ const enc=new TextEncoder(); const localHdrs=[],cdEntries=[],fileData=[]; let offset=0; files.forEach(({name,data})=>{ const fd=typeof data==='string'?enc.encode(data):data; const nb=enc.encode(name); const crc=crc32(fd); const lh=new Uint8Array(30+nb.length); const lv=new DataView(lh.buffer); lv.setUint32(0,0x04034b50,true);lv.setUint16(4,20,true); lv.setUint16(6,0,true);lv.setUint16(8,0,true); lv.setUint16(10,0,true);lv.setUint16(12,0,true); lv.setUint32(14,crc,true);lv.setUint32(18,fd.length,true);lv.setUint32(22,fd.length,true); lv.setUint16(26,nb.length,true);lv.setUint16(28,0,true); lh.set(nb,30); localHdrs.push(lh); fileData.push(fd); const cd=new Uint8Array(46+nb.length); const cv=new DataView(cd.buffer); cv.setUint32(0,0x02014b50,true);cv.setUint16(4,20,true);cv.setUint16(6,20,true); cv.setUint16(8,0,true);cv.setUint16(10,0,true);cv.setUint16(12,0,true);cv.setUint16(14,0,true); cv.setUint32(16,crc,true);cv.setUint32(20,fd.length,true);cv.setUint32(24,fd.length,true); cv.setUint16(28,nb.length,true);cv.setUint16(30,0,true);cv.setUint16(32,0,true); cv.setUint16(34,0,true);cv.setUint16(36,0,true);cv.setUint32(38,0x20,true); cv.setUint32(42,offset,true);cd.set(nb,46);cdEntries.push(cd); offset+=lh.length+fd.length; }); const cdSize=cdEntries.reduce((s,e)=>s+e.length,0); const eocd=new Uint8Array(22); const ev=new DataView(eocd.buffer); ev.setUint32(0,0x06054b50,true);ev.setUint16(4,0,true);ev.setUint16(6,0,true); ev.setUint16(8,files.length,true);ev.setUint16(10,files.length,true); ev.setUint32(12,cdSize,true);ev.setUint32(16,offset,true);ev.setUint16(20,0,true); const parts=[]; files.forEach((_,i)=>{parts.push(localHdrs[i]);parts.push(fileData[i]);}); cdEntries.forEach(e=>parts.push(e)); parts.push(eocd); const total=parts.reduce((s,p)=>s+p.length,0); const out=new Uint8Array(total); let pos=0; parts.forEach(p=>{out.set(p,pos);pos+=p.length;}); return out; } function xmlEsc(s){return String(s||'').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');} function W(ns){return `xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"`;} function wpara(prXml,runs){return `${prXml||''}${runs}`;} function wrun(text,rpr){return `${rpr?`${rpr}`:''}${xmlEsc(text)}`;} function wsec(title){return `${xmlEsc(title)}`;} function wbullet(text){return `${xmlEsc(text)}`;} function buildRealDocx(){ const nm=[val('inp-firstname'),val('inp-lastname')].filter(Boolean).join(' ')||'Applicant'; const title=val('inp-title')||''; const email=val('inp-email')||''; const phone=val('inp-phone')||''; const city=val('inp-city')||''; const linkedin=val('inp-linkedin')||''; const summary=val('inp-summary')||''; const contact=[email,phone,city,linkedin].filter(Boolean).join(' · '); const country=COUNTRIES[STATE.country]; const parts=[]; // Name parts.push(`${xmlEsc(nm)}`); if(title) parts.push(`${xmlEsc(title)}`); if(contact) parts.push(`${xmlEsc(contact)}`); // Country compliance note if(country) parts.push(`Target Country: ${xmlEsc(country.name)}`); // Summary if(summary){parts.push(wsec('Professional Summary'));parts.push(`${xmlEsc(summary)}`);} // Experience if(STATE.experience.length){ parts.push(wsec('Work Experience')); STATE.experience.forEach(e=>{ parts.push(`${xmlEsc(e.title||'')}${xmlEsc((e.start||'')+(e.end?' – '+e.end:''))}`); parts.push(`${xmlEsc((e.company||'')+(e.location?' · '+e.location:''))}`); if(e.bullets) e.bullets.split('\n').filter(b=>b.trim()).forEach(b=>parts.push(wbullet(b.replace(/^[•\-\*]\s*/,'')))); }); } // Education if(STATE.education.length){ parts.push(wsec('Education')); STATE.education.forEach(e=>{ parts.push(`${xmlEsc(e.degree||'')}${xmlEsc(e.year||'')}`); if(e.school) parts.push(`${xmlEsc(e.school+(e.grade?' · '+e.grade:''))}`); }); } // Skills if(STATE.skills.length){parts.push(wsec('Skills'));parts.push(`${xmlEsc(STATE.skills.join(' · '))}`);} // Certifications if(STATE.certifications.length){ parts.push(wsec('Certifications')); STATE.certifications.forEach(c=>{ parts.push(`${xmlEsc(c.name||'')}${c.issuer?` — ${xmlEsc(c.issuer)}`:''}${c.year?` (${xmlEsc(c.year)})`:''}`); }); } // Projects if(STATE.projects.length){ parts.push(wsec('Projects')); STATE.projects.forEach(p=>{ parts.push(`${xmlEsc(p.name||'')}${p.year?' ('+p.year+')':''}${p.tech?` · ${xmlEsc(p.tech)}`:''}`); if(p.desc) parts.push(`${xmlEsc(p.desc)}`); }); } // Cover letter page const clOp=val('cl-opening'),clBd=val('cl-body'),clCl=val('cl-closing'); if(clOp||clBd){ parts.push(``); parts.push(`${xmlEsc(new Date().toLocaleDateString('en-GB',{day:'numeric',month:'long',year:'numeric'}))}`); const mgr=val('cl-manager')||'The Hiring Manager'; parts.push(`${xmlEsc(mgr)}`); if(val('cl-company')) parts.push(`${xmlEsc(val('cl-company'))}`); const pos=val('cl-position')||val('inp-target-role')||title; parts.push(`Re: Application for the Position of ${xmlEsc(pos)}`); const greet=mgr.toLowerCase().includes('hiring')?'Dear Hiring Manager,':'Dear '+mgr+','; parts.push(`${xmlEsc(greet)}`); [clOp,clBd,clCl].filter(Boolean).forEach(p=>{parts.push(`${xmlEsc(p)}`);}); parts.push(`Yours sincerely,`); parts.push(`${xmlEsc(nm)}`); const ctc=[phone,email].filter(Boolean).join(' · '); if(ctc) parts.push(`${xmlEsc(ctc)}`); } // Footer parts.push(`Generated by GEMI Controlled Application Engine™ v10 · gemitravelandtour.com · info@gemitravelandtour.com`); const docXml=`${parts.join('')}`; const stylesXml=``; const numberingXml=``; const settingsXml=``; const ctXml=``; const relsXml=``; const wordRelsXml=``; const zipData=buildZip([ {name:'[Content_Types].xml',data:ctXml}, {name:'_rels/.rels',data:relsXml}, {name:'word/document.xml',data:docXml}, {name:'word/_rels/document.xml.rels',data:wordRelsXml}, {name:'word/styles.xml',data:stylesXml}, {name:'word/settings.xml',data:settingsXml}, {name:'word/numbering.xml',data:numberingXml} ]); const blob=new Blob([zipData],{type:'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=(nm.replace(/\s+/g,'_')||'CV')+'_GEMI_v7.docx'; a.click(); URL.revokeObjectURL(url); notify('✅ Real DOCX downloaded — opens in Word, Google Docs, LibreOffice','success'); } /* ════════════════════════════════════════════════════ v7 — ATS KEYWORD SCANNER ENGINE ════════════════════════════════════════════════════ */ const ATS_STOP = new Set(['the','a','an','and','or','in','of','to','for','with','on','at','by','from','is','are','was','were','be','been','have','has','had','will','would','could','should','may','might','that','this','these','those','their','they','we','you','your','our','its','it','as','if','i','my','but','not','so','do','does','did','all','more','other','such','when','which','who','how','what','each','any','some','into','than','about','also','both','during','through','while','after','before','between','under','over','within','without']); function atsTokenize(text){ const words=text.toLowerCase().replace(/[^a-z0-9\s\-\/\+#\.]/g,' ').split(/\s+/).filter(w=>w.length>2&&!ATS_STOP.has(w)); // Also extract bigrams (2-word phrases) const bigrams=[]; const raw=text.toLowerCase().split(/\s+/).map(w=>w.replace(/[^a-z0-9\-\/\+#\.]/g,'')); for(let i=0;i2&&raw[i+1].length>2&&!ATS_STOP.has(raw[i])&&!ATS_STOP.has(raw[i+1])){ bigrams.push(raw[i]+' '+raw[i+1]); } } return{words:[...new Set(words)],bigrams:[...new Set(bigrams)]}; } function getCVText(){ const parts=[val('inp-title'),val('inp-summary'),val('inp-target-role'),...STATE.skills,...STATE.experience.map(e=>[(e.title||''),(e.company||''),(e.bullets||'')].join(' ')),...STATE.certifications.map(c=>c.name||''),...STATE.projects.map(p=>p.name+' '+(p.tech||''))]; return parts.join(' ').toLowerCase(); } function runATSScan(){ const jd=document.getElementById('ats-jd-input').value.trim(); if(!jd){notify('Paste a job description first','');return;} const {words,bigrams}=atsTokenize(jd); const cvText=getCVText(); // Score bigrams first (weighted higher) const allKws=[...bigrams.slice(0,30),...words.slice(0,60)]; const matched=[],missing=[]; allKws.forEach(kw=>{ const present=cvText.includes(kw); (present?matched:missing).push(kw); }); // Categorise const techTerms=new Set(['javascript','python','java','react','sql','aws','excel','sap','oracle','node','typescript','docker','kubernetes','api','agile','scrum','devops','cloud','data','analytics','machine learning','power bi','tableau','git','ci/cd','linux','azure','gcp']); const softTerms=new Set(['communication','leadership','teamwork','problem solving','management','strategy','analysis','planning','reporting','coordination','stakeholder','presentation','negotiation','mentoring','project management','time management','critical thinking','adaptability','collaboration']); const certTerms=new Set(['acca','cpa','cfa','pmp','prince2','aws certified','cissp','itil','cima','mba','phd','msc','bsc','certification','qualified','certified','licensed','accredited']); const cats={tech:{label:'Technical Skills',matched:0,total:0,color:'var(--info)'},soft:{label:'Soft Skills',matched:0,total:0,color:'var(--success)'},cert:{label:'Qualifications',matched:0,total:0,color:'var(--gold)'},other:{label:'Domain Terms',matched:0,total:0,color:'var(--purple)'}}; [...matched,...missing].forEach(kw=>{ const cat=techTerms.has(kw)?'tech':softTerms.has(kw)?'soft':certTerms.has(kw)?'cert':'other'; cats[cat].total++; if(matched.includes(kw)) cats[cat].matched++; }); const total=allKws.length||1; const matchedPct=Math.round((matched.length/total)*100); document.getElementById('ats-results').style.display=''; const arc=document.getElementById('ats-ring-arc'); const circumference=289; setTimeout(()=>{ arc.setAttribute('stroke-dasharray',`${(matchedPct/100)*circumference} ${circumference}`); arc.setAttribute('stroke',matchedPct>=80?'var(--success)':matchedPct>=50?'var(--gold)':'var(--danger)'); },100); document.getElementById('ats-ring-pct').textContent=matchedPct+'%'; document.getElementById('ats-score-verdict').textContent=matchedPct>=80?'✅ Strong ATS Match — likely to pass screening':matchedPct>=60?'⚠️ Moderate match — add missing keywords':matchedPct>=40?'🔄 Weak match — significant gaps found':'🚫 Poor match — major keyword gaps'; // Category bars const catsGrid=document.getElementById('ats-cats-grid'); catsGrid.innerHTML=Object.entries(cats).map(([k,c])=>{ const pct=c.total?Math.round((c.matched/c.total)*100):0; return `
${c.label}
${pct}% (${c.matched}/${c.total})
`; }).join(''); // Matched keywords const matchEl=document.getElementById('ats-matched-kws'); matchEl.innerHTML=matched.slice(0,30).map(kw=>`✓ ${kw}`).join(''); if(!matched.length) matchEl.innerHTML='No matching keywords found yet.'; // Missing keywords (clickable to add) const missEl=document.getElementById('ats-missing-kws'); missEl.innerHTML=missing.slice(0,40).map(kw=>`+ ${kw}`).join(''); missEl.querySelectorAll('.ats-kw.missing').forEach(btn=>{ btn.addEventListener('click',()=>{ const kw=btn.dataset.kw; if(!STATE.skills.includes(kw)){STATE.skills.push(kw);renderSkillTags();updatePreview();updateReadiness();renderPortfolioSkillLevels&&renderPortfolioSkillLevels();} btn.className='ats-kw matched';btn.textContent='✓ '+kw; notify('"'+kw+'" added to Skills ✓','success'); }); }); // Tips const tipsEl=document.getElementById('ats-tips'); const tips=[]; if(matchedPct<60) tips.push('Your CV matches fewer than 60% of job description keywords — add the missing terms above to your Skills section or weave them into your Summary.'); if(!val('inp-target-role')) tips.push('Add a Target Job Title in the Summary section — ATS systems score role-title alignment heavily.'); if(STATE.skills.length<8) tips.push('You have fewer than 8 skills listed. ATS systems weight skill density. Use the Skills tab to bulk-add industry keywords.'); if(!val('inp-summary')||val('inp-summary').length<200) tips.push('Your Professional Summary is too short. Aim for 80–120 words packed with role-relevant keywords from the job description.'); if(STATE.experience.every(e=>!e.bullets||e.bullets.length<50)) tips.push('Your experience bullets are thin. Use the Template Library to add quantified achievement bullets containing role-specific keywords.'); tips.push('Mirror the exact language used in the job posting — ATS systems match exact strings, not synonyms. If the JD says "Financial Modelling" use exactly that term, not "Financial Modeling" or "Financial Analysis".'); tipsEl.innerHTML=tips.map(t=>`
💡 ${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'?'⚠️':'❌'}
${r.rule}
${r.fix}
`).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(''+xmlEsc(nm)+''); if(title)parts.push(''+xmlEsc(title)+''); const contact=[email,phone,city].filter(Boolean).join(' · '); if(contact)parts.push(''+xmlEsc(contact)+''); if(linkedin)parts.push(''+xmlEsc(linkedin)+''); // About if(summary){ parts.push(wsec('About Me')); parts.push(''+xmlEsc(summary)+''); } // Experience if(STATE.experience.length){ parts.push(wsec('Professional Experience')); STATE.experience.forEach(function(e){ parts.push(''+xmlEsc(e.title||'')+''+xmlEsc((e.start||'')+(e.end?' – '+e.end:''))+''); parts.push(''+xmlEsc((e.company||'')+(e.location?' · '+e.location:''))+''); if(e.bullets)e.bullets.split('\n').filter(function(b){return b.trim();}).forEach(function(b){parts.push(wbullet(b.replace(/^[•\-\*]\s*/,'')));}); }); } // Skills if(STATE.skills.length){ parts.push(wsec('Skills & Expertise')); // Group into rows of 5 var rows=[]; for(var i=0;i'+xmlEsc(row)+'');}); } // Projects if(STATE.projects.length){ parts.push(wsec('Featured Projects')); STATE.projects.forEach(function(p){ parts.push(''+xmlEsc(p.name||'')+(p.year?' ('+p.year+')':'')+''+(p.tech?' · '+xmlEsc(p.tech)+'':'')+''); if(p.desc)parts.push(''+xmlEsc(p.desc)+''); }); } // Certifications if(STATE.certifications.length){ parts.push(wsec('Certifications')); STATE.certifications.forEach(function(c){parts.push(''+xmlEsc(c.name||'')+''+(c.issuer?' — '+xmlEsc(c.issuer)+'':'')+(c.year?' ('+xmlEsc(c.year)+')':'')+'');}); } // Contact parts.push(wsec('Contact')); var ctcLines=[[email,'📧 '+email],[phone,'📞 '+phone],[city,'📍 '+city],[linkedin,'🔗 '+linkedin]].filter(function(x){return x[0];}); ctcLines.forEach(function(cl){parts.push(''+xmlEsc(cl[1])+'');}); // Footer parts.push('Portfolio generated by GEMI CAE™ v9 · gemitravelandtour.com'); const WNS='xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'; const docXml=''+parts.join('')+''; const stylesXml=''; const numXml=''; const settXml=''; const ctXml=''; const relsXml=''; const wRelsXml=''; const zip=buildZip([{name:'[Content_Types].xml',data:ctXml},{name:'_rels/.rels',data:relsXml},{name:'word/document.xml',data:docXml},{name:'word/_rels/document.xml.rels',data:wRelsXml},{name:'word/styles.xml',data:stylesXml},{name:'word/settings.xml',data:settXml},{name:'word/numbering.xml',data:numXml}]); const blob=new Blob([zip],{type:'application/vnd.openxmlformats-officedocument.wordprocessingml.document'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=(nm.replace(/\s+/g,'_')||'Portfolio')+'_Portfolio_GEMI_v8.docx'; a.click(); URL.revokeObjectURL(url); notify('Portfolio DOCX exported — opens in Word, Google Docs, LibreOffice ✓','success'); } /* ═══════════════════════════════════════════════ PORTFOLIO STATE ═══════════════════════════════════════════════ */ const PF_STATE = { theme: 'midnight', accent: 'theme', sections: { hero:true, about:true, experience:true, skills:true, projects:true, certifications:true, contact:true, testimonials:false }, skillLevels: {}, tagline: '', github: '', linkedinUrl: '', ctaLink: '', years: '' }; /* ═══════════════════════════════════════════════ THEME CONFIGS ═══════════════════════════════════════════════ */ const PF_THEMES = { midnight: { name:'Midnight', bg:'#06060a', surface:'#0f0f18', card:'#14141f', border:'#252535', accent:'#d4a843', accentDk:'#9c7520', accentLt:'#e8c96a', text:'#f0ead8', muted:'#6a6a84', heading:'#f0ead8', navBg:'rgba(6,6,10,0.92)', heroGrad:'linear-gradient(135deg,#06060a 0%,#0f0f18 50%,#1a1408 100%)', timelineLine:'#252535', sectionAlt:'#0f0f18', fontHead:"'Palatino Linotype','Book Antiqua',Palatino,Georgia,serif", fontBody:"'Gill Sans','Gill Sans MT',Calibri,sans-serif", badgeBg:'rgba(212,168,67,0.12)', badgeBorder:'rgba(212,168,67,0.3)', }, arctic: { name:'Arctic', bg:'#f4f7fb', surface:'#ffffff', card:'#f0f4f8', border:'#dde4f0', accent:'#1e5ff0', accentDk:'#1448c8', accentLt:'#4a80f5', text:'#1a1a2e', muted:'#6b7a99', heading:'#0a0a1a', navBg:'rgba(244,247,251,0.95)', heroGrad:'linear-gradient(135deg,#1a1a3a 0%,#1e5ff0 100%)', timelineLine:'#dde4f0', sectionAlt:'#f0f4f8', fontHead:"'Georgia','Times New Roman',serif", fontBody:"system-ui,-apple-system,'Segoe UI',sans-serif", badgeBg:'rgba(30,95,240,0.08)', badgeBorder:'rgba(30,95,240,0.25)', }, graphite: { name:'Graphite', bg:'#0d1117', surface:'#161b22', card:'#1c2128', border:'#30363d', accent:'#00b4d8', accentDk:'#0090b0', accentLt:'#48cae4', text:'#e6edf3', muted:'#7d8590', heading:'#f0f6fc', navBg:'rgba(13,17,23,0.95)', heroGrad:'linear-gradient(135deg,#0d1117 0%,#161b22 50%,#0a1a20 100%)', timelineLine:'#30363d', sectionAlt:'#161b22', fontHead:"'JetBrains Mono','Fira Code','Courier New',monospace", fontBody:"system-ui,-apple-system,'Segoe UI',sans-serif", badgeBg:'rgba(0,180,216,0.1)', badgeBorder:'rgba(0,180,216,0.3)', }, crimson: { name:'Crimson', bg:'#080808', surface:'#111111', card:'#181818', border:'#2a2a2a', accent:'#e85d04', accentDk:'#c04d00', accentLt:'#ff7a2e', text:'#f5f5f5', muted:'#888888', heading:'#ffffff', navBg:'rgba(8,8,8,0.95)', heroGrad:'linear-gradient(135deg,#0a0000 0%,#200500 50%,#0a0000 100%)', timelineLine:'#2a2a2a', sectionAlt:'#111111', fontHead:"'Arial Black','Arial',sans-serif", fontBody:"'Arial','Helvetica Neue',sans-serif", badgeBg:'rgba(232,93,4,0.12)', badgeBorder:'rgba(232,93,4,0.35)', } }; /* ═══════════════════════════════════════════════ PORTFOLIO HTML GENERATOR ═══════════════════════════════════════════════ */ function generatePortfolioHTML() { const theme = PF_THEMES[PF_STATE.theme] || PF_THEMES.midnight; const accent = PF_STATE.accent === 'theme' ? theme.accent : PF_STATE.accent; const accentDk = PF_STATE.accent === 'theme' ? theme.accentDk : shadeColor(PF_STATE.accent, -30); const accentLt = PF_STATE.accent === 'theme' ? theme.accentLt : shadeColor(PF_STATE.accent, 30); // Pull data from STATE (the main app state) const name = [val('inp-firstname'), val('inp-lastname')].filter(Boolean).join(' ') || 'Your Name'; const title = val('inp-title') || 'Professional'; const email = val('inp-email'); const phone = val('inp-phone'); const city = val('inp-city'); const summary = val('inp-summary'); const linkedin = PF_STATE.linkedinUrl || val('inp-linkedin'); const github = PF_STATE.github; const tagline = PF_STATE.tagline || summary.substring(0, 120) + (summary.length > 120 ? '...' : ''); const ctaLink = PF_STATE.ctaLink || (email ? 'mailto:'+email : '#contact'); const years = PF_STATE.years || (STATE.experience.length > 0 ? STATE.experience.length + '+' : ''); const prof = getProfKey(); const country = COUNTRIES[STATE.country]; const secs = PF_STATE.sections; /* ─── CSS ─── */ const css = ` *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} :root{--accent:${accent};--accent-dk:${accentDk};--accent-lt:${accentLt};--bg:${theme.bg};--surface:${theme.surface};--card:${theme.card};--border:${theme.border};--text:${theme.text};--muted:${theme.muted};--heading:${theme.heading}} html{font-size:16px;scroll-behavior:smooth} body{background:var(--bg);color:var(--text);font-family:${theme.fontBody};line-height:1.6;overflow-x:hidden} ::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px} a{color:var(--accent);text-decoration:none;transition:color .2s}a:hover{color:var(--accent-lt)} h1,h2,h3,h4{font-family:${theme.fontHead};color:var(--heading);line-height:1.25} .container{max-width:1100px;margin:0 auto;padding:0 24px} /* NAV */ #pf-nav{position:fixed;top:0;left:0;right:0;z-index:1000;background:${theme.navBg};backdrop-filter:blur(12px);border-bottom:1px solid var(--border);transition:all .3s;padding:0} .nav-inner{display:flex;align-items:center;justify-content:space-between;height:60px;max-width:1100px;margin:0 auto;padding:0 24px} .nav-brand{font-family:${theme.fontHead};font-size:18px;font-weight:700;color:var(--accent)} .nav-links{display:flex;gap:28px;list-style:none} .nav-links a{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--muted);transition:color .2s;padding:4px 0;border-bottom:2px solid transparent} .nav-links a:hover{color:var(--accent);border-color:var(--accent)} .nav-cta{padding:8px 20px;border-radius:6px;background:var(--accent);color:${theme.bg};font-size:12px;font-weight:700;transition:all .2s} .nav-cta:hover{background:var(--accent-lt);color:${theme.bg}} .hamburger{display:none;flex-direction:column;gap:5px;cursor:pointer;padding:4px} .hamburger span{display:block;width:22px;height:2px;background:var(--text);border-radius:2px;transition:all .3s} @media(max-width:768px){.nav-links{display:none;position:absolute;top:60px;left:0;right:0;background:${theme.surface};flex-direction:column;padding:16px 24px;gap:14px;border-bottom:1px solid var(--border)}.nav-links.open{display:flex}.hamburger{display:flex}.nav-cta{display:none}} /* HERO */ #hero{min-height:100vh;background:${theme.heroGrad};display:flex;align-items:center;position:relative;overflow:hidden;padding-top:60px} .hero-particles{position:absolute;inset:0;overflow:hidden;pointer-events:none} .hero-particle{position:absolute;border-radius:50%;background:var(--accent);opacity:.05;animation:float linear infinite} @keyframes float{0%{transform:translateY(100vh) scale(0)}100%{transform:translateY(-10vh) scale(1)}} .hero-glow{position:absolute;width:600px;height:600px;border-radius:50%;background:radial-gradient(circle,${accent}22 0%,transparent 70%);top:-100px;right:-100px;animation:pulse 4s ease-in-out infinite} @keyframes pulse{0%,100%{transform:scale(1);opacity:.5}50%{transform:scale(1.1);opacity:.8}} .hero-grid{position:absolute;inset:0;background-image:linear-gradient(var(--border) 1px,transparent 1px),linear-gradient(90deg,var(--border) 1px,transparent 1px);background-size:50px 50px;opacity:.12} .hero-content{position:relative;z-index:1;max-width:700px} .hero-eyebrow{display:inline-flex;align-items:center;gap:7px;padding:5px 14px;border:1px solid ${accent}55;border-radius:20px;background:${accent}12;font-size:11px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--accent);margin-bottom:18px} .hero-name{font-size:clamp(2.5rem,5vw,4rem);font-weight:700;color:var(--heading);margin-bottom:10px;line-height:1.1} .hero-name .accent-word{color:var(--accent);display:block} .hero-job{font-size:clamp(1rem,2.5vw,1.3rem);color:var(--muted);margin-bottom:14px;font-weight:400} .hero-tagline{font-size:1.05rem;color:var(--text);opacity:.85;max-width:580px;line-height:1.7;margin-bottom:28px} .hero-btns{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:24px} .hero-btn{padding:13px 28px;border-radius:7px;font-size:13px;font-weight:700;letter-spacing:.5px;transition:all .25s;border:2px solid transparent} .hero-btn.primary{background:var(--accent);color:${theme.bg};border-color:var(--accent)} .hero-btn.primary:hover{background:var(--accent-lt);transform:translateY(-2px);box-shadow:0 8px 24px ${accent}44} .hero-btn.secondary{background:transparent;color:var(--text);border-color:var(--border)} .hero-btn.secondary:hover{border-color:var(--accent);color:var(--accent);transform:translateY(-2px)} .hero-meta{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;color:var(--muted)} .hero-meta span{display:flex;align-items:center;gap:5px} .hero-scroll{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:6px;font-size:10px;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);animation:bounce 2s infinite} .scroll-line{width:1.5px;height:40px;background:linear-gradient(to bottom,transparent,var(--accent));margin:0 auto} @keyframes bounce{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(6px)}} /* SECTIONS */ section{padding:90px 0} section:nth-child(even){background:${theme.sectionAlt}} .section-label{display:inline-flex;align-items:center;gap:7px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:2px;color:var(--accent);margin-bottom:10px} .section-label::before{content:'';width:20px;height:1.5px;background:var(--accent);display:block} .section-title{font-size:clamp(1.6rem,3.5vw,2.4rem);font-weight:700;color:var(--heading);margin-bottom:10px} .section-sub{font-size:1rem;color:var(--muted);max-width:500px;line-height:1.65;margin-bottom:40px} /* ABOUT */ .about-grid{display:grid;grid-template-columns:1fr 1fr;gap:48px;align-items:center} @media(max-width:768px){.about-grid{grid-template-columns:1fr}} .about-text{font-size:1rem;color:var(--text);line-height:1.8;margin-bottom:18px} .about-badges{display:flex;flex-wrap:wrap;gap:7px;margin-top:14px} .about-badge{padding:4px 12px;background:${theme.badgeBg};border:1px solid ${theme.badgeBorder};border-radius:20px;font-size:11px;color:var(--accent);font-weight:600} .about-cards{display:flex;flex-direction:column;gap:14px} .about-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px 22px;border-left:3px solid var(--accent);transition:transform .2s,box-shadow .2s} .about-card:hover{transform:translateX(6px);box-shadow:0 4px 24px ${accent}22} .about-card-num{font-size:2rem;font-weight:700;color:var(--accent);font-family:${theme.fontHead}} .about-card-lbl{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.8px;margin-top:2px} .about-card-desc{font-size:12px;color:var(--text);margin-top:4px;opacity:.7} /* EXPERIENCE */ .timeline{position:relative;padding-left:30px} .timeline::before{content:'';position:absolute;left:8px;top:8px;bottom:0;width:1.5px;background:linear-gradient(to bottom,var(--accent),transparent)} .tl-item{position:relative;margin-bottom:36px} .tl-dot{position:absolute;left:-26px;top:5px;width:16px;height:16px;border-radius:50%;background:var(--accent);border:3px solid ${theme.bg};box-shadow:0 0 0 2px var(--accent)} .tl-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px 24px;transition:transform .2s,box-shadow .2s} .tl-card:hover{transform:translateX(6px);box-shadow:0 4px 24px ${accent}18} .tl-header{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:8px;margin-bottom:4px} .tl-title{font-size:1rem;font-weight:700;color:var(--heading)} .tl-date{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:var(--accent);padding:3px 9px;background:${theme.badgeBg};border:1px solid ${theme.badgeBorder};border-radius:10px} .tl-company{font-size:13px;color:var(--muted);margin-bottom:10px;display:flex;align-items:center;gap:6px} .tl-company::before{content:'';width:14px;height:1px;background:var(--accent)} .tl-bullets{padding-left:14px;font-size:13px;color:var(--text);line-height:1.65} .tl-bullets li{margin-bottom:4px;list-style:none;padding-left:12px;position:relative} .tl-bullets li::before{content:'▸';position:absolute;left:0;color:var(--accent);font-size:10px} /* SKILLS */ .skills-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px} .skill-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 18px;transition:transform .2s,box-shadow .2s} .skill-card:hover{transform:translateY(-3px);box-shadow:0 6px 24px ${accent}18;border-color:${accent}55} .skill-name{font-size:13px;font-weight:700;color:var(--heading);margin-bottom:8px;display:flex;justify-content:space-between;align-items:center} .skill-pct{font-size:10px;color:var(--accent);font-weight:700} .skill-bar-track{height:3px;background:var(--border);border-radius:2px;overflow:hidden} .skill-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--accent-dk),var(--accent-lt));transition:width 1.2s cubic-bezier(.4,0,.2,1);width:0%} .skills-cloud{display:flex;flex-wrap:wrap;gap:9px} .skill-tag{padding:7px 16px;border:1.5px solid var(--border);border-radius:24px;font-size:12px;color:var(--text);transition:all .2s;cursor:default;background:var(--card)} .skill-tag:hover{border-color:var(--accent);color:var(--accent);background:SKILLTAG_BG_PLACEHOLDER;transform:translateY(-2px)} /* PROJECTS */ .projects-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px} .proj-card{background:var(--card);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:transform .25s,box-shadow .25s;display:flex;flex-direction:column} .proj-card:hover{transform:translateY(-6px);box-shadow:0 16px 48px ${accent}20;border-color:${accent}55} .proj-card-top{height:4px;background:linear-gradient(90deg,var(--accent-dk),var(--accent-lt))} .proj-card-body{padding:22px;flex:1} .proj-card-year{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--accent);margin-bottom:8px} .proj-card-name{font-size:1rem;font-weight:700;color:var(--heading);margin-bottom:10px} .proj-card-desc{font-size:13px;color:var(--text);line-height:1.65;margin-bottom:14px;opacity:.85} .proj-card-tech{display:flex;flex-wrap:wrap;gap:5px} .proj-tag{padding:3px 9px;background:${theme.badgeBg};border:1px solid ${theme.badgeBorder};border-radius:12px;font-size:10px;color:var(--accent);font-weight:600} /* CERTIFICATIONS */ .certs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px} .cert-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px;display:flex;gap:14px;align-items:flex-start;transition:transform .2s,box-shadow .2s} .cert-card:hover{transform:translateY(-3px);box-shadow:0 8px 28px ${accent}18;border-color:${accent}44} .cert-icon{width:42px;height:42px;border-radius:8px;background:linear-gradient(135deg,var(--accent-dk),var(--accent-lt));display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0} .cert-content .cert-name{font-size:13px;font-weight:700;color:var(--heading);margin-bottom:3px} .cert-content .cert-issuer{font-size:11px;color:var(--muted)} .cert-content .cert-year{font-size:10px;color:var(--accent);font-weight:700;margin-top:4px} /* CONTACT */ #contact{background:${theme.heroGrad};position:relative;overflow:hidden} .contact-glow{position:absolute;inset:0;background:radial-gradient(ellipse at 50% 50%,${accent}14,transparent 70%)} .contact-inner{position:relative;z-index:1;text-align:center;max-width:600px;margin:0 auto} .contact-title{font-size:clamp(1.8rem,4vw,2.8rem);font-weight:700;color:var(--heading);margin-bottom:12px} .contact-sub{font-size:1rem;color:var(--muted);margin-bottom:32px;line-height:1.7} .contact-links{display:flex;justify-content:center;gap:14px;flex-wrap:wrap;margin-bottom:28px} .contact-link{display:flex;align-items:center;gap:8px;padding:11px 22px;border-radius:8px;border:1.5px solid var(--border);background:var(--card);font-size:12px;font-weight:700;color:var(--text);transition:all .2s} .contact-link:hover{border-color:var(--accent);color:var(--accent);transform:translateY(-2px);box-shadow:0 6px 20px ${accent}22} .contact-link .cl-icon{font-size:16px} .contact-cta-btn{display:inline-flex;align-items:center;gap:9px;padding:16px 36px;border-radius:9px;background:linear-gradient(135deg,var(--accent),var(--accent-lt));color:${theme.bg};font-size:14px;font-weight:700;letter-spacing:.5px;transition:all .25s;border:none;cursor:pointer} .contact-cta-btn:hover{transform:translateY(-3px);box-shadow:0 10px 32px ${accent}44} /* FOOTER */ footer{background:${theme.bg};border-top:1px solid var(--border);padding:28px 24px;text-align:center;font-size:11px;color:var(--muted)} footer strong{color:var(--accent)} /* ANIMATIONS */ .reveal{opacity:0;transform:translateY(28px);transition:opacity .6s ease,transform .6s ease} .reveal.visible{opacity:1;transform:none} .reveal-left{opacity:0;transform:translateX(-28px);transition:opacity .6s ease,transform .6s ease} .reveal-left.visible{opacity:1;transform:none} .reveal-right{opacity:0;transform:translateX(28px);transition:opacity .6s ease,transform .6s ease} .reveal-right.visible{opacity:1;transform:none} `; // Replace placeholder with actual theme value const cssFixed = css.replace('SKILLTAG_BG_PLACEHOLDER', theme.badgeBg); /* ─── SECTIONS HTML ─── */ const heroHTML = secs.hero ? `
${country ? country.flag + ' ' : ''}${city || 'Available Worldwide'}

${name.split(' ')[0]} ${name.split(' ').slice(1).join(' ')}

${escHtml(title)}

${escHtml(tagline)}

Hire Me → ${secs.projects ? 'View Work' : ''} ${secs.about ? 'About Me' : ''}
${email ? `📧 ${escHtml(email)}` : ''} ${phone ? `📞 ${escHtml(phone)}` : ''} ${city ? `📍 ${escHtml(city)}` : ''}
scroll
` : ''; const expYrs = years || (STATE.experience.length > 1 ? (STATE.experience.length * 2) + '+' : '3+'); const aboutHTML = secs.about && summary ? `

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 ? `
${STATE.projects.length}
Notable Projects
Delivered with measurable impact
` : ''}
` : ''; const expHTML = secs.experience && STATE.experience.length ? `

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 `
    ${escHtml(e.title||'')}
    ${escHtml(e.start||'')}${e.end?' – '+e.end:''}
    ${escHtml(e.company||'')}${e.location?' · '+e.location:''}
    ${buls?`
      ${buls}
    `:''}
    `; }).join('')}
    ` : ''; const skillsHTML = secs.skills && STATE.skills.length ? `

    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 `
    ${escHtml(s)}${pct}%
    `; }).join('')}
    ` : ''; const projHTML = secs.projects && STATE.projects.length ? `

    Selected Projects

    Showcasing work that demonstrates impact and innovation.

    ${STATE.projects.map(p => `
    ${p.year?`
    ${escHtml(p.year)}
    `:''}
    ${escHtml(p.name||'')}
    ${escHtml(p.desc||'')}
    ${p.tech?`
    ${p.tech.split(',').map(t=>`${escHtml(t.trim())}`).join('')}
    `:''}
    `).join('')}
    ` : ''; const certHTML = secs.certifications && STATE.certifications.length ? `

    Certifications & Qualifications

    Professionally recognised credentials that validate expertise.

    ${STATE.certifications.map(c => `
    🏅
    ${escHtml(c.name||'')}
    ${escHtml(c.issuer||'')}
    ${c.year?`
    ${escHtml(c.year)} · ${escHtml(c.expiry||'Active')}
    `:''}
    `).join('')}
    ` : ''; const testimonialsHTML = secs.testimonials ? `

    What People Say

    "An exceptional professional who consistently delivers above expectations. A genuine asset to any organisation."

    Senior Manager
    Former Employer
    ` : ''; const contactLinks = [ email ? `📧${escHtml(email)}` : '', phone ? `📞${escHtml(phone)}` : '', linkedin ? `💼LinkedIn` : '', github ? `💻GitHub` : '' ].filter(Boolean).join(''); const contactHTML = secs.contact ? `

    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.

    📩 Hire Me Now →
    ` : ''; const eduStr = STATE.education.map(e=>`${e.degree||''}, ${e.school||''}${e.year?' ('+e.year+')':''}`).filter(e=>e.trim().length>2).join(' | '); /* ─── JS ─── */ const js = ` // Particles const pc = document.getElementById('hero-particles'); if(pc){for(let i=0;i<15;i++){const p=document.createElement('div');p.className='hero-particle';const s=Math.random()*80+20;p.style.cssText='width:'+s+'px;height:'+s+'px;left:'+Math.random()*100+'%;animation-duration:'+(Math.random()*20+15)+'s;animation-delay:'+(Math.random()*10)+'s';pc.appendChild(p);}} // Scroll reveal const obs=new IntersectionObserver(entries=>entries.forEach(e=>{if(e.isIntersecting){e.target.classList.add('visible');obs.unobserve(e.target);}}),{threshold:.1}); document.querySelectorAll('.reveal,.reveal-left,.reveal-right').forEach(el=>obs.observe(el)); // Skill bars animate on reveal const sbo=new IntersectionObserver(entries=>entries.forEach(e=>{if(e.isIntersecting){e.target.querySelectorAll('.skill-bar-fill').forEach(b=>b.style.width=b.style.width);};}),{threshold:.2}); document.querySelectorAll('.skills-grid').forEach(el=>sbo.observe(el)); // Nav scroll window.addEventListener('scroll',()=>{const n=document.getElementById('pf-nav');if(n){n.style.boxShadow=window.scrollY>50?'0 2px 24px rgba(0,0,0,.4)':'none';}}); // Hamburger const hb=document.querySelector('.hamburger');const nl=document.querySelector('.nav-links'); if(hb&&nl){hb.addEventListener('click',()=>{nl.classList.toggle('open');});} // Smooth scroll document.querySelectorAll('a[href^="#"]').forEach(a=>a.addEventListener('click',e=>{const t=document.querySelector(a.getAttribute('href'));if(t){e.preventDefault();t.scrollIntoView({behavior:'smooth',block:'start'});}})); `; /* ─── FULL HTML ─── */ return ` ${escHtml(name)} · Portfolio ${heroHTML} ${aboutHTML} ${expHTML} ${skillsHTML} ${projHTML} ${certHTML} ${testimonialsHTML} ${contactHTML}

    ${escHtml(name)} · ${escHtml(title)}

    ${eduStr ? `

    ${escHtml(eduStr)}

    ` : ''}

    Generated by GEMI Controlled Application Engine™ v10 · gemitravelandtour.com · info@gemitravelandtour.com