Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Resume Matcher</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Righteous&family=Urbanist:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| :root { | |
| --bg-color: #05050A; | |
| --surface-color: rgba(255, 255, 255, 0.03); | |
| --surface-border: rgba(255, 255, 255, 0.08); | |
| --primary: #8B5CF6; | |
| --secondary: #3B82F6; | |
| --accent: #06B6D4; | |
| --success: #10B981; | |
| --danger: #EF4444; | |
| --text-main: #FFFFFF; | |
| --text-muted: #9CA3AF; | |
| --font-display: 'Righteous', cursive; | |
| --font-body: 'Urbanist', sans-serif; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| color: var(--text-main); | |
| font-family: var(--font-body); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { width: 8px; } | |
| ::-webkit-scrollbar-track { background: var(--bg-color); } | |
| ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 4px; } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.2); } | |
| /* --- Fast Liquid Background Elements --- */ | |
| .bg-elements { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| z-index: -1; | |
| overflow: hidden; | |
| background: #05050A; | |
| } | |
| /* Wrappers handle the fast sweeping across the screen behind the card */ | |
| .blob-wrapper { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| top: 0; | |
| left: 0; | |
| } | |
| .wrapper-1 { animation: drift1 12s infinite alternate ease-in-out; } | |
| .wrapper-2 { animation: drift2 14s infinite alternate ease-in-out; } | |
| .wrapper-3 { animation: drift3 16s infinite alternate ease-in-out; } | |
| /* Blobs handle the fluid water morphing */ | |
| .blob { | |
| position: absolute; | |
| filter: blur(90px); | |
| opacity: 0.5; | |
| mix-blend-mode: screen; | |
| animation: waterMorph 10s infinite linear; | |
| } | |
| .blob-1 { | |
| top: -10%; left: -10%; | |
| width: 600px; height: 600px; | |
| background: var(--primary); | |
| animation-duration: 12s; | |
| } | |
| .blob-2 { | |
| bottom: -20%; right: -10%; | |
| width: 700px; height: 700px; | |
| background: var(--secondary); | |
| animation-duration: 14s; | |
| animation-direction: reverse; | |
| } | |
| .blob-3 { | |
| top: 20%; left: -20%; | |
| width: 500px; height: 500px; | |
| background: var(--accent); | |
| opacity: 0.35; | |
| animation-duration: 10s; | |
| } | |
| /* Liquid Morphing Keyframes */ | |
| @keyframes waterMorph { | |
| 0% { | |
| border-radius: 40% 60% 70% 30% / 40% 40% 60% 50%; | |
| transform: rotate(0deg) scale(1); | |
| } | |
| 34% { | |
| border-radius: 70% 30% 50% 50% / 30% 30% 70% 70%; | |
| transform: rotate(120deg) scale(1.05); | |
| } | |
| 67% { | |
| border-radius: 100% 60% 60% 100% / 100% 100% 60% 60%; | |
| transform: rotate(240deg) scale(0.95); | |
| } | |
| 100% { | |
| border-radius: 40% 60% 70% 30% / 40% 40% 60% 50%; | |
| transform: rotate(360deg) scale(1); | |
| } | |
| } | |
| /* Faster Drifting Keyframes that cross the center of the screen */ | |
| @keyframes drift1 { | |
| 0% { transform: translate(0, 0); } | |
| 100% { transform: translate(60vw, 40vh); } /* Sweeps top-left to bottom-right */ | |
| } | |
| @keyframes drift2 { | |
| 0% { transform: translate(0, 0); } | |
| 100% { transform: translate(-50vw, -50vh); } /* Sweeps bottom-right to top-left */ | |
| } | |
| @keyframes drift3 { | |
| 0% { transform: translate(0, 0); } | |
| 100% { transform: translate(80vw, 10vh); } /* Sweeps straight across the middle */ | |
| } | |
| /* --- Layout --- */ | |
| .container { | |
| width: 100%; | |
| max-width: 1200px; | |
| padding: 4rem 2rem 2rem 2rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3rem; | |
| z-index: 1; | |
| flex: 1; | |
| } | |
| /* Hero Section */ | |
| header { | |
| text-align: center; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 1.5rem; | |
| animation: slideDown 0.6s ease-out forwards; | |
| } | |
| .ai-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.5rem 1rem; | |
| background: rgba(139, 92, 246, 0.1); | |
| border: 1px solid rgba(139, 92, 246, 0.3); | |
| border-radius: 100px; | |
| font-size: 0.875rem; | |
| font-weight: 700; | |
| color: #C4B5FD; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| box-shadow: 0 0 20px rgba(139, 92, 246, 0.2); | |
| animation: pulse-glow 2s infinite; | |
| } | |
| @keyframes pulse-glow { | |
| 0%, 100% { box-shadow: 0 0 15px rgba(139, 92, 246, 0.2); } | |
| 50% { box-shadow: 0 0 25px rgba(139, 92, 246, 0.5); } | |
| } | |
| h1 { | |
| font-family: var(--font-display); | |
| font-size: 4.5rem; | |
| line-height: 1.1; | |
| letter-spacing: 0.02em; | |
| background: linear-gradient(to right, #FFFFFF, #A78BFA, #06B6D4); | |
| background-clip: text; | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-shadow: 0px 4px 20px rgba(139, 92, 246, 0.3); | |
| } | |
| .subtitle { | |
| font-size: 1.25rem; | |
| color: var(--text-muted); | |
| max-width: 600px; | |
| font-weight: 500; | |
| } | |
| /* Glass Card */ | |
| .glass-card { | |
| background: var(--surface-color); | |
| backdrop-filter: blur(24px); | |
| -webkit-backdrop-filter: blur(24px); | |
| border: 1px solid var(--surface-border); | |
| border-radius: 24px; | |
| padding: 2.5rem; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| animation: fadeUp 0.8s ease-out forwards; | |
| } | |
| .grid-layout { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 2rem; | |
| } | |
| /* Form Elements */ | |
| .input-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| position: relative; | |
| } | |
| .input-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| label { | |
| font-family: var(--font-display); | |
| font-size: 1.2rem; | |
| letter-spacing: 1px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| label i { color: var(--accent); } | |
| .char-count { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| font-weight: 600; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 350px; | |
| background: rgba(0, 0, 0, 0.3); | |
| border: 1px solid var(--surface-border); | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| color: var(--text-main); | |
| font-family: var(--font-body); | |
| font-size: 1rem; | |
| line-height: 1.6; | |
| resize: none; | |
| transition: all 0.3s ease; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 4px rgba(139, 92, 246, 0.15); | |
| background: rgba(0, 0, 0, 0.5); | |
| } | |
| textarea::placeholder { color: #4B5563; } | |
| /* Controls */ | |
| .controls { | |
| margin-top: 2.5rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding-top: 2rem; | |
| border-top: 1px solid var(--surface-border); | |
| } | |
| .keyboard-hint { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| } | |
| kbd { | |
| background: rgba(255, 255, 255, 0.1); | |
| padding: 0.3rem 0.6rem; | |
| border-radius: 6px; | |
| font-family: monospace; | |
| font-size: 0.85rem; | |
| color: var(--text-main); | |
| } | |
| .buttons { display: flex; gap: 1rem; } | |
| button { | |
| border: none; | |
| cursor: pointer; | |
| font-family: var(--font-display); | |
| letter-spacing: 1px; | |
| font-size: 1.1rem; | |
| padding: 1rem 2rem; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| .btn-clear { | |
| background: transparent; | |
| color: var(--text-muted); | |
| border: 1px solid var(--surface-border); | |
| } | |
| .btn-clear:hover { | |
| color: var(--text-main); | |
| background: rgba(255, 255, 255, 0.05); | |
| transform: scale(1.05); | |
| } | |
| .btn-analyze { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| color: white; | |
| position: relative; | |
| overflow: hidden; | |
| box-shadow: 0 10px 20px -10px rgba(139, 92, 246, 0.5); | |
| } | |
| .btn-analyze::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: -100%; | |
| width: 100%; height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); | |
| transition: left 0.5s ease; | |
| } | |
| .btn-analyze:hover { | |
| transform: translateY(-4px) scale(1.02); | |
| box-shadow: 0 15px 25px -10px rgba(139, 92, 246, 0.7); | |
| } | |
| .btn-analyze:hover::before { left: 100%; } | |
| /* Error States */ | |
| .error-msg { | |
| color: var(--danger); | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| position: absolute; | |
| bottom: -25px; left: 0; | |
| opacity: 0; | |
| transform: translateY(-5px); | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.4rem; | |
| } | |
| .has-error .error-msg { opacity: 1; transform: translateY(0); } | |
| .has-error textarea { | |
| border-color: rgba(239, 68, 68, 0.5); | |
| background: rgba(239, 68, 68, 0.02); | |
| } | |
| /* Loader */ | |
| .loader-container { | |
| display: none; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 4rem 0; | |
| gap: 1.5rem; | |
| } | |
| .spinner { | |
| width: 54px; height: 54px; | |
| border: 4px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 50%; | |
| border-top-color: var(--accent); | |
| animation: spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; | |
| } | |
| .loader-text { | |
| font-family: var(--font-display); | |
| color: var(--accent); | |
| letter-spacing: 3px; | |
| font-size: 1.2rem; | |
| animation: pulse 2s infinite; | |
| } | |
| /* Results Section */ | |
| .results-container { | |
| display: none; | |
| margin-top: 2rem; | |
| animation: fadeUp 0.6s ease-out forwards; | |
| } | |
| .result-card { | |
| background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.01) 100%); | |
| border-radius: 24px; | |
| padding: 3rem; | |
| border: 1px solid var(--surface-border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 4rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .result-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; | |
| width: 100%; height: 5px; | |
| background: var(--bg-glow); | |
| transition: background 0.5s ease; | |
| } | |
| .score-circle { | |
| position: relative; | |
| width: 220px; height: 220px; | |
| } | |
| .score-circle svg { | |
| width: 100%; height: 100%; | |
| transform: rotate(-90deg); | |
| } | |
| .score-circle circle { | |
| fill: none; | |
| stroke-width: 10; | |
| stroke-linecap: round; | |
| } | |
| .circle-bg { stroke: rgba(255, 255, 255, 0.05); } | |
| .circle-progress { | |
| stroke: var(--circle-color); | |
| stroke-dasharray: 628; | |
| stroke-dashoffset: 628; | |
| transition: stroke-dashoffset 1.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| .score-value { | |
| position: absolute; | |
| top: 50%; left: 50%; | |
| transform: translate(-50%, -50%); | |
| font-family: var(--font-display); | |
| font-size: 4rem; | |
| color: var(--text-main); | |
| } | |
| .verdict-info { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .verdict-label { | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 3px; | |
| font-size: 0.9rem; | |
| font-weight: 700; | |
| } | |
| .verdict-text { | |
| font-family: var(--font-display); | |
| font-size: 3rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| text-shadow: 0 0 25px var(--bg-glow); | |
| animation: bounceIn 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| /* Footer */ | |
| footer { | |
| width: 100%; | |
| padding: 2rem 0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-top: auto; | |
| opacity: 0; | |
| animation: fadeUp 1s ease-out forwards; | |
| animation-delay: 0.5s; | |
| } | |
| .footer-text { | |
| color: var(--text-muted); | |
| font-size: 1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .heart-icon { | |
| color: var(--danger); | |
| animation: heartbeat 1.5s infinite; | |
| } | |
| .github-link { | |
| color: var(--text-main); | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 14px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border: 1px solid var(--surface-border); | |
| text-decoration: none; | |
| transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); | |
| } | |
| .github-link:hover { | |
| transform: translateY(-8px) rotate(10deg) scale(1.1); | |
| background: #ffffff; | |
| color: #000000; | |
| border-color: #ffffff; | |
| box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2); | |
| } | |
| /* Keyframes */ | |
| @keyframes slideDown { | |
| from { opacity: 0; transform: translateY(-40px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeUp { | |
| from { opacity: 0; transform: translateY(40px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.7; transform: scale(1.05); } | |
| } | |
| @keyframes bounceIn { | |
| 0% { transform: scale(0.5); opacity: 0; } | |
| 80% { transform: scale(1.05); opacity: 1; } | |
| 100% { transform: scale(1); opacity: 1; } | |
| } | |
| @keyframes heartbeat { | |
| 0%, 100% { transform: scale(1); } | |
| 25% { transform: scale(1.2); } | |
| 50% { transform: scale(1); } | |
| 75% { transform: scale(1.2); } | |
| } | |
| /* --- Deep Responsiveness --- */ | |
| @media (max-width: 1024px) { | |
| h1 { font-size: 3.5rem; } | |
| textarea { height: 280px; } | |
| .glass-card { padding: 2rem; } | |
| } | |
| @media (max-width: 768px) { | |
| .container { padding: 2rem 1rem 1rem 1rem; gap: 2rem; } | |
| h1 { font-size: 2.8rem; } | |
| .subtitle { font-size: 1.1rem; } | |
| .grid-layout { grid-template-columns: 1fr; gap: 1.5rem; } | |
| textarea { height: 220px; } | |
| .controls { flex-direction: column; gap: 1.5rem; padding-top: 1.5rem; } | |
| .keyboard-hint { display: none; } /* Hide shortcut on mobile */ | |
| .buttons { width: 100%; display: grid; grid-template-columns: 1fr 2fr; } | |
| button { justify-content: center; font-size: 1rem; padding: 0.8rem 1rem; } | |
| /* Responsive Result Card */ | |
| .result-card { flex-direction: column; text-align: center; gap: 2rem; padding: 2rem 1rem; } | |
| .verdict-text { justify-content: center; font-size: 2.5rem; } | |
| .score-circle { width: 180px; height: 180px; } | |
| .score-value { font-size: 3.2rem; } | |
| } | |
| @media (max-width: 480px) { | |
| h1 { font-size: 2.2rem; } | |
| .glass-card { padding: 1.25rem; border-radius: 16px; } | |
| .buttons { grid-template-columns: 1fr; } /* Stack buttons on very small screens */ | |
| .verdict-text { font-size: 2rem; } | |
| .verdict-info { gap: 0.5rem; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-elements"> | |
| <div class="blob-wrapper wrapper-1"> | |
| <div class="blob blob-1"></div> | |
| </div> | |
| <div class="blob-wrapper wrapper-2"> | |
| <div class="blob blob-2"></div> | |
| </div> | |
| <div class="blob-wrapper wrapper-3"> | |
| <div class="blob blob-3"></div> | |
| </div> | |
| </div> | |
| <main class="container"> | |
| <header> | |
| <div class="ai-badge"> | |
| <i data-lucide="sparkles"></i> AI Powered | |
| </div> | |
| <h1>Resume Matcher</h1> | |
| <p class="subtitle">See exactly how perfectly your resume aligns with any job description in seconds.</p> | |
| </header> | |
| <div class="glass-card"> | |
| <div class="grid-layout"> | |
| <div class="input-group" id="resumeGroup"> | |
| <div class="input-header"> | |
| <label><i data-lucide="file-text"></i> Your Resume</label> | |
| <span class="char-count" id="resumeCount">0 chars</span> | |
| </div> | |
| <textarea id="resumeInput" placeholder="Paste your plain text resume content here..."></textarea> | |
| <span class="error-msg"><i data-lucide="alert-triangle" width="16" height="16"></i> Resume is required</span> | |
| </div> | |
| <div class="input-group" id="jobGroup"> | |
| <div class="input-header"> | |
| <label><i data-lucide="briefcase"></i> Job Description</label> | |
| <span class="char-count" id="jobCount">0 chars</span> | |
| </div> | |
| <textarea id="jobInput" placeholder="Paste the target job description here..."></textarea> | |
| <span class="error-msg"><i data-lucide="alert-triangle" width="16" height="16"></i> Job description is required</span> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <div class="keyboard-hint"> | |
| <kbd>Ctrl</kbd> + <kbd>Enter</kbd> to analyze | |
| </div> | |
| <div class="buttons"> | |
| <button class="btn-clear" id="clearBtn"> | |
| <i data-lucide="rotate-ccw" width="20" height="20"></i> Clear | |
| </button> | |
| <button class="btn-analyze" id="analyzeBtn"> | |
| <i data-lucide="zap" width="20" height="20"></i> Analyze Match | |
| </button> | |
| </div> | |
| </div> | |
| <div class="loader-container" id="loader"> | |
| <div class="spinner"></div> | |
| <div class="loader-text">CRUNCHING DATA...</div> | |
| </div> | |
| <div class="results-container" id="results"> | |
| <div class="result-card" id="resultCard"> | |
| <div class="score-circle"> | |
| <svg viewBox="0 0 220 220"> | |
| <circle class="circle-bg" cx="110" cy="110" r="100"></circle> | |
| <circle class="circle-progress" id="progressCircle" cx="110" cy="110" r="100"></circle> | |
| </svg> | |
| <div class="score-value" id="scoreValue">0.00</div> | |
| </div> | |
| <div class="verdict-info"> | |
| <div class="verdict-label">AI Match Verdict</div> | |
| <div class="verdict-text" id="verdictText"> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <footer> | |
| <div class="footer-text"> | |
| Built with <i data-lucide="heart" class="heart-icon" width="18" height="18"></i> with Flask • TensorFlow • NLP | |
| </div> | |
| <a href="https://github.com/Nishi1195/ResumeMatching-DLmodel" target="_blank" class="github-link" aria-label="GitHub Profile"> | |
| <i data-lucide="github" width="24" height="24"></i> | |
| </a> | |
| </footer> | |
| <script> | |
| lucide.createIcons(); | |
| // DOM Elements | |
| const resumeInput = document.getElementById('resumeInput'); | |
| const jobInput = document.getElementById('jobInput'); | |
| const resumeCount = document.getElementById('resumeCount'); | |
| const jobCount = document.getElementById('jobCount'); | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const loader = document.getElementById('loader'); | |
| const results = document.getElementById('results'); | |
| const progressCircle = document.getElementById('progressCircle'); | |
| const scoreValue = document.getElementById('scoreValue'); | |
| const verdictText = document.getElementById('verdictText'); | |
| const resultCard = document.getElementById('resultCard'); | |
| const resumeGroup = document.getElementById('resumeGroup'); | |
| const jobGroup = document.getElementById('jobGroup'); | |
| // Character Counters | |
| const updateCount = (input, counter) => { | |
| counter.textContent = `${input.value.length.toLocaleString()} chars`; | |
| }; | |
| resumeInput.addEventListener('input', () => updateCount(resumeInput, resumeCount)); | |
| jobInput.addEventListener('input', () => updateCount(jobInput, jobCount)); | |
| // Error handling | |
| const clearErrors = () => { | |
| resumeGroup.classList.remove('has-error'); | |
| jobGroup.classList.remove('has-error'); | |
| }; | |
| // 🚀 REAL API CALL (FIXED) | |
| const analyzeMatch = async () => { | |
| const resume = resumeInput.value.trim(); | |
| const jobDesc = jobInput.value.trim(); | |
| let hasError = false; | |
| clearErrors(); | |
| if (!resume) { resumeGroup.classList.add('has-error'); hasError = true; } | |
| if (!jobDesc) { jobGroup.classList.add('has-error'); hasError = true; } | |
| if (hasError) return; | |
| results.style.display = 'none'; | |
| loader.style.display = 'flex'; | |
| analyzeBtn.disabled = true; | |
| analyzeBtn.style.opacity = '0.7'; | |
| loader.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| try { | |
| const response = await fetch("/predict", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify({ | |
| resume: resume, | |
| job_description: jobDesc | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error("Server error"); | |
| } | |
| const data = await response.json(); | |
| updateResultsUI(data); | |
| } catch (error) { | |
| alert("⚠️ Failed to connect to backend. Please try again."); | |
| } finally { | |
| loader.style.display = 'none'; | |
| analyzeBtn.disabled = false; | |
| analyzeBtn.style.opacity = '1'; | |
| } | |
| }; | |
| // UI Update | |
| const updateResultsUI = (data) => { | |
| results.style.display = 'block'; | |
| const isAccepted = data.verdict === 'Accepted'; | |
| const color = isAccepted ? 'var(--success)' : 'var(--danger)'; | |
| const icon = isAccepted ? 'check-circle-2' : 'x-octagon'; | |
| resultCard.style.setProperty('--bg-glow', color); | |
| resultCard.style.setProperty('--circle-color', color); | |
| scoreValue.textContent = "0.000"; | |
| scoreValue.style.color = color; | |
| let startTime = performance.now(); | |
| const duration = 1500; | |
| const end = data.match_probability; | |
| const animateScore = (currentTime) => { | |
| const progress = Math.min((currentTime - startTime) / duration, 1); | |
| const easeOut = 1 - Math.pow(1 - progress, 4); | |
| scoreValue.textContent = (end * easeOut).toFixed(3); | |
| if (progress < 1) requestAnimationFrame(animateScore); | |
| }; | |
| requestAnimationFrame(animateScore); | |
| setTimeout(() => { | |
| const circumference = 628; | |
| progressCircle.style.strokeDashoffset = circumference - (end * circumference); | |
| }, 100); | |
| verdictText.innerHTML = ` | |
| <i data-lucide="${icon}" width="42" height="42" style="color: ${color}"></i> | |
| <span style="color: ${color}">${data.verdict}</span> | |
| `; | |
| lucide.createIcons(); | |
| setTimeout(() => { | |
| const y = results.getBoundingClientRect().top + window.scrollY - 50; | |
| window.scrollTo({ top: y, behavior: 'smooth' }); | |
| }, 150); | |
| }; | |
| // Events | |
| analyzeBtn.addEventListener('click', analyzeMatch); | |
| clearBtn.addEventListener('click', () => { | |
| resumeInput.value = ''; | |
| jobInput.value = ''; | |
| updateCount(resumeInput, resumeCount); | |
| updateCount(jobInput, jobCount); | |
| results.style.display = 'none'; | |
| clearErrors(); | |
| progressCircle.style.strokeDashoffset = 628; | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| }); | |
| document.addEventListener('keydown', (e) => { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') analyzeMatch(); | |
| }); | |
| resumeInput.addEventListener('input', () => resumeGroup.classList.remove('has-error')); | |
| jobInput.addEventListener('input', () => jobGroup.classList.remove('has-error')); | |
| </script> | |
| </body> | |
| </html> |