Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TechRadar MCP — Universal Tech Hiring Intelligence</title> | |
| <!-- Google Fonts: Bebas Neue & Poppins --> | |
| <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=Bebas+Neue&family=Poppins:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <!-- Chart.js for Market Analytics --> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| :root { | |
| --bg-dark: #090514; | |
| --bg-card: rgba(20, 14, 45, 0.65); | |
| --border-glass: rgba(0, 229, 255, 0.25); | |
| --border-glow: #00e5ff; | |
| --accent-cyan: #00e5ff; | |
| --accent-purple: #9d4edd; | |
| --accent-pink: #e94057; | |
| --text-main: #f0f4f8; | |
| --text-muted: #a0aec0; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: 'Poppins', sans-serif; | |
| overflow-x: hidden; | |
| min-height: 100vh; | |
| } | |
| /* Canvas 3D Cyber Wave Grid Background */ | |
| #cyber-canvas { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| z-index: -1; | |
| pointer-events: none; | |
| } | |
| /* Main Wrapper */ | |
| .app-container { | |
| max-width: 1350px; | |
| margin: 0 auto; | |
| padding: 20px 30px 80px 30px; | |
| } | |
| /* Navbar */ | |
| .navbar { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 20px 0; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.08); | |
| margin-bottom: 30px; | |
| } | |
| .brand-logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 2.2rem; | |
| letter-spacing: 2px; | |
| background: linear-gradient(90deg, #00e5ff, #9d4edd, #e94057); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .mcp-status-pill { | |
| background: rgba(0, 229, 255, 0.1); | |
| border: 1px solid var(--accent-cyan); | |
| color: var(--accent-cyan); | |
| padding: 6px 16px; | |
| border-radius: 50px; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| box-shadow: 0 0 15px rgba(0, 229, 255, 0.3); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| background-color: var(--accent-cyan); | |
| border-radius: 50%; | |
| animation: pulse-dot 1.5s infinite; | |
| } | |
| @keyframes pulse-dot { | |
| 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 229, 255, 0.7); } | |
| 70% { transform: scale(1); box-shadow: 0 0 0 8px rgba(0, 229, 255, 0); } | |
| 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(0, 229, 255, 0); } | |
| } | |
| /* Hero Banner Section (Inspired by reference 5718062.jpg) */ | |
| .hero-banner { | |
| position: relative; | |
| background: linear-gradient(135deg, rgba(20, 14, 50, 0.8) 0%, rgba(10, 6, 30, 0.9) 100%); | |
| border: 1px solid var(--border-glass); | |
| backdrop-filter: blur(20px); | |
| border-radius: 24px; | |
| padding: 45px 50px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-bottom: 40px; | |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), inset 0 1px 1px rgba(255, 255, 255, 0.15); | |
| overflow: hidden; | |
| } | |
| .hero-banner::before { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| right: -10%; | |
| width: 400px; | |
| height: 400px; | |
| background: radial-gradient(circle, rgba(157, 78, 221, 0.3) 0%, transparent 70%); | |
| pointer-events: none; | |
| } | |
| .hero-text { | |
| max-width: 650px; | |
| z-index: 2; | |
| } | |
| .hero-tag { | |
| text-transform: uppercase; | |
| font-size: 0.9rem; | |
| letter-spacing: 3px; | |
| color: var(--accent-cyan); | |
| font-weight: 700; | |
| margin-bottom: 10px; | |
| } | |
| .hero-title { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 4rem; | |
| line-height: 1; | |
| letter-spacing: 3px; | |
| margin-bottom: 15px; | |
| background: linear-gradient(90deg, #ffffff, #e2e8f0); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-shadow: 0 0 30px rgba(0, 229, 255, 0.3); | |
| } | |
| .hero-subtitle { | |
| font-size: 1.05rem; | |
| color: var(--text-muted); | |
| margin-bottom: 25px; | |
| line-height: 1.6; | |
| } | |
| .btn-gradient { | |
| background: linear-gradient(90deg, var(--accent-cyan), var(--accent-purple)); | |
| color: white; | |
| padding: 14px 34px; | |
| border-radius: 50px; | |
| font-size: 0.95rem; | |
| font-weight: 700; | |
| border: none; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 0 25px rgba(0, 229, 255, 0.4); | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .btn-gradient:hover { | |
| transform: translateY(-3px) scale(1.02); | |
| box-shadow: 0 0 35px rgba(157, 78, 221, 0.6); | |
| } | |
| /* 3D Visual Orb / Graphics */ | |
| .hero-graphic { | |
| position: relative; | |
| width: 320px; | |
| height: 220px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 2; | |
| } | |
| .orb-ring { | |
| position: absolute; | |
| border-radius: 50%; | |
| border: 2px dashed rgba(0, 229, 255, 0.4); | |
| animation: spin 20s linear infinite; | |
| } | |
| .orb-ring-1 { width: 220px; height: 220px; border-color: rgba(0, 229, 255, 0.5); } | |
| .orb-ring-2 { width: 170px; height: 170px; border-color: rgba(157, 78, 221, 0.6); animation-direction: reverse; animation-duration: 15s; } | |
| .orb-core { | |
| width: 110px; | |
| height: 110px; | |
| border-radius: 50%; | |
| background: radial-gradient(circle at 30% 30%, #00e5ff, #9d4edd 70%, #090514); | |
| box-shadow: 0 0 50px rgba(0, 229, 255, 0.6); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 2rem; | |
| color: white; | |
| text-shadow: 0 0 10px rgba(255, 255, 255, 0.8); | |
| } | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Controls & Filter Bar */ | |
| .filter-section { | |
| background: var(--bg-card); | |
| backdrop-filter: blur(15px); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 16px; | |
| padding: 20px 25px; | |
| margin-bottom: 30px; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .filter-group { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex-wrap: wrap; | |
| } | |
| .filter-label { | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .pill-btn { | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| color: var(--text-main); | |
| padding: 8px 16px; | |
| border-radius: 30px; | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.25s ease; | |
| } | |
| .pill-btn:hover, .pill-btn.active { | |
| background: linear-gradient(90deg, rgba(0, 229, 255, 0.2), rgba(157, 78, 221, 0.2)); | |
| border-color: var(--accent-cyan); | |
| color: white; | |
| box-shadow: 0 0 15px rgba(0, 229, 255, 0.3); | |
| } | |
| .search-input { | |
| background: rgba(10, 6, 25, 0.8); | |
| border: 1px solid var(--border-glass); | |
| color: white; | |
| padding: 10px 20px; | |
| border-radius: 30px; | |
| font-size: 0.9rem; | |
| width: 280px; | |
| outline: none; | |
| transition: all 0.3s ease; | |
| } | |
| .search-input:focus { | |
| border-color: var(--accent-cyan); | |
| box-shadow: 0 0 20px rgba(0, 229, 255, 0.4); | |
| } | |
| /* Navigation Tabs */ | |
| .tab-menu { | |
| display: flex; | |
| gap: 15px; | |
| margin-bottom: 30px; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| padding-bottom: 10px; | |
| } | |
| .tab-item { | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 1.5rem; | |
| letter-spacing: 1.5px; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| padding: 8px 16px; | |
| border-radius: 8px; | |
| transition: all 0.3s ease; | |
| } | |
| .tab-item:hover, .tab-item.active { | |
| color: var(--accent-cyan); | |
| background: rgba(0, 229, 255, 0.08); | |
| text-shadow: 0 0 10px rgba(0, 229, 255, 0.5); | |
| } | |
| /* Tab Content Panes */ | |
| .tab-pane { | |
| display: none; | |
| } | |
| .tab-pane.active { | |
| display: block; | |
| } | |
| /* Job Grid & 3D Glass Cards */ | |
| .job-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); | |
| gap: 25px; | |
| } | |
| .job-card-3d { | |
| background: var(--bg-card); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 20px; | |
| padding: 25px; | |
| transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| transform-style: preserve-3d; | |
| perspective: 1000px; | |
| } | |
| .job-card-3d:hover { | |
| transform: translateY(-8px) rotateX(2deg) rotateY(-2deg); | |
| border-color: var(--accent-cyan); | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), 0 0 30px rgba(0, 229, 255, 0.3); | |
| } | |
| .job-card-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| margin-bottom: 12px; | |
| } | |
| .job-title { | |
| font-family: 'Poppins', sans-serif; | |
| font-size: 1.2rem; | |
| font-weight: 700; | |
| color: #ffffff; | |
| line-height: 1.3; | |
| } | |
| .badge-location { | |
| background: rgba(0, 229, 255, 0.15); | |
| border: 1px solid rgba(0, 229, 255, 0.4); | |
| color: var(--accent-cyan); | |
| padding: 4px 10px; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| } | |
| .job-company { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| margin-bottom: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .job-salary { | |
| font-size: 1rem; | |
| font-weight: 700; | |
| color: #00ff9d; | |
| margin-bottom: 12px; | |
| } | |
| .job-desc { | |
| font-size: 0.85rem; | |
| color: #cbd5e0; | |
| line-height: 1.5; | |
| margin-bottom: 15px; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 3; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .stack-tags { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| margin-bottom: 15px; | |
| } | |
| .stack-tag { | |
| background: rgba(255, 255, 255, 0.06); | |
| border: 1px solid rgba(255, 255, 255, 0.12); | |
| color: var(--text-main); | |
| padding: 3px 8px; | |
| border-radius: 6px; | |
| font-size: 0.75rem; | |
| } | |
| .job-card-footer { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 0.75rem; | |
| color: var(--text-muted); | |
| border-top: 1px solid rgba(255, 255, 255, 0.08); | |
| padding-top: 12px; | |
| } | |
| /* ATS & Skill Gap Section */ | |
| .analyzer-container { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 30px; | |
| } | |
| .analyzer-box { | |
| background: var(--bg-card); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 20px; | |
| padding: 30px; | |
| } | |
| .form-label { | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| margin-bottom: 8px; | |
| display: block; | |
| } | |
| .form-textarea { | |
| width: 100%; | |
| height: 180px; | |
| background: rgba(10, 6, 25, 0.8); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 12px; | |
| padding: 15px; | |
| color: white; | |
| font-family: 'Poppins', sans-serif; | |
| font-size: 0.85rem; | |
| outline: none; | |
| resize: vertical; | |
| margin-bottom: 15px; | |
| } | |
| .form-select { | |
| width: 100%; | |
| background: rgba(10, 6, 25, 0.8); | |
| border: 1px solid var(--border-glass); | |
| border-radius: 10px; | |
| padding: 12px; | |
| color: white; | |
| font-family: 'Poppins', sans-serif; | |
| font-size: 0.85rem; | |
| outline: none; | |
| margin-bottom: 20px; | |
| } | |
| .score-circle { | |
| width: 110px; | |
| height: 110px; | |
| border-radius: 50%; | |
| border: 4px solid var(--accent-cyan); | |
| box-shadow: 0 0 25px rgba(0, 229, 255, 0.5); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: 'Bebas Neue', sans-serif; | |
| font-size: 2.5rem; | |
| color: var(--accent-cyan); | |
| margin: 0 auto 20px auto; | |
| } | |
| .diff-card { | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid rgba(255, 204, 0, 0.3); | |
| border-radius: 10px; | |
| padding: 12px 15px; | |
| margin-bottom: 12px; | |
| font-size: 0.85rem; | |
| } | |
| .diff-original { color: #eb5757; margin-bottom: 4px; } | |
| .diff-tailored { color: #27ae60; font-weight: 600; } | |
| /* JSON Output Box */ | |
| .json-box { | |
| background: #05030a; | |
| border: 1px solid var(--border-glass); | |
| border-radius: 12px; | |
| padding: 15px; | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 0.85rem; | |
| color: #00e5ff; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| white-space: pre-wrap; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- 3D Cyber Mesh Canvas Background --> | |
| <canvas id="cyber-canvas"></canvas> | |
| <div class="app-container"> | |
| <!-- Top Navbar --> | |
| <div class="navbar"> | |
| <div class="brand-logo">⚡ TECHRADAR MCP</div> | |
| <div class="mcp-status-pill"> | |
| <span class="status-dot"></span> FASTMCP SERVER ACTIVE (STDIO/SSE) | |
| </div> | |
| </div> | |
| <!-- Hero Banner (3D Glassmorphism, matching 5718062.jpg) --> | |
| <div class="hero-banner"> | |
| <div class="hero-text"> | |
| <div class="hero-tag">MODEL CONTEXT PROTOCOL ECOSYSTEM</div> | |
| <h1 class="hero-title">UNIVERSAL TECH HIRING INTELLIGENCE</h1> | |
| <p class="hero-subtitle"> | |
| Autonomous AI job market intelligence, AST skill-gap evaluation, and ATS resume patching across Bengaluru, Pune, Hyderabad, Gurgaon, Mumbai & Remote tech hubs. | |
| </p> | |
| <button class="btn-gradient" onclick="switchTab('radar')"> | |
| 🚀 EXPLORE TECH RADAR | |
| </button> | |
| </div> | |
| <div class="hero-graphic"> | |
| <div class="orb-ring orb-ring-1"></div> | |
| <div class="orb-ring orb-ring-2"></div> | |
| <div class="orb-core">MCP AI</div> | |
| </div> | |
| </div> | |
| <!-- Filter Controls --> | |
| <div class="filter-section"> | |
| <div class="filter-group"> | |
| <span class="filter-label">Domain:</span> | |
| <button class="pill-btn active" onclick="filterDomain('All', this)">ALL DOMAINS</button> | |
| <button class="pill-btn" onclick="filterDomain('Backend Engineering', this)">BACKEND</button> | |
| <button class="pill-btn" onclick="filterDomain('Frontend Engineering', this)">FRONTEND</button> | |
| <button class="pill-btn" onclick="filterDomain('Full Stack Engineering', this)">FULL STACK</button> | |
| <button class="pill-btn" onclick="filterDomain('Cloud & DevOps', this)">DEVOPS</button> | |
| <button class="pill-btn" onclick="filterDomain('AI/ML & GenAI', this)">AI / GENAI</button> | |
| </div> | |
| <div class="filter-group"> | |
| <span class="filter-label">City:</span> | |
| <button class="pill-btn active" onclick="filterCity('All', this)">ALL CITIES</button> | |
| <button class="pill-btn" onclick="filterCity('Bengaluru', this)">BLR</button> | |
| <button class="pill-btn" onclick="filterCity('Pune', this)">PUNE</button> | |
| <button class="pill-btn" onclick="filterCity('Hyderabad', this)">HYD</button> | |
| <button class="pill-btn" onclick="filterCity('Remote', this)">REMOTE</button> | |
| </div> | |
| <input type="text" id="searchInput" class="search-input" placeholder="🔍 Search Go, React, vLLM..." onkeyup="handleSearch()"> | |
| </div> | |
| <!-- Navigation Tabs --> | |
| <div class="tab-menu"> | |
| <div class="tab-item active" onclick="switchTab('radar', this)">📡 UNIVERSAL TECH RADAR</div> | |
| <div class="tab-item" onclick="switchTab('analytics', this)">📊 MARKET ANALYTICS</div> | |
| <div class="tab-item" onclick="switchTab('ats', this)">🎯 ATS SKILL GAP EVALUATOR</div> | |
| <div class="tab-item" onclick="switchTab('mcp', this)">🧪 FASTMCP SERVER TESTER</div> | |
| </div> | |
| <!-- TAB 1: RADAR GRID --> | |
| <div id="tab-radar" class="tab-pane active"> | |
| <div class="job-grid" id="jobGrid"> | |
| <!-- Rendered dynamically via JS --> | |
| </div> | |
| </div> | |
| <!-- TAB 2: ANALYTICS --> | |
| <div id="tab-analytics" class="tab-pane"> | |
| <div class="analyzer-container"> | |
| <div class="analyzer-box"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px; color: var(--accent-cyan);">🔥 TOP DEMANDED TECH FRAMEWORKS</h3> | |
| <canvas id="skillsChart" height="220"></canvas> | |
| </div> | |
| <div class="analyzer-box"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px; color: var(--accent-purple);">🏢 MAJOR TECH HIRING HUBS</h3> | |
| <canvas id="hubsChart" height="220"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TAB 3: ATS ANALYZER --> | |
| <div id="tab-ats" class="tab-pane"> | |
| <div class="analyzer-container"> | |
| <div class="analyzer-box"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px;">INPUT CANDIDATE PROFILE</h3> | |
| <label class="form-label">Paste Resume Text:</label> | |
| <textarea id="resumeInput" class="form-textarea">Senior Backend Engineer with 4 years experience building Go microservices, REST APIs, and Docker containers in Bengaluru. Seeking Senior Go or Distributed Systems roles.</textarea> | |
| <label class="form-label">Select Target Job Opening:</label> | |
| <select id="jobSelect" class="form-select"></select> | |
| <button class="btn-gradient" style="width: 100%; justify-content: center;" onclick="runAtsEvaluation()"> | |
| 🚀 EVALUATE ATS MATCH & SKILL GAP | |
| </button> | |
| </div> | |
| <div class="analyzer-box" id="atsResultBox"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px; text-align: center;">EVALUATION RESULT</h3> | |
| <div class="score-circle" id="matchScore">--</div> | |
| <h4 style="font-size: 0.95rem; margin-bottom: 5px; color: #00ff9d;">✅ Matched Skills:</h4> | |
| <p id="matchedSkills" style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 15px;">-</p> | |
| <h4 style="font-size: 0.95rem; margin-bottom: 5px; color: #eb5757;">❌ Missing Skills (Dealbreakers):</h4> | |
| <p id="missingSkills" style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 20px;">-</p> | |
| <h4 style="font-size: 0.95rem; margin-bottom: 10px; color: var(--accent-cyan);">📝 ATS Tailored Resume Bullets:</h4> | |
| <div id="tailoredBullets"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- TAB 4: MCP TESTER --> | |
| <div id="tab-mcp" class="tab-pane"> | |
| <div class="analyzer-container"> | |
| <div class="analyzer-box"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px;">SELECT FASTMCP TOOL</h3> | |
| <select id="mcpToolSelect" class="form-select" onchange="updateMcpInputs()"> | |
| <option value="search_tech_jobs">search_tech_jobs</option> | |
| <option value="analyze_skill_gap">analyze_skill_gap</option> | |
| <option value="generate_tailored_resume_patch">generate_tailored_resume_patch</option> | |
| <option value="get_market_insights">get_market_insights</option> | |
| <option value="generate_interview_prep_kit">generate_interview_prep_kit</option> | |
| </select> | |
| <div id="mcpInputFields"></div> | |
| <button class="btn-gradient" style="width: 100%; justify-content: center;" onclick="executeMcpTool()"> | |
| ⚡ EXECUTE FASTMCP TOOL | |
| </button> | |
| </div> | |
| <div class="analyzer-box"> | |
| <h3 style="font-family: 'Bebas Neue', sans-serif; font-size: 1.8rem; margin-bottom: 15px; color: var(--accent-cyan);">JSON-RPC TOOL OUTPUT</h3> | |
| <div class="json-box" id="mcpJsonOutput">// Execute an MCP tool to view response</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Scripts for 3D Mesh Canvas & App Logic --> | |
| <script> | |
| // 1. Interactive 3D Cyber Dot Mesh Canvas (Matching 5718062.jpg) | |
| const canvas = document.getElementById('cyber-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let width = canvas.width = window.innerWidth; | |
| let height = canvas.height = window.innerHeight; | |
| window.addEventListener('resize', () => { | |
| width = canvas.width = window.innerWidth; | |
| height = canvas.height = window.innerHeight; | |
| }); | |
| const dots = []; | |
| const spacing = 45; | |
| for (let x = 0; x < width + spacing; x += spacing) { | |
| for (let y = 0; y < height + spacing; y += spacing) { | |
| dots.push({ | |
| baseX: x, | |
| baseY: y, | |
| x: x, | |
| y: y, | |
| angle: Math.random() * Math.PI * 2, | |
| speed: 0.02 + Math.random() * 0.02 | |
| }); | |
| } | |
| } | |
| let mouseX = width / 2; | |
| let mouseY = height / 2; | |
| window.addEventListener('mousemove', (e) => { | |
| mouseX = e.clientX; | |
| mouseY = e.clientY; | |
| }); | |
| function animateCanvas() { | |
| ctx.clearRect(0, 0, width, height); | |
| dots.forEach(dot => { | |
| dot.angle += dot.speed; | |
| const dist = Math.hypot(mouseX - dot.baseX, mouseY - dot.baseY); | |
| const maxDist = 200; | |
| let offset = Math.sin(dot.angle) * 4; | |
| if (dist < maxDist) { | |
| offset += (1 - dist / maxDist) * 15; | |
| } | |
| ctx.fillStyle = dist < 150 ? 'rgba(0, 229, 255, 0.4)' : 'rgba(157, 78, 221, 0.15)'; | |
| ctx.beginPath(); | |
| ctx.arc(dot.baseX, dot.baseY + offset, dist < 150 ? 2 : 1.2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| }); | |
| requestAnimationFrame(animateCanvas); | |
| } | |
| animateCanvas(); | |
| // 2. State & API Fetching Logic | |
| let allJobs = []; | |
| let currentDomain = 'All'; | |
| let currentCity = 'All'; | |
| async function fetchJobs() { | |
| try { | |
| const res = await fetch('/api/jobs'); | |
| const data = await res.json(); | |
| allJobs = data.jobs || []; | |
| renderJobs(); | |
| populateJobSelect(); | |
| initCharts(); | |
| } catch (err) { | |
| console.error("Failed to load jobs:", err); | |
| } | |
| } | |
| function renderJobs() { | |
| const grid = document.getElementById('jobGrid'); | |
| grid.innerHTML = ''; | |
| const filtered = allJobs.filter(j => { | |
| const domainMatch = currentDomain === 'All' || j.tech_domain === currentDomain; | |
| const cityMatch = currentCity === 'All' || j.city.toLowerCase() === currentCity.toLowerCase(); | |
| return domainMatch && cityMatch; | |
| }); | |
| if (filtered.length === 0) { | |
| grid.innerHTML = `<div style="grid-column: 1/-1; text-align: center; color: var(--text-muted); padding: 40px;">No matching tech roles found for selected filters.</div>`; | |
| return; | |
| } | |
| filtered.forEach(job => { | |
| const card = document.createElement('div'); | |
| card.className = 'job-card-3d'; | |
| card.innerHTML = ` | |
| <div class="job-card-header"> | |
| <h3 class="job-title">${job.title}</h3> | |
| <span class="badge-location">📍 ${job.city}</span> | |
| </div> | |
| <div class="job-company">🏢 ${job.company} • <span style="color: var(--accent-cyan);">${job.tech_domain}</span></div> | |
| <div class="job-salary">💰 ₹${job.salary_min_lpa}L - ₹${job.salary_max_lpa}L PA | ⏳ ${job.experience_min_years}-${job.experience_max_years} Yrs</div> | |
| <p class="job-desc">${job.requirements}</p> | |
| <div class="stack-tags"> | |
| ${job.tech_stack.map(s => `<span class="stack-tag">${s}</span>`).join('')} | |
| </div> | |
| <div class="job-card-footer"> | |
| <span>Job ID: <code>${job.id}</code></span> | |
| <span style="color: #00ff9d;">🌐 ${job.work_mode}</span> | |
| </div> | |
| `; | |
| // 3D Card Hover Perspective Effect | |
| card.addEventListener('mousemove', (e) => { | |
| const rect = card.getBoundingClientRect(); | |
| const x = e.clientX - rect.left - rect.width / 2; | |
| const y = e.clientY - rect.top - rect.height / 2; | |
| card.style.transform = `perspective(1000px) rotateX(${-y / 15}deg) rotateY(${x / 15}deg) translateY(-5px)`; | |
| }); | |
| card.addEventListener('mouseleave', () => { | |
| card.style.transform = `perspective(1000px) rotateX(0deg) rotateY(0deg) translateY(0)`; | |
| }); | |
| grid.appendChild(card); | |
| }); | |
| } | |
| function filterDomain(domain, el) { | |
| currentDomain = domain; | |
| document.querySelectorAll('.filter-group:nth-child(1) .pill-btn').forEach(b => b.classList.remove('active')); | |
| el.classList.add('active'); | |
| renderJobs(); | |
| } | |
| function filterCity(city, el) { | |
| currentCity = city; | |
| document.querySelectorAll('.filter-group:nth-child(2) .pill-btn').forEach(b => b.classList.remove('active')); | |
| el.classList.add('active'); | |
| renderJobs(); | |
| } | |
| function handleSearch() { | |
| const query = document.getElementById('searchInput').value.toLowerCase(); | |
| const cards = document.querySelectorAll('.job-card-3d'); | |
| cards.forEach(card => { | |
| const text = card.innerText.toLowerCase(); | |
| card.style.display = text.includes(query) ? 'block' : 'none'; | |
| }); | |
| } | |
| function switchTab(tabId, el) { | |
| document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active')); | |
| document.querySelectorAll('.tab-item').forEach(t => t.classList.remove('active')); | |
| document.getElementById(`tab-${tabId}`).classList.add('active'); | |
| if (el) el.classList.add('active'); | |
| } | |
| function populateJobSelect() { | |
| const sel = document.getElementById('jobSelect'); | |
| sel.innerHTML = allJobs.map(j => `<option value="${j.id}">${j.id} — ${j.title} at ${j.company} (${j.city})</option>`).join(''); | |
| } | |
| async function runAtsEvaluation() { | |
| const resumeText = document.getElementById('resumeInput').value; | |
| const targetJobId = document.getElementById('jobSelect').value; | |
| try { | |
| const res = await fetch('/api/mcp/execute', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ tool: 'analyze_skill_gap', args: { resume_text: resumeText, target_job_id: targetJobId } }) | |
| }); | |
| const data = await res.json(); | |
| const report = JSON.parse(data.result); | |
| document.getElementById('matchScore').innerText = `${report.match_percentage}%`; | |
| document.getElementById('matchedSkills').innerText = report.matched_skills.join(', ') || 'None'; | |
| document.getElementById('missingSkills').innerText = report.missing_skills.join(', ') || 'None'; | |
| // Fetch Patch | |
| const patchRes = await fetch('/api/mcp/execute', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ tool: 'generate_tailored_resume_patch', args: { resume_text: resumeText, target_job_id: targetJobId } }) | |
| }); | |
| const patchData = await patchRes.json(); | |
| const patch = JSON.parse(patchData.result); | |
| document.getElementById('tailoredBullets').innerHTML = patch.tailored_bullets.map(b => ` | |
| <div class="diff-card"> | |
| <div class="diff-original">Original: ${b.original}</div> | |
| <div class="diff-tailored">Tailored: ${b.tailored}</div> | |
| </div> | |
| `).join(''); | |
| } catch (err) { | |
| console.error("Evaluation failed:", err); | |
| } | |
| } | |
| function updateMcpInputs() { | |
| const tool = document.getElementById('mcpToolSelect').value; | |
| const fields = document.getElementById('mcpInputFields'); | |
| if (tool === 'search_tech_jobs') { | |
| fields.innerHTML = ` | |
| <label class="form-label">domain:</label><input id="arg_domain" class="search-input" style="width:100%; margin-bottom:10px;" value="Backend Engineering"> | |
| <label class="form-label">city:</label><input id="arg_city" class="search-input" style="width:100%; margin-bottom:10px;" value="Bengaluru"> | |
| <label class="form-label">query:</label><input id="arg_query" class="search-input" style="width:100%; margin-bottom:15px;" value="Go"> | |
| `; | |
| } else { | |
| fields.innerHTML = ` | |
| <label class="form-label">target_job_id:</label><input id="arg_job_id" class="search-input" style="width:100%; margin-bottom:15px;" value="${allJobs[0]?.id || 'BLR-BACKEND-101'}"> | |
| `; | |
| } | |
| } | |
| async function executeMcpTool() { | |
| const tool = document.getElementById('mcpToolSelect').value; | |
| let args = {}; | |
| if (tool === 'search_tech_jobs') { | |
| args = { | |
| domain: document.getElementById('arg_domain').value, | |
| city: document.getElementById('arg_city').value, | |
| query: document.getElementById('arg_query').value | |
| }; | |
| } else { | |
| args = { | |
| target_job_id: document.getElementById('arg_job_id').value, | |
| resume_text: "Senior backend developer experienced in Go, Docker, AWS." | |
| }; | |
| } | |
| try { | |
| const res = await fetch('/api/mcp/execute', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ tool: tool, args: args }) | |
| }); | |
| const data = await res.json(); | |
| document.getElementById('mcpJsonOutput').innerText = JSON.stringify(JSON.parse(data.result), null, 2); | |
| } catch (err) { | |
| document.getElementById('mcpJsonOutput').innerText = "Error executing tool: " + err; | |
| } | |
| } | |
| function initCharts() { | |
| const skillsCtx = document.getElementById('skillsChart').getContext('2d'); | |
| new Chart(skillsCtx, { | |
| type: 'bar', | |
| data: { | |
| labels: ['Go', 'React', 'Kubernetes', 'Python', 'FastMCP', 'Spark'], | |
| datasets: [{ | |
| label: 'Skill Demand %', | |
| data: [85, 78, 72, 90, 65, 55], | |
| backgroundColor: 'rgba(0, 229, 255, 0.6)', | |
| borderColor: '#00e5ff', | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { legend: { display: false } }, | |
| scales: { y: { ticks: { color: '#a0aec0' } }, x: { ticks: { color: '#a0aec0' } } } | |
| } | |
| }); | |
| const hubsCtx = document.getElementById('hubsChart').getContext('2d'); | |
| new Chart(hubsCtx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Bengaluru', 'Pune', 'Hyderabad', 'Gurgaon', 'Remote'], | |
| datasets: [{ | |
| data: [40, 25, 20, 10, 5], | |
| backgroundColor: ['#00e5ff', '#9d4edd', '#e94057', '#00ff9d', '#ffcc00'] | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { legend: { labels: { color: '#a0aec0' } } } | |
| } | |
| }); | |
| } | |
| // Initialize | |
| fetchJobs(); | |
| updateMcpInputs(); | |
| </script> | |
| </body> | |
| </html> | |