Krina2005's picture
Upload 9 files
29017a7 verified
<!DOCTYPE html>
<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>