Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ynov CV Analyzer - Ycampers</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #4bb7a9 0%, #3a9a8d 100%); | |
| } | |
| .progress-bar { | |
| height: 20px; | |
| border-radius: 10px; | |
| background-color: #e5e7eb; | |
| overflow: hidden; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| transition: width 0.5s ease-in-out; | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(75, 183, 169, 0.7); } | |
| 70% { box-shadow: 0 0 0 10px rgba(75, 183, 169, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(75, 183, 169, 0); } | |
| } | |
| .floating { | |
| animation: floating 3s ease-in-out infinite; | |
| } | |
| @keyframes floating { | |
| 0% { transform: translateY(0px); } | |
| 50% { transform: translateY(-10px); } | |
| 100% { transform: translateY(0px); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 font-sans"> | |
| <!-- Header --> | |
| <header class="gradient-bg text-white shadow-lg"> | |
| <div class="container mx-auto px-4 py-6 flex justify-between items-center"> | |
| <div class="flex items-center space-x-4"> | |
| <img src="https://privacy.ynov.com/img/logo_ynov_campus.svg" alt="Ynov Campus" class="h-12"> | |
| <h1 class="text-2xl font-bold">Ynov CV Analyzer</h1> | |
| </div> | |
| <div class="hidden md:block"> | |
| <span class="bg-white text-[#4bb7a9] px-4 py-2 rounded-full font-semibold">Ycampers 2025</span> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <div class="max-w-4xl mx-auto"> | |
| <!-- Upload Section --> | |
| <section class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Analyse ton CV</h2> | |
| <p class="text-gray-600 mb-6">Télécharge ton CV pour obtenir une analyse détaillée de son contenu et de sa forme, avec des conseils pour l'améliorer.</p> | |
| <div id="upload-container" class="border-2 border-dashed border-[#4bb7a9] rounded-xl p-8 text-center cursor-pointer hover:bg-gray-50 transition"> | |
| <div class="floating text-[#4bb7a9] mb-4"> | |
| <i class="fas fa-file-upload text-5xl"></i> | |
| </div> | |
| <p class="font-semibold text-gray-700">Glisse-dépose ton CV ici ou clique pour sélectionner</p> | |
| <p class="text-sm text-gray-500 mt-2">Formats supportés: PDF, DOC, DOCX (max 5MB)</p> | |
| <input type="file" id="cv-upload" class="hidden" accept=".pdf,.doc,.docx"> | |
| </div> | |
| <div id="uploading" class="hidden text-center py-8"> | |
| <div class="inline-block animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-[#4bb7a9] mb-4"></div> | |
| <p class="text-gray-700">Analyse en cours avec Deepseek AI...</p> | |
| </div> | |
| </section> | |
| <!-- Results Section --> | |
| <section id="results-section" class="hidden bg-white rounded-xl shadow-md p-6 mb-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-bold text-gray-800">Résultats de l'analyse</h2> | |
| <div id="score-display" class="flex items-center"> | |
| <div class="text-3xl font-bold mr-2">0%</div> | |
| <div class="text-sm text-gray-500">Score global</div> | |
| </div> | |
| </div> | |
| <!-- Score Card --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-gray-50 rounded-lg p-6"> | |
| <h3 class="font-semibold text-gray-700 mb-4">Détail du score</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Contenu</span> | |
| <span class="text-sm font-medium text-gray-600" id="content-score">0%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="content-fill" class="progress-fill bg-red-500" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Forme</span> | |
| <span class="text-sm font-medium text-gray-600" id="form-score">0%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="form-fill" class="progress-fill bg-red-500" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Clarté</span> | |
| <span class="text-sm font-medium text-gray-600" id="clarity-score">0%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="clarity-fill" class="progress-fill bg-red-500" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-600">Pertinence</span> | |
| <span class="text-sm font-medium text-gray-600" id="relevance-score">0%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div id="relevance-fill" class="progress-fill bg-red-500" style="width: 0%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 rounded-lg p-6"> | |
| <h3 class="font-semibold text-gray-700 mb-4">Visualisation du score</h3> | |
| <canvas id="score-chart" height="200"></canvas> | |
| </div> | |
| </div> | |
| <!-- Recommendations --> | |
| <div class="bg-[#f8fafc] border border-[#e2e8f0] rounded-lg p-6 mb-6"> | |
| <h3 class="font-semibold text-gray-700 mb-4 flex items-center"> | |
| <i class="fas fa-lightbulb text-[#4bb7a9] mr-2"></i> | |
| Recommandations pour améliorer ton CV | |
| </h3> | |
| <div id="recommendations" class="space-y-3"> | |
| <!-- Dynamically filled by JS --> | |
| </div> | |
| </div> | |
| <!-- New CV Button --> | |
| <div class="text-center"> | |
| <button id="new-cv-btn" class="bg-[#4bb7a9] hover:bg-[#3a9a8d] text-white font-medium py-2 px-6 rounded-lg transition duration-300 flex items-center mx-auto"> | |
| <i class="fas fa-redo mr-2"></i> | |
| Analyser un nouveau CV | |
| </button> | |
| </div> | |
| </section> | |
| <!-- AI Analysis --> | |
| <section id="ai-analysis" class="hidden bg-white rounded-xl shadow-md p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">Analyse approfondie par Deepseek AI</h2> | |
| <div class="bg-[#4bb7a9] bg-opacity-10 border-l-4 border-[#4bb7a9] p-4 mb-6"> | |
| <div class="flex"> | |
| <div class="flex-shrink-0"> | |
| <i class="fas fa-robot text-[#4bb7a9]"></i> | |
| </div> | |
| <div class="ml-3"> | |
| <p id="ai-feedback" class="text-sm text-gray-700"> | |
| <!-- AI feedback will be inserted here --> | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <h3 class="font-semibold text-gray-700 mb-3">Points forts</h3> | |
| <ul id="strengths" class="space-y-2"> | |
| <!-- Strengths will be inserted here --> | |
| </ul> | |
| </div> | |
| <div> | |
| <h3 class="font-semibold text-gray-700 mb-3">Points à améliorer</h3> | |
| <ul id="weaknesses" class="space-y-2"> | |
| <!-- Weaknesses will be inserted here --> | |
| </ul> | |
| </div> | |
| </div> | |
| </section> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="gradient-bg text-white py-6"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| <img src="https://privacy.ynov.com/img/logo_ynov_campus.svg" alt="Ynov Campus" class="h-8"> | |
| </div> | |
| <div class="text-center md:text-right"> | |
| <p class="text-sm">© YNOV Campus 2025 - Tous droits réservés</p> | |
| <p class="text-xs opacity-75 mt-1">Analyseur de CV pour Ycampers - Powered by Deepseek AI</p> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elements | |
| const uploadContainer = document.getElementById('upload-container'); | |
| const fileInput = document.getElementById('cv-upload'); | |
| const uploadingDiv = document.getElementById('uploading'); | |
| const resultsSection = document.getElementById('results-section'); | |
| const aiAnalysisSection = document.getElementById('ai-analysis'); | |
| const newCvBtn = document.getElementById('new-cv-btn'); | |
| // Upload handling | |
| uploadContainer.addEventListener('click', () => fileInput.click()); | |
| uploadContainer.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadContainer.classList.add('border-[#4bb7a9]', 'bg-gray-100'); | |
| }); | |
| uploadContainer.addEventListener('dragleave', () => { | |
| uploadContainer.classList.remove('border-[#4bb7a9]', 'bg-gray-100'); | |
| }); | |
| uploadContainer.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadContainer.classList.remove('border-[#4bb7a9]', 'bg-gray-100'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFileUpload(); | |
| } | |
| }); | |
| fileInput.addEventListener('change', handleFileUpload); | |
| // New CV button handler | |
| newCvBtn.addEventListener('click', resetForm); | |
| function resetForm() { | |
| // Hide results and show upload form | |
| resultsSection.classList.add('hidden'); | |
| aiAnalysisSection.classList.add('hidden'); | |
| uploadingDiv.classList.add('hidden'); | |
| uploadContainer.classList.remove('hidden'); | |
| // Reset file input | |
| fileInput.value = ''; | |
| // Reset scores display | |
| document.querySelectorAll('.progress-fill').forEach(el => { | |
| el.style.width = '0%'; | |
| el.classList.remove('bg-yellow-500', 'bg-green-500'); | |
| el.classList.add('bg-red-500'); | |
| }); | |
| document.querySelectorAll('[id$="-score"]').forEach(el => { | |
| el.textContent = '0%'; | |
| }); | |
| document.getElementById('score-display').querySelector('div:first-child').textContent = '0%'; | |
| document.getElementById('score-display').classList.remove('pulse'); | |
| // Reset recommendations and AI feedback | |
| document.getElementById('recommendations').innerHTML = ''; | |
| document.getElementById('ai-feedback').textContent = ''; | |
| document.getElementById('strengths').innerHTML = ''; | |
| document.getElementById('weaknesses').innerHTML = ''; | |
| // Destroy previous chart if exists | |
| const chartCanvas = document.getElementById('score-chart'); | |
| if (chartCanvas.chart) { | |
| chartCanvas.chart.destroy(); | |
| } | |
| } | |
| function handleFileUpload() { | |
| if (fileInput.files.length) { | |
| const file = fileInput.files[0]; | |
| const validTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; | |
| if (!validTypes.includes(file.type) && !file.name.match(/\.(pdf|doc|docx)$/)) { | |
| alert('Format de fichier non supporté. Veuillez uploader un PDF, DOC ou DOCX.'); | |
| return; | |
| } | |
| if (file.size > 5 * 1024 * 1024) { | |
| alert('Le fichier est trop volumineux (max 5MB).'); | |
| return; | |
| } | |
| // Show uploading state | |
| uploadContainer.classList.add('hidden'); | |
| uploadingDiv.classList.remove('hidden'); | |
| // Simulate API call to Deepseek AI | |
| setTimeout(() => { | |
| analyzeCV(file.name); | |
| }, 2500); | |
| } | |
| } | |
| function analyzeCV(filename) { | |
| // Hide uploading and show results | |
| uploadingDiv.classList.add('hidden'); | |
| resultsSection.classList.remove('hidden'); | |
| aiAnalysisSection.classList.remove('hidden'); | |
| // Generate random scores for demo purposes | |
| const contentScore = Math.floor(Math.random() * 30) + 50; // 50-80 | |
| const formScore = Math.floor(Math.random() * 40) + 30; // 30-70 | |
| const clarityScore = Math.floor(Math.random() * 35) + 45; // 45-80 | |
| const relevanceScore = Math.floor(Math.random() * 25) + 60; // 60-85 | |
| const overallScore = Math.round((contentScore + formScore + clarityScore + relevanceScore) / 4); | |
| // Update scores | |
| updateScore('content', contentScore); | |
| updateScore('form', formScore); | |
| updateScore('clarity', clarityScore); | |
| updateScore('relevance', relevanceScore); | |
| updateOverallScore(overallScore); | |
| // Create chart | |
| createScoreChart(contentScore, formScore, clarityScore, relevanceScore); | |
| // Generate recommendations based on scores | |
| generateRecommendations(contentScore, formScore, clarityScore, relevanceScore); | |
| // Generate AI feedback | |
| generateAIFeedback(filename, overallScore, contentScore, formScore, clarityScore, relevanceScore); | |
| } | |
| function updateScore(type, score) { | |
| const scoreElement = document.getElementById(`${type}-score`); | |
| const fillElement = document.getElementById(`${type}-fill`); | |
| scoreElement.textContent = `${score}%`; | |
| fillElement.style.width = `${score}%`; | |
| // Update color based on score | |
| if (score <= 40) { | |
| fillElement.classList.remove('bg-yellow-500', 'bg-green-500'); | |
| fillElement.classList.add('bg-red-500'); | |
| } else if (score <= 70) { | |
| fillElement.classList.remove('bg-red-500', 'bg-green-500'); | |
| fillElement.classList.add('bg-yellow-500'); | |
| } else { | |
| fillElement.classList.remove('bg-red-500', 'bg-yellow-500'); | |
| fillElement.classList.add('bg-green-500'); | |
| } | |
| } | |
| function updateOverallScore(score) { | |
| const scoreDisplay = document.getElementById('score-display'); | |
| const percentageElement = scoreDisplay.querySelector('div:first-child'); | |
| percentageElement.textContent = `${score}%`; | |
| // Pulse animation for good scores | |
| if (score > 70) { | |
| scoreDisplay.classList.add('pulse'); | |
| } else { | |
| scoreDisplay.classList.remove('pulse'); | |
| } | |
| } | |
| function createScoreChart(content, form, clarity, relevance) { | |
| const ctx = document.getElementById('score-chart').getContext('2d'); | |
| // Store chart instance on canvas element | |
| ctx.canvas.chart = new Chart(ctx, { | |
| type: 'radar', | |
| data: { | |
| labels: ['Contenu', 'Forme', 'Clarté', 'Pertinence'], | |
| datasets: [{ | |
| label: 'Score', | |
| data: [content, form, clarity, relevance], | |
| backgroundColor: 'rgba(75, 183, 169, 0.2)', | |
| borderColor: '#4bb7a9', | |
| borderWidth: 2, | |
| pointBackgroundColor: '#4bb7a9', | |
| pointRadius: 4 | |
| }] | |
| }, | |
| options: { | |
| scales: { | |
| r: { | |
| angleLines: { | |
| display: true, | |
| color: 'rgba(0, 0, 0, 0.1)' | |
| }, | |
| suggestedMin: 0, | |
| suggestedMax: 100, | |
| ticks: { | |
| stepSize: 20, | |
| backdropColor: 'transparent' | |
| }, | |
| grid: { | |
| color: 'rgba(0, 0, 0, 0.1)' | |
| }, | |
| pointLabels: { | |
| font: { | |
| size: 12, | |
| weight: 'bold' | |
| } | |
| } | |
| } | |
| }, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| elements: { | |
| line: { | |
| tension: 0.1 | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| function generateRecommendations(content, form, clarity, relevance) { | |
| const recommendationsContainer = document.getElementById('recommendations'); | |
| recommendationsContainer.innerHTML = ''; | |
| const allRecommendations = []; | |
| // Content recommendations | |
| if (content < 50) { | |
| allRecommendations.push({ | |
| icon: 'fa-file-alt', | |
| text: 'Ajoute plus de détails sur tes expériences professionnelles et formations.' | |
| }); | |
| allRecommendations.push({ | |
| icon: 'fa-tasks', | |
| text: 'Structure mieux les sections de ton CV (expériences, formations, compétences).' | |
| }); | |
| } else if (content < 70) { | |
| allRecommendations.push({ | |
| icon: 'fa-file-alt', | |
| text: 'Enrichis les descriptions de tes expériences avec des réalisations concrètes.' | |
| }); | |
| } | |
| // Form recommendations | |
| if (form < 50) { | |
| allRecommendations.push({ | |
| icon: 'fa-paint-brush', | |
| text: 'Améliore la mise en page pour une meilleure lisibilité.' | |
| }); | |
| allRecommendations.push({ | |
| icon: 'fa-font', | |
| text: 'Utilise une police professionnelle et des tailles cohérentes.' | |
| }); | |
| } else if (form < 70) { | |
| allRecommendations.push({ | |
| icon: 'fa-align-left', | |
| text: 'Soigne davantage l\'alignement et les espacements.' | |
| }); | |
| } | |
| // Clarity recommendations | |
| if (clarity < 60) { | |
| allRecommendations.push({ | |
| icon: 'fa-spell-check', | |
| text: 'Corrige les fautes d\'orthographe et de grammaire.' | |
| }); | |
| allRecommendations.push({ | |
| icon: 'fa-bullhorn', | |
| text: 'Utilise un langage plus clair et concis.' | |
| }); | |
| } | |
| // Relevance recommendations | |
| if (relevance < 60) { | |
| allRecommendations.push({ | |
| icon: 'fa-bullseye', | |
| text: 'Adapte ton CV au secteur d\'activité visé.' | |
| }); | |
| allRecommendations.push({ | |
| icon: 'fa-filter', | |
| text: 'Mets en avant les expériences les plus pertinentes.' | |
| }); | |
| } else if (relevance < 80) { | |
| allRecommendations.push({ | |
| icon: 'fa-star', | |
| text: 'Mets davantage en valeur tes compétences clés.' | |
| }); | |
| } | |
| // Default recommendations if none generated | |
| if (allRecommendations.length === 0) { | |
| allRecommendations.push({ | |
| icon: 'fa-thumbs-up', | |
| text: 'Ton CV est déjà bien construit! Pense juste à le personnaliser pour chaque candidature.' | |
| }); | |
| } | |
| // Add recommendations to DOM | |
| allRecommendations.forEach(rec => { | |
| const recElement = document.createElement('div'); | |
| recElement.className = 'flex items-start'; | |
| recElement.innerHTML = ` | |
| <i class="fas ${rec.icon} text-[#4bb7a9] mt-1 mr-3"></i> | |
| <span class="text-gray-700">${rec.text}</span> | |
| `; | |
| recommendationsContainer.appendChild(recElement); | |
| }); | |
| } | |
| function generateAIFeedback(filename, overallScore, content, form, clarity, relevance) { | |
| const feedbackElement = document.getElementById('ai-feedback'); | |
| const strengthsList = document.getElementById('strengths'); | |
| const weaknessesList = document.getElementById('weaknesses'); | |
| // Generate feedback based on scores | |
| let feedback = ''; | |
| let strengths = []; | |
| let weaknesses = []; | |
| if (overallScore > 70) { | |
| feedback = `L'analyse de ton CV "${filename}" montre un document globalement bien construit avec un score de ${overallScore}%. `; | |
| } else if (overallScore > 50) { | |
| feedback = `Ton CV "${filename}" obtient un score moyen de ${overallScore}% avec plusieurs points à améliorer. `; | |
| } else { | |
| feedback = `L'analyse de ton CV "${filename}" révèle un score faible de ${overallScore}% avec des améliorations importantes à apporter. `; | |
| } | |
| // Content feedback | |
| if (content > 70) { | |
| strengths.push('Contenu riche et détaillé'); | |
| } else if (content < 50) { | |
| weaknesses.push('Contenu trop succinct ou incomplet'); | |
| } | |
| // Form feedback | |
| if (form > 70) { | |
| strengths.push('Mise en page professionnelle'); | |
| } else if (form < 50) { | |
| weaknesses.push('Problèmes de mise en page'); | |
| } | |
| // Clarity feedback | |
| if (clarity > 70) { | |
| strengths.push('Expression claire et précise'); | |
| } else if (clarity < 50) { | |
| weaknesses.push('Problèmes de clarté ou de langue'); | |
| } | |
| // Relevance feedback | |
| if (relevance > 70) { | |
| strengths.push('Pertinence des informations par rapport au secteur'); | |
| } else if (relevance < 50) { | |
| weaknesses.push('Manque de pertinence pour le secteur visé'); | |
| } | |
| // Default if no strengths/weaknesses detected (shouldn't happen) | |
| if (strengths.length === 0) strengths.push('Motivation et potentiel'); | |
| if (weaknesses.length === 0) weaknesses.push('Aucun point faible majeur détecté'); | |
| // Complete feedback | |
| feedback += `Voici une analyse détaillée avec des pistes concrètes pour optimiser ton CV et maximiser tes chances.`; | |
| // Update DOM | |
| feedbackElement.textContent = feedback; | |
| strengthsList.innerHTML = strengths.map(str => | |
| `<li class="flex items-start"> | |
| <i class="fas fa-check-circle text-green-500 mt-1 mr-2"></i> | |
| <span>${str}</span> | |
| </li>` | |
| ).join(''); | |
| weaknessesList.innerHTML = weaknesses.map(weak => | |
| `<li class="flex items-start"> | |
| <i class="fas fa-exclamation-circle text-red-500 mt-1 mr-2"></i> | |
| <span>${weak}</span> | |
| </li>` | |
| ).join(''); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=RaphaelC76/ynov-cv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |