diff --git a/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py b/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py deleted file mode 100644 index 145a7b8b..00000000 --- a/utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py +++ /dev/null @@ -1,185 +0,0 @@ -import re -import nltk -from nltk.corpus import stopwords -from nltk.tokenize import word_tokenize -from nltk.util import ngrams - - -def _ensure_nltk_resources() -> None: - """Download NLTK resources only if not already present.""" - resources = { - 'punkt': 'tokenizers/punkt', - 'punkt_tab': 'tokenizers/punkt_tab', - 'stopwords': 'corpora/stopwords', - } - for name, path in resources.items(): - try: - nltk.data.find(path) - except LookupError: - nltk.download(name, quiet=True) - - -def analyze_resume(resume_text: str) -> dict: - _ensure_nltk_resources() - - resume = resume_text.lower() - - # Pre-process to protect C++, C#, and .NET from NLTK tokenization - PROTECTED = {"c++": "cpplang", "c#": "csharplang", ".net": "dotnetlang"} - for raw, placeholder in PROTECTED.items(): - resume = resume.replace(raw, placeholder) - - # NLP processing - words = word_tokenize(resume) - - # Restore protected tokens - words = [PROTECTED.get(w, w) for w in words] - - stop_words = set(stopwords.words('english')) - KEEP_AS_IS = {"c++", "c#", ".net"} - - clean_words = [ - w for w in words - if (w.isalnum() or w in KEEP_AS_IS) and w not in stop_words - ] - - # Phrase tokens - bigrams = [' '.join(bg) for bg in ngrams(clean_words, 2)] - - # Skill aliases - skill_aliases = { - # Languages - "py": "python", - "js": "javascript", - "ts": "typescript", - "cpp": "c++", - # Databases - "postgres": "postgresql", - "mongo": "mongodb", - # AI / ML - "ml": "machine learning", - "dl": "deep learning", - "cv": "computer vision", - # Frameworks - "reactjs": "react", - "react.js": "react", - "nodejs": "node.js", - "expressjs": "express", - # Cloud / DevOps - "aws": "amazon web services", - "gcp": "google cloud platform", - # Tools - "gitlab": "git", - "github": "git", - } - - normalized_words = [ - skill_aliases.get(word, word) - for word in clean_words - ] - - normalized_bigrams = [ - skill_aliases.get(bg, bg) - for bg in bigrams - ] - - # Skills - skills = [ - "python", "java", "c++", "c#", ".net", "django", "sql", - "machine learning", "html", "css", - "javascript", "communication", "teamwork" - ] - - found_skills = [] - for skill in skills: - if skill in normalized_words or skill in normalized_bigrams: - found_skills.append(skill) - - # Resume section parsing - education_pattern = r"(education|academic|qualification)" - experience_pattern = r"(experience|internship|work experience)" - project_pattern = r"(projects|project)" - - edu_found = bool(re.search(education_pattern, resume)) - exp_found = bool(re.search(experience_pattern, resume)) - project_found = bool(re.search(project_pattern, resume)) - - # Improved score system - score = 0 - - # Skill score - score += min(len(found_skills) * 8, 40) - - # Section score - if edu_found: - score += 20 - if exp_found: - score += 25 - if project_found: - score += 15 - - # Resume quality bonus - if len(found_skills) >= 5: - score += 10 - - if score > 100: - score = 100 - - missing_skills = [s for s in skills if s not in found_skills] - - return { - "found_skills": found_skills, - "score": score, - "edu_found": edu_found, - "exp_found": exp_found, - "project_found": project_found, - "missing_skills": missing_skills, - "all_skills": skills, - } - - -def main() -> None: - print("=== πŸ€– AI Resume Analyzer (Advanced NLP Version) ===") - - resume = input("\nPaste your resume text:\n") - if not resume.strip(): - print("❌ Error: Resume cannot be empty.") - return - - result = analyze_resume(resume) - - # OUTPUT - print("\nπŸ“Š === Analysis Result ===") - - print("\nβœ… Skills detected:") - for s in result["found_skills"]: - print("-", s) - - score = result["score"] - print(f"\n🎯 ATS Score: {score}/100") - - # Strength - if score >= 80: - print("βœ… Resume Strength: Excellent") - elif score >= 50: - print("πŸ‘ Resume Strength: Good") - else: - print("⚠️ Needs Improvement") - - # Suggestions - print("\nπŸ’‘ Recommendations:") - for m in result["missing_skills"][:5]: - print("-", m) - - # Section check - print("\nπŸ“„ Resume Sections:") - print("- Education:", "βœ… Found" if result["edu_found"] else "❌ Missing") - print("- Experience:", "βœ… Found" if result["exp_found"] else "❌ Missing") - print("- Projects:", "βœ… Found" if result["project_found"] else "❌ Missing") - - # Final - print("\nπŸš€ Analysis Completed") - - -if __name__ == "__main__": - main() diff --git a/utilities/AI-Resume-Analyzer/README.MD b/utilities/AI-Resume-Analyzer/README.MD deleted file mode 100644 index cca36ba7..00000000 --- a/utilities/AI-Resume-Analyzer/README.MD +++ /dev/null @@ -1,50 +0,0 @@ -# πŸ€– AI Resume Analyzer (NLP Powered) - -An intelligent resume analysis tool built using Python and NLTK that extracts skills, evaluates resumes, and provides ATS scoring with improvement suggestions. - ---- - -## πŸš€ Features - -- 🧠 NLP-based text processing using NLTK -- 🎯 Skill detection from resume text -- πŸ“Š ATS (Applicant Tracking System) score generation -- πŸ“„ Resume strength evaluation -- πŸ’‘ Smart improvement suggestions -- πŸ“Œ Education & experience detection - ---- - -## πŸ› οΈ Tech Stack - -- Python 🐍 -- NLTK (Natural Language Toolkit) - ---- - -## πŸ“‚ Project Structure -AI-Resume-Analyzer/ -β”œβ”€β”€ main.py -β”œβ”€β”€ requirements.txt -β”œβ”€β”€ README.md - - ---- - -## βš™οΈ Installation & Setup - -### 1️⃣ Install dependencies - -```bash -pip install -r requirements.txt - -run project- python main.py - ----NLTK Setup (FIRST TIME ONLY)--- - -Run this once in Python: - -import nltk -nltk.download('punkt_tab') -nltk.download('punkt') -nltk.download('stopwords') \ No newline at end of file diff --git a/utilities/AI-Resume-Analyzer/requirements.txt b/utilities/AI-Resume-Analyzer/requirements.txt deleted file mode 100644 index 6fa2de44..00000000 --- a/utilities/AI-Resume-Analyzer/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -nltk \ No newline at end of file diff --git a/web-app/assets/banners/reverse-hangman.webp b/web-app/assets/banners/reverse-hangman.webp deleted file mode 100644 index 841df72d..00000000 Binary files a/web-app/assets/banners/reverse-hangman.webp and /dev/null differ diff --git a/web-app/css/styles.css b/web-app/css/styles.css index f376d128..a3130834 100644 --- a/web-app/css/styles.css +++ b/web-app/css/styles.css @@ -1052,28 +1052,8 @@ main>.hero-section:has(.hero-code-snippets) { animation: pulseKicker 2s ease-in-out infinite; } -@keyframes pulseKicker { - - 0%, - 100% { - opacity: 1; - transform: scale(1.1); - } - - 50% { - opacity: 0.4; - transform: scale(0.85); - } -} - -[data-theme="dark"] .hero-kicker { - background: linear-gradient(135deg, - rgba(106, 191, 141, 0.18), - rgba(242, 194, 108, 0.1)); - border-color: rgba(255, 243, 224, 0.1); - color: #d4b48a; -} - +/* ── Hero Badge Row (Glassified Kicker & Status Button) ──────── */ +.hero-kicker, .hero-status { display: inline-flex; align-items: center; diff --git a/web-app/generate_banners.py b/web-app/generate_banners.py index bc6b0aad..b86721fa 100644 --- a/web-app/generate_banners.py +++ b/web-app/generate_banners.py @@ -468,18 +468,6 @@ def draw_o(ox, oy): x = 220 + i * 80 y = 175 v_draw.ellipse([x, y, x + 65, y + 65], fill=col, outline=(255,255,255,100), width=2) - elif "resume" in n_lower or "analyzer" in n_lower: - # Resume analyzer dashboard - v_draw.rounded_rectangle([250, 100, 550, 350], radius=24, fill=(255,255,255,12), outline=color_accent, width=3) - v_draw.rounded_rectangle([285, 135, 515, 315], radius=18, fill=(255,255,255,8), outline=(255,255,255,50), width=2) - v_draw.polygon([(332, 135), (515, 135), (515, 190)], fill=(255,255,255,20), outline=color_accent) - v_draw.ellipse([280, 145, 390, 255], outline=color_accent, width=8) - v_draw.text((335, 200), "82%", fill=color_accent, anchor="mm") - for i, w in enumerate([90, 75, 65]): - y = 275 + i * 18 - v_draw.rounded_rectangle([410, y, 410 + w, y + 10], radius=4, fill=color_accent) - v_draw.text((430, 165), "AI RESUME", fill=color_accent, anchor="lm") - v_draw.text((430, 188), "ANALYZER", fill=(255,255,255), anchor="lm") elif "caesar" in n_lower: # Cipher wheel and shifting letters cx, cy = 400, 225 @@ -720,7 +708,6 @@ def draw_o(ox, oy): ("Number Converter", "utilities", "number-converter.webp"), ("Typing Speed Tester", "utilities", "typing-speed-tester.webp"), ("Color Palette Suggestor", "utilities", "color-palette.webp"), - ("AI Resume Analyzer", "utilities", "resume-analyzer.webp"), ("Caesar Cipher", "utilities", "caesar-cipher.webp"), ("Unit Converter", "utilities", "unit-converter.webp"), ("Budget Tracker", "utilities", "budget-tracker.webp"), diff --git a/web-app/index.html b/web-app/index.html index be026e0d..ea257c14 100644 --- a/web-app/index.html +++ b/web-app/index.html @@ -508,7 +508,6 @@

Stay Updated

- @@ -865,13 +864,6 @@

Stay Updated

desc: "Generate color palettes", tags: "utility,design", }, - { - project: "resume-analyzer", - title: "Resume Analyzer", - category: "utilities", - desc: "Analyze and improve resumes", - tags: "utility,career", - }, { project: "calculator", title: "Scientific Graphing Calculator", diff --git a/web-app/js/main.js b/web-app/js/main.js index 80e8ba7d..15e0f7b4 100644 --- a/web-app/js/main.js +++ b/web-app/js/main.js @@ -1354,8 +1354,7 @@ document.addEventListener("DOMContentLoaded", function () { // Info button already exists, skip injection console.log('ℹ️ Info button already exists for', name); } else { - // Look for any heading element - var firstHeading = projectContent.querySelector("h2, h3, .resume-analyzer-copy h2, .pet-title"); + var firstHeading = projectContent.querySelector("h2, h3, .pet-title"); // Special case for Tic Tac Toe - look for the heading inside project-content if (!firstHeading) { diff --git a/web-app/js/modules/modal.js b/web-app/js/modules/modal.js index 54e2e0c9..323583b1 100644 --- a/web-app/js/modules/modal.js +++ b/web-app/js/modules/modal.js @@ -143,7 +143,7 @@ export function openProjectSafe(name, trigger) { const projectContent = modalBody.querySelector(".project-content"); if (projectContent) { let firstHeading = projectContent.querySelector( - "h2, h3, .resume-analyzer-copy h2, .pet-title" + "h2, h3, .pet-title" ); if (!firstHeading) { firstHeading = projectContent.querySelector( diff --git a/web-app/js/projects.js b/web-app/js/projects.js index fe6ece08..0bf144bf 100644 --- a/web-app/js/projects.js +++ b/web-app/js/projects.js @@ -25,7 +25,6 @@ function getProjectHTML(projectName) { 'matrix-calculator': () => getMatrixCalculatorHTML(), 'sudoku-game': getSudokuGameHTML(), 'unit-converter': getUnitConverterHTML(), - 'resume-analyzer': getResumeAnalyzerHTML(), 'reverse-hangman': () => getReverseHangmanHTML(), 'budget-tracker': getBudgetTrackerHTML(), 'snake-game': getSnakeGameHTML(), @@ -465,15 +464,6 @@ const projectInstructions = { "Copy the secure password" ] }, - "resume-analyzer": { - title: "πŸ“„ How to Use AI Resume Analyzer", - steps: [ - "Upload your resume (PDF, DOC, or TXT)", - "Click 'Analyze Resume'", - "View your ATS score and keyword matches", - "Check suggestions to improve your resume" - ] - }, "typing-speed-tester": { title: "⌨️ How to Use Typing Speed Tester", steps: [ diff --git a/web-app/js/projects/resume-analyzer.js b/web-app/js/projects/resume-analyzer.js deleted file mode 100644 index a70632eb..00000000 --- a/web-app/js/projects/resume-analyzer.js +++ /dev/null @@ -1,355 +0,0 @@ -// ============================================ -// AI RESUME ANALYZER -// Logic ported from utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py -// No API calls β€” pure client-side text analysis. -// ============================================ - -function getResumeAnalyzerHTML() { - return ` -
-
-
- GSSoC Utility -

AI Resume Analyzer

-

Upload a resume and get a quick ATS-style snapshot with keyword, structure, and formatting feedback.

-
-
No backend required
-
- -
-
-
- - -

Drag & drop or click to choose a resume (.pdf, .doc, .docx, .txt)

-
- - -
- - - - -
-
- `; -} - -function initResumeAnalyzer() { - const analyzeBtn = document.getElementById('analyzeBtn'); - const resumeInput = document.getElementById('resumeInput'); - const fileStatus = document.getElementById('resumeFileStatus'); - const uploadIcon = document.getElementById('resumeUploadIcon'); - const ats = document.getElementById('ats'); - const bottomSection = document.getElementById('bottomSection'); - - if (!analyzeBtn || !resumeInput) return; - - function loadPdfJs() { - return new Promise((resolve, reject) => { - if (window.pdfjsLib) { resolve(window.pdfjsLib); return; } - const script = document.createElement('script'); - script.src = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js'; - script.onload = () => { - window.pdfjsLib.GlobalWorkerOptions.workerSrc = - 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; - resolve(window.pdfjsLib); - }; - script.onerror = () => reject(new Error('Failed to load PDF.js')); - document.head.appendChild(script); - }); - } - - async function readPdfAsText(file) { - const pdfjsLib = await loadPdfJs(); - const arrayBuffer = await file.arrayBuffer(); - const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; - const pageTexts = []; - for (let i = 1; i <= pdf.numPages; i++) { - const page = await pdf.getPage(i); - const content = await page.getTextContent(); - pageTexts.push(content.items.map(item => item.str).join(' ')); - } - return pageTexts.join('\n'); - } - - const SKILLS = [ - "python", "java", "c++", "c#", ".net", "django", "sql", - "machine learning", "html", "css", "javascript", - "communication", "teamwork" - ]; - - const ALIASES = { - "py": "python", - "js": "javascript", - "ts": "typescript", - "cpp": "c++", - "postgres": "postgresql", - "mongo": "mongodb", - "ml": "machine learning", - "dl": "deep learning", - "cv": "computer vision", - "reactjs": "react", - "react.js": "react", - "nodejs": "node.js", - "expressjs": "express", - "aws": "amazon web services", - "gcp": "google cloud platform", - "gitlab": "git", - "github": "git", - }; - - const EDU_RE = /education|academic|qualification/i; - const EXP_RE = /experience|internship|work experience/i; - const PROJ_RE = /projects?/i; - - resumeInput.addEventListener('change', () => { - const file = resumeInput.files[0]; - if (!file) return; - - fileStatus.textContent = `βœ… File selected: ${file.name}`; - fileStatus.style.color = 'var(--accent)'; - uploadIcon.style.color = 'var(--accent)'; - uploadIcon.className = 'fa-solid fa-circle-check resume-upload-icon'; - }); - - const dropZone = document.getElementById('resumeDropZone'); - if (dropZone) { - dropZone.addEventListener('dragover', e => { - e.preventDefault(); - dropZone.style.borderColor = 'var(--accent)'; - }); - dropZone.addEventListener('dragleave', () => { - dropZone.style.borderColor = ''; - }); - dropZone.addEventListener('drop', e => { - e.preventDefault(); - dropZone.style.borderColor = ''; - const file = e.dataTransfer.files[0]; - if (file) { - const dt = new DataTransfer(); - dt.items.add(file); - resumeInput.files = dt.files; - fileStatus.textContent = `βœ… File selected: ${file.name}`; - fileStatus.style.color = 'var(--accent)'; - uploadIcon.className = 'fa-solid fa-circle-check resume-upload-icon'; - } - }); - } - - analyzeBtn.addEventListener('click', async () => { - if (!resumeInput.files.length) { - fileStatus.textContent = '⚠️ Please upload a resume file first!'; - fileStatus.style.color = 'var(--danger-color, #ef4444)'; - return; - } - - const file = resumeInput.files[0]; - const ext = file.name.split('.').pop().toLowerCase(); - - analyzeBtn.textContent = 'Analyzing… ⏳'; - analyzeBtn.disabled = true; - - try { - let text = ''; - - if (ext === 'pdf') { - fileStatus.textContent = 'πŸ“„ Extracting text from PDF…'; - fileStatus.style.color = 'var(--accent)'; - text = await readPdfAsText(file); - //console.log(`[ResumeAnalyzer] PDF extracted (${text.length} chars):`, text); - if (!text || text.trim().length < 30) { - console.warn('[ResumeAnalyzer] Text too short β€” likely a scanned/image PDF.'); - fileStatus.textContent = - `⚠️ "${file.name}" appears to be a scanned/image-only PDF. ` + - `For best results, export your resume as a text-based PDF or .txt file.`; - fileStatus.style.color = 'var(--warning-color, #f59e0b)'; - return; - } - } else if (ext === 'doc' || ext === 'docx') { - text = await readFileAsText(file); - //console.log(`[ResumeAnalyzer] DOCX raw text (${text.length} chars):`, text); - if (!text || text.trim().length < 30) { - console.warn('[ResumeAnalyzer] DOCX text too short β€” binary file.'); - fileStatus.textContent = - `⚠️ "${file.name}" could not be read as plain text in the browser. ` + - `For best results, export your resume as a .pdf or .txt file and re-upload.`; - fileStatus.style.color = 'var(--warning-color, #f59e0b)'; - return; - } - } else { - text = await readFileAsText(file); - //console.log(`[ResumeAnalyzer] TXT extracted (${text.length} chars):`, text); - } - - //console.log('[ResumeAnalyzer] Running analysis on text:', text); - runAnalysis(text); - } catch (err) { - fileStatus.textContent = `❌ Could not read file: ${err.message}`; - fileStatus.style.color = 'var(--danger-color, #ef4444)'; - } finally { - analyzeBtn.textContent = 'Analyze Resume πŸš€'; - analyzeBtn.disabled = false; - } - }); - - function readFileAsText(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = e => resolve(e.target.result); - reader.onerror = () => reject(new Error('Read error')); - reader.readAsText(file); - }); - } - - function runAnalysis(rawText) { - const text = rawText.toLowerCase(); - - const tokenRe = /c\+\+|c#|\.net|[a-z0-9]+/gi; - const tokens = (text.match(tokenRe) || []).map(t => t.toLowerCase()); - - const normalised = tokens.map(t => ALIASES[t] ?? t); - - const bigrams = []; - for (let i = 0; i < normalised.length - 1; i++) { - bigrams.push(normalised[i] + ' ' + normalised[i + 1]); - } - - const foundSkills = SKILLS.filter( - skill => normalised.includes(skill) || bigrams.includes(skill) - ); - - const eduFound = EDU_RE.test(text); - const expFound = EXP_RE.test(text); - const projFound = PROJ_RE.test(text); - - let score = 0; - score += Math.min(foundSkills.length * 8, 40); // up to 40 - if (eduFound) score += 20; - if (expFound) score += 25; - if (projFound) score += 15; - if (foundSkills.length >= 5) score += 10; // quality bonus - score = Math.min(score, 100); - - const formattingScore = Math.round( - ((eduFound ? 1 : 0) + (expFound ? 1 : 0) + (projFound ? 1 : 0)) / 3 * 100 - ); - const keywordScore = Math.min( - Math.round((foundSkills.length / SKILLS.length) * 100), 100 - ); - - let strength; - if (score >= 80) strength = 'βœ… Resume Strength: Excellent'; - else if (score >= 50) strength = 'πŸ‘ Resume Strength: Good'; - else strength = '⚠️ Resume Strength: Needs Improvement'; - - const missingSkills = SKILLS.filter(s => !foundSkills.includes(s)); - - renderResults({ - score, formattingScore, keywordScore, strength, - foundSkills, missingSkills, - eduFound, expFound, projFound - }); - } - - function renderResults({ score, formattingScore, keywordScore, strength, - foundSkills, missingSkills, - eduFound, expFound, projFound }) { - ats.classList.remove('hidden'); - bottomSection.classList.remove('hidden'); - - document.getElementById('atsScoreDisplay').textContent = score + '%'; - document.getElementById('atsFormattingScore').textContent = `βœ” Formatting Score: ${formattingScore}%`; - document.getElementById('atsKeywordScore').textContent = `βœ” Keyword Match: ${keywordScore}%`; - document.getElementById('atsStrength').textContent = strength; - - const kwList = document.getElementById('resumeKeywordsList'); - kwList.textContent = ''; - - const displaySkills = [ - ...foundSkills.map(s => ({ name: s, pct: Math.min(70 + Math.random() * 30 | 0, 100), found: true })), - ...missingSkills.slice(0, Math.max(0, 5 - foundSkills.length)) - .map(s => ({ name: s, pct: 0, found: false })) - ].slice(0, 6); - - if (displaySkills.length === 0) { - kwList.innerHTML = '

No matching keywords detected. Try a .txt version of your resume.

'; - } else { - displaySkills.forEach(({ name, pct, found }) => { - kwList.innerHTML += ` -
- ${capitalise(name)}${found ? '' : ' ❌'} -
-
-
-
`; - }); - } - - const suggestionsEl = document.getElementById('resumeSuggestions'); - suggestionsEl.innerHTML = ''; - - const suggestions = buildSuggestions({ foundSkills, missingSkills, eduFound, expFound, projFound }); - suggestions.forEach(s => { - suggestionsEl.innerHTML += ` -
- -

${s.text}

-
`; - }); - - ats.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - - function buildSuggestions({ foundSkills, missingSkills, eduFound, expFound, projFound }) { - const list = []; - - if (eduFound) list.push({ ok: true, text: 'Education section detected' }); - else list.push({ ok: false, text: 'Add an Education section' }); - - if (expFound) list.push({ ok: true, text: 'Experience section detected' }); - else list.push({ ok: false, text: 'Add a Work Experience / Internship section' }); - - if (projFound) list.push({ ok: true, text: 'Projects section detected' }); - else list.push({ ok: false, text: 'Add a Projects section' }); - - if (foundSkills.length >= 5) - list.push({ ok: true, text: `Strong skill set detected (${foundSkills.length} skills)` }); - else - list.push({ ok: false, text: 'Add more technical skills' }); - - if (missingSkills.length > 0) - list.push({ ok: false, text: `Consider adding: ${missingSkills.slice(0, 3).map(capitalise).join(', ')}` }); - - list.push({ ok: false, text: 'Use strong action verbs (e.g. "Developed", "Led", "Built")' }); - - return list.slice(0, 6); - } - - function capitalise(str) { - return str.charAt(0).toUpperCase() + str.slice(1); - } -} \ No newline at end of file diff --git a/web-app/projects_registry.json b/web-app/projects_registry.json index 4c88a0c9..5535901e 100644 --- a/web-app/projects_registry.json +++ b/web-app/projects_registry.json @@ -487,21 +487,6 @@ ], "path": "math/Quadratic-Solver/Quadratic-Solver.py" }, - { - "name": "AI Resume Analyzer", - "emoji": "πŸ“„", - "category": "utilities", - "difficulty": "advanced", - "description": "Analyze resumes using NLTK to extract key skills and suggestions.", - "keywords": [ - "resume", - "analyzer", - "nltk", - "skills", - "parser" - ], - "path": "utilities/AI-Resume-Analyzer/AI-Resume-Analyzer.py" - }, { "name": "Budget Tracker", "emoji": "πŸ’°", diff --git a/web-app/utilities.html b/web-app/utilities.html index 2ed4577e..12f05017 100644 --- a/web-app/utilities.html +++ b/web-app/utilities.html @@ -370,27 +370,6 @@

Budget Tracker

Track income and expenses, view categories, and persist data locally

-
- AI Resume Analyzer -
- -
- -

AI Resume Analyzer

-

- Score resumes with ATS-style keyword and formatting insights! -

-
-
TSP Visualizer -