| <!DOCTYPE html>
|
| <html lang="fr">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Compétences ciblées, niveau maîtrisé, délai respecté — avec persistance et gestion de l’effort.</title>
|
|
|
|
|
| <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>
|
| :root {
|
| --primary-color: #3b82f6;
|
| --secondary-color: #10b981;
|
| --accent-color: #8b5cf6;
|
| --warning-color: #f59e0b;
|
| --danger-color: #ef4444;
|
| --dark-color: #1e293b;
|
| --light-color: #f8fafc;
|
| --gray-color: #64748b;
|
| }
|
|
|
| * {
|
| margin: 0;
|
| padding: 0;
|
| box-sizing: border-box;
|
| }
|
|
|
| body {
|
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
| color: var(--dark-color);
|
| line-height: 1.6;
|
| min-height: 100vh;
|
| }
|
|
|
| .container {
|
| max-width: 1400px;
|
| margin: 0 auto;
|
| padding: 20px;
|
| }
|
|
|
|
|
| .header {
|
| background: linear-gradient(135deg, var(--dark-color) 0%, #0f172a 100%);
|
| color: white;
|
| padding: 2rem;
|
| border-radius: 15px;
|
| margin-bottom: 2rem;
|
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
| position: relative;
|
| overflow: hidden;
|
| }
|
|
|
| .header::before {
|
| content: '';
|
| position: absolute;
|
| top: 0;
|
| left: 0;
|
| right: 0;
|
| height: 4px;
|
| background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
|
| }
|
|
|
| .header h1 {
|
| font-size: 2.5rem;
|
| margin-bottom: 0.5rem;
|
| display: flex;
|
| align-items: center;
|
| gap: 15px;
|
| }
|
|
|
| .header p {
|
| font-size: 1.1rem;
|
| opacity: 0.9;
|
| max-width: 800px;
|
| }
|
|
|
| .badge {
|
| background: var(--primary-color);
|
| color: white;
|
| padding: 5px 15px;
|
| border-radius: 20px;
|
| font-size: 0.9rem;
|
| display: inline-block;
|
| margin-top: 10px;
|
| }
|
|
|
|
|
| .nav-tabs {
|
| display: flex;
|
| background: white;
|
| border-radius: 12px;
|
| overflow: hidden;
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| margin-bottom: 2rem;
|
| flex-wrap: wrap;
|
| }
|
|
|
| .nav-tab {
|
| flex: 1;
|
| min-width: 200px;
|
| padding: 1.2rem;
|
| border: none;
|
| background: transparent;
|
| color: var(--gray-color);
|
| font-size: 1rem;
|
| font-weight: 600;
|
| cursor: pointer;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| gap: 10px;
|
| transition: all 0.3s ease;
|
| }
|
|
|
| .nav-tab:hover {
|
| background: #f8fafc;
|
| color: var(--primary-color);
|
| }
|
|
|
| .nav-tab.active {
|
| background: var(--primary-color);
|
| color: white;
|
| box-shadow: 0 5px 15px rgba(59, 130, 246, 0.3);
|
| }
|
|
|
|
|
| .tab-content {
|
| display: none;
|
| animation: fadeIn 0.5s ease;
|
| }
|
|
|
| .tab-content.active {
|
| display: block;
|
| }
|
|
|
| @keyframes fadeIn {
|
| from { opacity: 0; transform: translateY(10px); }
|
| to { opacity: 1; transform: translateY(0); }
|
| }
|
|
|
| .card {
|
| background: white;
|
| border-radius: 15px;
|
| padding: 2rem;
|
| margin-bottom: 2rem;
|
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
|
| border-left: 5px solid var(--primary-color);
|
| }
|
|
|
| .card-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| margin-bottom: 1.5rem;
|
| padding-bottom: 1rem;
|
| border-bottom: 2px solid #f1f5f9;
|
| }
|
|
|
| .card-header h3 {
|
| font-size: 1.5rem;
|
| color: var(--dark-color);
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| }
|
|
|
|
|
| .heatmap-container {
|
| margin-top: 2rem;
|
| }
|
|
|
| .heatmap-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .heatmap-title {
|
| font-size: 1.3rem;
|
| color: var(--dark-color);
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| }
|
|
|
| .heatmap-controls {
|
| display: flex;
|
| gap: 10px;
|
| }
|
|
|
| .heatmap-period {
|
| padding: 8px 15px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 8px;
|
| background: #f8fafc;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| }
|
|
|
| .heatmap-period:hover {
|
| border-color: var(--primary-color);
|
| }
|
|
|
| .heatmap-period.active {
|
| background: var(--primary-color);
|
| color: white;
|
| border-color: var(--primary-color);
|
| }
|
|
|
| .heatmap-grid {
|
| display: grid;
|
| grid-template-columns: auto repeat(7, 1fr);
|
| gap: 8px;
|
| margin-top: 1rem;
|
| }
|
|
|
| .heatmap-day-label {
|
| text-align: right;
|
| padding: 10px;
|
| font-weight: 600;
|
| color: var(--dark-color);
|
| }
|
|
|
| .heatmap-cell {
|
| aspect-ratio: 1;
|
| border-radius: 4px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| font-size: 0.8rem;
|
| font-weight: bold;
|
| cursor: pointer;
|
| transition: transform 0.2s;
|
| }
|
|
|
| .heatmap-cell:hover {
|
| transform: scale(1.1);
|
| z-index: 10;
|
| }
|
|
|
| .heatmap-intensity-0 { background: #f1f5f9; color: var(--gray-color); }
|
| .heatmap-intensity-1 { background: #dbeafe; color: var(--dark-color); }
|
| .heatmap-intensity-2 { background: #93c5fd; color: white; }
|
| .heatmap-intensity-3 { background: #3b82f6; color: white; }
|
| .heatmap-intensity-4 { background: #1d4ed8; color: white; }
|
|
|
| .heatmap-week-label {
|
| text-align: center;
|
| padding: 5px;
|
| font-size: 0.9rem;
|
| color: var(--gray-color);
|
| }
|
|
|
| .heatmap-legend {
|
| display: flex;
|
| justify-content: center;
|
| align-items: center;
|
| gap: 15px;
|
| margin-top: 1.5rem;
|
| padding-top: 1.5rem;
|
| border-top: 2px solid #f1f5f9;
|
| }
|
|
|
| .legend-item {
|
| display: flex;
|
| align-items: center;
|
| gap: 5px;
|
| }
|
|
|
| .legend-color {
|
| width: 20px;
|
| height: 20px;
|
| border-radius: 4px;
|
| }
|
|
|
|
|
| .time-management-stats {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| gap: 20px;
|
| margin-top: 2rem;
|
| }
|
|
|
| .time-stat-card {
|
| background: white;
|
| border-radius: 12px;
|
| padding: 1.5rem;
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| border-top: 4px solid var(--secondary-color);
|
| }
|
|
|
| .time-stat-header {
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| margin-bottom: 1rem;
|
| }
|
|
|
| .time-stat-icon {
|
| width: 40px;
|
| height: 40px;
|
| border-radius: 10px;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| font-size: 1.2rem;
|
| color: white;
|
| }
|
|
|
| .time-stat-value {
|
| font-size: 2.5rem;
|
| font-weight: bold;
|
| color: var(--secondary-color);
|
| margin: 10px 0;
|
| }
|
|
|
| .time-stat-description {
|
| color: var(--gray-color);
|
| font-size: 0.9rem;
|
| margin-top: 5px;
|
| }
|
|
|
| .progress-circle {
|
| width: 100px;
|
| height: 100px;
|
| position: relative;
|
| margin: 0 auto;
|
| }
|
|
|
| .progress-circle svg {
|
| width: 100%;
|
| height: 100%;
|
| }
|
|
|
| .progress-text {
|
| position: absolute;
|
| top: 50%;
|
| left: 50%;
|
| transform: translate(-50%, -50%);
|
| font-size: 1.5rem;
|
| font-weight: bold;
|
| color: var(--secondary-color);
|
| }
|
|
|
|
|
| .effort-pattern {
|
| background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
| border-radius: 12px;
|
| padding: 1.5rem;
|
| margin-top: 2rem;
|
| }
|
|
|
| .pattern-badge {
|
| display: inline-block;
|
| padding: 8px 15px;
|
| background: linear-gradient(135deg, #f59e0b, #d97706);
|
| color: white;
|
| border-radius: 20px;
|
| font-weight: bold;
|
| margin-bottom: 1rem;
|
| }
|
|
|
| .pattern-details {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| gap: 15px;
|
| margin-top: 1rem;
|
| }
|
|
|
| .pattern-item {
|
| background: white;
|
| padding: 1rem;
|
| border-radius: 8px;
|
| box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
|
| }
|
|
|
| .pattern-label {
|
| font-size: 0.9rem;
|
| color: var(--gray-color);
|
| margin-bottom: 5px;
|
| }
|
|
|
| .pattern-value {
|
| font-size: 1.1rem;
|
| font-weight: bold;
|
| color: var(--dark-color);
|
| }
|
|
|
|
|
| .persistence-section {
|
| background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
| border-radius: 12px;
|
| padding: 1.5rem;
|
| margin-top: 2rem;
|
| border-left: 5px solid #f59e0b;
|
| }
|
|
|
| .persistence-header {
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| margin-bottom: 1rem;
|
| }
|
|
|
| .persistence-level {
|
| font-size: 1.5rem;
|
| font-weight: bold;
|
| color: #d97706;
|
| }
|
|
|
| .persistence-details {
|
| margin-top: 1rem;
|
| }
|
|
|
| .persistence-item {
|
| display: flex;
|
| align-items: center;
|
| gap: 10px;
|
| margin-bottom: 10px;
|
| padding: 8px;
|
| background: rgba(255, 255, 255, 0.5);
|
| border-radius: 8px;
|
| }
|
|
|
|
|
| .form-group {
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .form-label {
|
| display: block;
|
| margin-bottom: 0.5rem;
|
| font-weight: 600;
|
| color: var(--dark-color);
|
| display: flex;
|
| align-items: center;
|
| gap: 8px;
|
| }
|
|
|
| .form-control {
|
| width: 100%;
|
| padding: 12px 15px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 10px;
|
| font-size: 1rem;
|
| transition: all 0.3s;
|
| background: #f8fafc;
|
| }
|
|
|
| .form-control:focus {
|
| outline: none;
|
| border-color: var(--primary-color);
|
| background: white;
|
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
| }
|
|
|
| .form-row {
|
| display: flex;
|
| gap: 20px;
|
| margin-bottom: 1.5rem;
|
| }
|
|
|
| .form-col {
|
| flex: 1;
|
| }
|
|
|
|
|
| .btn {
|
| padding: 12px 25px;
|
| border: none;
|
| border-radius: 10px;
|
| font-size: 1rem;
|
| font-weight: 600;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| display: inline-flex;
|
| align-items: center;
|
| justify-content: center;
|
| gap: 10px;
|
| }
|
|
|
| .btn-primary {
|
| background: linear-gradient(135deg, var(--primary-color) 0%, #2563eb 100%);
|
| color: white;
|
| }
|
|
|
| .btn-primary:hover {
|
| transform: translateY(-2px);
|
| box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
|
| }
|
|
|
| .btn-secondary {
|
| background: var(--secondary-color);
|
| color: white;
|
| }
|
|
|
| .btn-large {
|
| padding: 15px 30px;
|
| font-size: 1.1rem;
|
| }
|
|
|
|
|
| .flashcard-section {
|
| margin-top: 2rem;
|
| border-top: 2px solid #f1f5f9;
|
| padding-top: 2rem;
|
| }
|
|
|
| .flashcard-container {
|
| max-width: 800px;
|
| margin: 0 auto;
|
| }
|
|
|
| .flashcard {
|
| background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
| border-radius: 15px;
|
| padding: 3rem 2rem;
|
| margin: 2rem 0;
|
| min-height: 300px;
|
| display: flex;
|
| flex-direction: column;
|
| justify-content: center;
|
| align-items: center;
|
| position: relative;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
| border: 2px solid #e2e8f0;
|
| }
|
|
|
| .flashcard:hover {
|
| transform: translateY(-5px);
|
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
|
| }
|
|
|
| .flashcard.flipped {
|
| background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
| }
|
|
|
| .flashcard-front, .flashcard-back {
|
| text-align: center;
|
| width: 100%;
|
| }
|
|
|
| .flashcard-category {
|
| position: absolute;
|
| top: 15px;
|
| left: 15px;
|
| background: var(--primary-color);
|
| color: white;
|
| padding: 5px 15px;
|
| border-radius: 20px;
|
| font-size: 0.8rem;
|
| }
|
|
|
| .flashcard-content {
|
| font-size: 1.5rem;
|
| font-weight: 600;
|
| color: var(--dark-color);
|
| margin: 1.5rem 0;
|
| line-height: 1.6;
|
| }
|
|
|
| .flashcard-example {
|
| font-size: 1rem;
|
| color: var(--gray-color);
|
| font-style: italic;
|
| margin-top: 1rem;
|
| border-left: 3px solid var(--secondary-color);
|
| padding-left: 15px;
|
| }
|
|
|
| .flashcard-stats {
|
| position: absolute;
|
| top: 15px;
|
| right: 15px;
|
| display: flex;
|
| gap: 10px;
|
| align-items: center;
|
| }
|
|
|
| .flashcard-score {
|
| background: #10b981;
|
| color: white;
|
| padding: 5px 10px;
|
| border-radius: 15px;
|
| font-size: 0.8rem;
|
| font-weight: bold;
|
| }
|
|
|
| .flashcard-difficulty {
|
| background: #f59e0b;
|
| color: white;
|
| padding: 5px 10px;
|
| border-radius: 15px;
|
| font-size: 0.8rem;
|
| }
|
|
|
| .flashcard-controls {
|
| display: flex;
|
| justify-content: center;
|
| gap: 15px;
|
| margin-top: 2rem;
|
| }
|
|
|
| .flashcard-btn {
|
| padding: 10px 25px;
|
| border: none;
|
| border-radius: 10px;
|
| font-size: 1rem;
|
| font-weight: 600;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| display: flex;
|
| align-items: center;
|
| gap: 8px;
|
| }
|
|
|
| .flashcard-btn.easy {
|
| background: #10b981;
|
| color: white;
|
| }
|
|
|
| .flashcard-btn.medium {
|
| background: #f59e0b;
|
| color: white;
|
| }
|
|
|
| .flashcard-btn.hard {
|
| background: #ef4444;
|
| color: white;
|
| }
|
|
|
| .flashcard-btn:hover {
|
| transform: translateY(-2px);
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| }
|
|
|
| .flashcard-progress {
|
| margin-top: 2rem;
|
| text-align: center;
|
| }
|
|
|
| .progress-bar {
|
| height: 8px;
|
| background: #e2e8f0;
|
| border-radius: 4px;
|
| margin: 10px 0;
|
| overflow: hidden;
|
| }
|
|
|
| .progress-fill {
|
| height: 100%;
|
| background: linear-gradient(90deg, #3b82f6, #8b5cf6);
|
| border-radius: 4px;
|
| transition: width 0.3s ease;
|
| }
|
|
|
| .flashcard-stats-summary {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| gap: 15px;
|
| margin-top: 2rem;
|
| }
|
|
|
| .flashcard-stat-item {
|
| background: white;
|
| padding: 1.5rem;
|
| border-radius: 10px;
|
| text-align: center;
|
| box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
|
| }
|
|
|
|
|
| .productivity-options {
|
| display: flex;
|
| gap: 15px;
|
| margin-top: 10px;
|
| }
|
|
|
| .productivity-option {
|
| flex: 1;
|
| padding: 15px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 10px;
|
| text-align: center;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| }
|
|
|
| .productivity-option:hover {
|
| border-color: var(--primary-color);
|
| background: rgba(59, 130, 246, 0.05);
|
| }
|
|
|
| .productivity-option.selected {
|
| border-color: var(--primary-color);
|
| background: rgba(59, 130, 246, 0.1);
|
| box-shadow: 0 5px 15px rgba(59, 130, 246, 0.1);
|
| }
|
|
|
| .productivity-icon {
|
| font-size: 1.5rem;
|
| margin-bottom: 8px;
|
| display: block;
|
| }
|
|
|
|
|
| .energy-patterns {
|
| display: flex;
|
| gap: 15px;
|
| margin-top: 10px;
|
| }
|
|
|
| .energy-pattern {
|
| flex: 1;
|
| padding: 15px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 10px;
|
| text-align: center;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| }
|
|
|
| .energy-pattern:hover {
|
| border-color: var(--secondary-color);
|
| background: rgba(16, 185, 129, 0.05);
|
| }
|
|
|
| .energy-pattern.selected {
|
| border-color: var(--secondary-color);
|
| background: rgba(16, 185, 129, 0.1);
|
| box-shadow: 0 5px 15px rgba(16, 185, 129, 0.1);
|
| }
|
|
|
|
|
| .time-slots {
|
| display: flex;
|
| flex-wrap: wrap;
|
| gap: 10px;
|
| margin-top: 10px;
|
| }
|
|
|
| .time-slot {
|
| padding: 10px 20px;
|
| border: 2px solid #e2e8f0;
|
| border-radius: 8px;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| text-align: center;
|
| min-width: 80px;
|
| }
|
|
|
| .time-slot:hover {
|
| border-color: var(--accent-color);
|
| background: rgba(139, 92, 246, 0.05);
|
| }
|
|
|
| .time-slot.selected {
|
| border-color: var(--accent-color);
|
| background: rgba(139, 92, 246, 0.1);
|
| box-shadow: 0 3px 10px rgba(139, 92, 246, 0.1);
|
| }
|
|
|
|
|
| .visualization-container {
|
| background: white;
|
| border-radius: 15px;
|
| padding: 2rem;
|
| margin-top: 2rem;
|
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
|
| }
|
|
|
| .chart-container {
|
| position: relative;
|
| height: 400px;
|
| width: 100%;
|
| }
|
|
|
|
|
| .planning-grid {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
| gap: 20px;
|
| margin-top: 2rem;
|
| }
|
|
|
| .session-card {
|
| background: white;
|
| border-radius: 12px;
|
| padding: 1.5rem;
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| border-top: 4px solid var(--primary-color);
|
| transition: transform 0.3s;
|
| }
|
|
|
| .session-card:hover {
|
| transform: translateY(-5px);
|
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
| }
|
|
|
| .session-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: flex-start;
|
| margin-bottom: 1rem;
|
| }
|
|
|
| .session-date {
|
| font-size: 1.2rem;
|
| font-weight: bold;
|
| color: var(--dark-color);
|
| }
|
|
|
| .session-repetition {
|
| background: var(--primary-color);
|
| color: white;
|
| padding: 5px 15px;
|
| border-radius: 20px;
|
| font-size: 0.9rem;
|
| }
|
|
|
| .session-details {
|
| margin-top: 1rem;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 8px;
|
| }
|
|
|
| .detail-item {
|
| display: flex;
|
| justify-content: space-between;
|
| padding: 5px 0;
|
| border-bottom: 1px dashed #e2e8f0;
|
| }
|
|
|
|
|
| .notification-card {
|
| background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
| border-left: 5px solid var(--warning-color);
|
| padding: 1.5rem;
|
| border-radius: 12px;
|
| margin-bottom: 1rem;
|
| animation: slideIn 0.5s ease;
|
| }
|
|
|
| @keyframes slideIn {
|
| from { transform: translateX(-20px); opacity: 0; }
|
| to { transform: translateX(0); opacity: 1; }
|
| }
|
|
|
|
|
| .stats-grid {
|
| display: grid;
|
| grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
| gap: 20px;
|
| margin-top: 2rem;
|
| }
|
|
|
| .stat-card {
|
| background: white;
|
| border-radius: 12px;
|
| padding: 1.5rem;
|
| text-align: center;
|
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| }
|
|
|
| .stat-value {
|
| font-size: 2.5rem;
|
| font-weight: bold;
|
| color: var(--primary-color);
|
| margin: 10px 0;
|
| }
|
|
|
| .stat-label {
|
| color: var(--gray-color);
|
| font-size: 0.9rem;
|
| }
|
|
|
|
|
| .quality-indicator {
|
| display: flex;
|
| gap: 10px;
|
| margin: 15px 0;
|
| }
|
|
|
| .quality-dot {
|
| width: 25px;
|
| height: 25px;
|
| border-radius: 50%;
|
| cursor: pointer;
|
| transition: all 0.3s;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| color: white;
|
| font-weight: bold;
|
| font-size: 0.8rem;
|
| }
|
|
|
| .quality-dot:hover {
|
| transform: scale(1.2);
|
| }
|
|
|
| .quality-0 { background: linear-gradient(135deg, #ef4444, #dc2626); }
|
| .quality-1 { background: linear-gradient(135deg, #f59e0b, #d97706); }
|
| .quality-2 { background: linear-gradient(135deg, #10b981, #059669); }
|
| .quality-3 { background: linear-gradient(135deg, #3b82f6, #1d4ed8); }
|
|
|
| .quality-dot.active {
|
| box-shadow: 0 0 0 3px white, 0 0 0 6px currentColor;
|
| }
|
|
|
|
|
| .flashcard-stats-card {
|
| grid-column: span 2;
|
| background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
| }
|
|
|
| .flashcard-stats-header {
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| margin-bottom: 1rem;
|
| }
|
|
|
| .streak-counter {
|
| background: linear-gradient(135deg, #f59e0b, #d97706);
|
| color: white;
|
| padding: 5px 15px;
|
| border-radius: 20px;
|
| font-weight: bold;
|
| display: flex;
|
| align-items: center;
|
| gap: 5px;
|
| }
|
|
|
|
|
| @media (max-width: 768px) {
|
| .form-row {
|
| flex-direction: column;
|
| gap: 0;
|
| }
|
|
|
| .nav-tabs {
|
| flex-direction: column;
|
| }
|
|
|
| .planning-grid {
|
| grid-template-columns: 1fr;
|
| }
|
|
|
| .header h1 {
|
| font-size: 2rem;
|
| }
|
|
|
| .productivity-options,
|
| .energy-patterns {
|
| flex-direction: column;
|
| }
|
|
|
| .time-slots {
|
| justify-content: center;
|
| }
|
|
|
| .flashcard-controls {
|
| flex-direction: column;
|
| }
|
|
|
| .flashcard-btn {
|
| width: 100%;
|
| justify-content: center;
|
| }
|
|
|
| .flashcard-stats-summary {
|
| grid-template-columns: 1fr;
|
| }
|
|
|
| .heatmap-grid {
|
| gap: 4px;
|
| }
|
|
|
| .heatmap-cell {
|
| font-size: 0.7rem;
|
| }
|
|
|
| .time-management-stats {
|
| grid-template-columns: 1fr;
|
| }
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="container">
|
|
|
| <header class="header">
|
| <h1><i class="fas fa-graduation-cap"></i> Compagnon Pédagogique</h1>
|
| <p>Soutien à l'Apprentissage Autorégulé Durable par une Planification d'Étude Personnalisée et une Adaptation Générative de Contenu</p>
|
| <div class="badge">Version Professionnelle 2.0 | SM-2 Niveau Avancé</div>
|
| </header>
|
|
|
|
|
| <nav class="nav-tabs">
|
| <button class="nav-tab active" onclick="showTab('objectif')">
|
| <i class="fas fa-bullseye"></i> Objectif
|
| </button>
|
| <button class="nav-tab" onclick="showTab('planner')">
|
| <i class="fas fa-calendar-alt"></i> Planner SM-2
|
| </button>
|
| <button class="nav-tab" onclick="showTab('flashcards')">
|
| <i class="fas fa-layer-group"></i> Flashcards
|
| </button>
|
| <button class="nav-tab" onclick="showTab('journal')">
|
| <i class="fas fa-book"></i> Journal SRL
|
| </button>
|
| <button class="nav-tab" onclick="showTab('notifications')">
|
| <i class="fas fa-bell"></i> Notifications IA
|
| </button>
|
| <button class="nav-tab" onclick="showTab('stats')">
|
| <i class="fas fa-chart-line"></i> Statistiques
|
| </button>
|
| <button class="nav-tab" onclick="showTab('learn')">
|
| <i class="fas fa-graduation-cap"></i> Apprendre à manager l'effort
|
| </button>
|
| </nav>
|
|
|
|
|
| <section id="objectif" class="tab-content active">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-bullseye"></i> Définir mon Objectif d'Apprentissage</h3>
|
| </div>
|
|
|
| <div class="form-row">
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-language"></i> Compétence Cible</label>
|
| <select id="competence" class="form-control" onchange="previewSM2Curve()">
|
| <option value="">Sélectionnez une compétence...</option>
|
| </select>
|
| </div>
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-brain"></i> Niveau Actuel</label>
|
| <select id="level" class="form-control">
|
| <option value="0">Débutant</option>
|
| <option value="1" selected>Intermédiaire</option>
|
| <option value="2">Avancé</option>
|
| <option value="3">Expert</option>
|
| </select>
|
| </div>
|
| </div>
|
|
|
| <div class="form-row">
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-calendar-start"></i> Date de Début</label>
|
| <input type="date" id="start" class="form-control" value="2026-01-29">
|
| </div>
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-calendar-end"></i> Date de Fin</label>
|
| <input type="date" id="end" class="form-control" value="2026-12-31">
|
| </div>
|
| </div>
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-clock"></i> Durée par Session (minutes)</label>
|
| <select id="duration" class="form-control">
|
| <option value="10">10 minutes (Micro-learning)</option>
|
| <option value="20" selected>20 minutes (Optimal)</option>
|
| <option value="30">30 minutes (Approfondissement)</option>
|
| <option value="45">45 minutes (Session intensive)</option>
|
| </select>
|
| </div>
|
|
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-chart-line"></i> Méthode Productivité</label>
|
| <div class="productivity-options">
|
| <div class="productivity-option selected" onclick="selectProductivityMethod('pomodoro')">
|
| <span class="productivity-icon">🍅</span>
|
| <div>Pomodoro</div>
|
| <small>25 min travail / 5 min pause</small>
|
| </div>
|
| <div class="productivity-option" onclick="selectProductivityMethod('deepwork')">
|
| <span class="productivity-icon">⏳</span>
|
| <div>Deep Work</div>
|
| <small>90 min focus intense</small>
|
| </div>
|
| </div>
|
| <input type="hidden" id="productivity_method" value="pomodoro">
|
| </div>
|
|
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-sun"></i> Type d'Énergie</label>
|
| <div class="energy-patterns">
|
| <div class="energy-pattern selected" onclick="selectEnergyPattern('morning')">
|
| <div>🌅 Matinale</div>
|
| <small>Meilleure concentration le matin</small>
|
| </div>
|
| <div class="energy-pattern" onclick="selectEnergyPattern('evening')">
|
| <div>🌙 Vespérale</div>
|
| <small>Plus productif en soirée</small>
|
| </div>
|
| <div class="energy-pattern" onclick="selectEnergyPattern('flexible')">
|
| <div>🔄 Flexible</div>
|
| <small>Adaptable selon le jour</small>
|
| </div>
|
| </div>
|
| <input type="hidden" id="energy_pattern" value="morning">
|
| </div>
|
|
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-calendar-check"></i> Créneaux Disponibles</label>
|
| <div class="time-slots">
|
| <div class="time-slot selected" onclick="toggleTimeSlot('morning')">08:00-10:00</div>
|
| <div class="time-slot" onclick="toggleTimeSlot('late_morning')">10:00-12:00</div>
|
| <div class="time-slot" onclick="toggleTimeSlot('afternoon')">14:00-16:00</div>
|
| <div class="time-slot" onclick="toggleTimeSlot('late_afternoon')">16:00-18:00</div>
|
| <div class="time-slot" onclick="toggleTimeSlot('evening')">19:00-21:00</div>
|
| </div>
|
| <input type="hidden" id="selected_time_slots" value="morning">
|
| </div>
|
|
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-chart-line"></i> Qualité Estimée de Rétention</label>
|
| <div class="quality-indicator">
|
| <div class="quality-dot quality-0" data-quality="0" onclick="selectQuality(0)">0</div>
|
| <div class="quality-dot quality-1" data-quality="1" onclick="selectQuality(1)">1</div>
|
| <div class="quality-dot quality-2 active" data-quality="2" onclick="selectQuality(2)">2</div>
|
| <div class="quality-dot quality-3" data-quality="3" onclick="selectQuality(3)">3</div>
|
| </div>
|
| <small id="quality-description">Bon - Consolidation progressive</small>
|
| </div>
|
|
|
| <button class="btn btn-primary btn-large" onclick="generatePlanner()">
|
| <i class="fas fa-cogs"></i> Générer le Planner SM-2 Personnalisé
|
| </button>
|
|
|
| <div id="preview" style="display: none; margin-top: 2rem;">
|
| <h4><i class="fas fa-eye"></i> Aperçu de la Courbe SM-2</h4>
|
| <div class="chart-container">
|
| <canvas id="intervalChart"></canvas>
|
| </div>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="planner" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-calendar-alt"></i> Planner SM-2 Personnalisé</h3>
|
| <button class="btn btn-secondary" onclick="exportPlanner()">
|
| <i class="fas fa-download"></i> Exporter
|
| </button>
|
| </div>
|
|
|
| <div id="plannerStats" class="stats-grid"></div>
|
|
|
| <div id="plannerContent" class="planning-grid"></div>
|
|
|
| <div class="visualization-container">
|
| <h4><i class="fas fa-chart-line"></i> Visualisation des Intervalles SM-2</h4>
|
| <div class="chart-container">
|
| <canvas id="sm2Visualization"></canvas>
|
| </div>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="flashcards" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-layer-group"></i> Application Flashcards avec Répétition Espacée</h3>
|
| <div>
|
| <span class="streak-counter">
|
| <i class="fas fa-fire"></i> Série: <span id="streakCount">0</span> jours
|
| </span>
|
| </div>
|
| </div>
|
|
|
| <div class="flashcard-container">
|
| <div class="flashcard" id="flashcard" onclick="flipCard()">
|
| <div class="flashcard-category" id="flashcardCategory">Conjugaison</div>
|
| <div class="flashcard-stats">
|
| <span class="flashcard-score" id="flashcardScore">Score: 85%</span>
|
| <span class="flashcard-difficulty" id="flashcardDifficulty">Moyen</span>
|
| </div>
|
|
|
| <div class="flashcard-front" id="flashcardFront">
|
| <div class="flashcard-content" id="flashcardQuestion">
|
| Chargement des flashcards...
|
| </div>
|
| <div class="flashcard-example" id="flashcardHint">
|
| Cliquez pour voir la réponse
|
| </div>
|
| </div>
|
|
|
| <div class="flashcard-back" id="flashcardBack" style="display: none;">
|
| <div class="flashcard-content" id="flashcardAnswer">
|
| Réponse ici
|
| </div>
|
| <div class="flashcard-example" id="flashcardExplanation">
|
| Explication détaillée
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <div class="flashcard-controls">
|
| <button class="flashcard-btn hard" onclick="rateCard(0)">
|
| <i class="fas fa-times"></i> Difficile
|
| </button>
|
| <button class="flashcard-btn medium" onclick="rateCard(1)">
|
| <i class="fas fa-minus"></i> Moyen
|
| </button>
|
| <button class="flashcard-btn easy" onclick="rateCard(2)">
|
| <i class="fas fa-check"></i> Facile
|
| </button>
|
| <button class="flashcard-btn" onclick="showAnswer()" style="background: #3b82f6; color: white;">
|
| <i class="fas fa-eye"></i> Voir Réponse
|
| </button>
|
| </div>
|
|
|
| <div class="flashcard-progress">
|
| <div>Progression: <span id="currentCard">1</span>/<span id="totalCards">10</span></div>
|
| <div class="progress-bar">
|
| <div class="progress-fill" id="progressFill" style="width: 10%"></div>
|
| </div>
|
| <div id="nextReview">Prochaine révision: Aujourd'hui</div>
|
| </div>
|
|
|
| <div class="flashcard-stats-summary">
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #3b82f6; font-weight: bold;" id="totalReviewed">0</div>
|
| <div>Cartes Révisées</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #10b981; font-weight: bold;" id="masteryRate">0%</div>
|
| <div>Taux de Maîtrise</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #8b5cf6; font-weight: bold;" id="avgScore">0%</div>
|
| <div>Score Moyen</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #f59e0b; font-weight: bold;" id="cardsDue">0</div>
|
| <div>Cartes à Réviser</div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <div class="flashcard-section">
|
| <h4><i class="fas fa-plus-circle"></i> Ajouter une Nouvelle Flashcard</h4>
|
| <div class="form-row">
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-question"></i> Question</label>
|
| <textarea id="newQuestion" class="form-control" rows="2" placeholder="Entrez la question..."></textarea>
|
| </div>
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-lightbulb"></i> Réponse</label>
|
| <textarea id="newAnswer" class="form-control" rows="2" placeholder="Entrez la réponse..."></textarea>
|
| </div>
|
| </div>
|
| <div class="form-row">
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-tag"></i> Catégorie</label>
|
| <select id="newCategory" class="form-control">
|
| <option value="conjugaison">Conjugaison</option>
|
| <option value="grammaire">Grammaire</option>
|
| <option value="vocabulaire">Vocabulaire</option>
|
| <option value="expressions">Expressions</option>
|
| <option value="autres">Autres</option>
|
| </select>
|
| </div>
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-star"></i> Difficulté</label>
|
| <select id="newDifficulty" class="form-control">
|
| <option value="facile">Facile</option>
|
| <option value="moyen" selected>Moyen</option>
|
| <option value="difficile">Difficile</option>
|
| </select>
|
| </div>
|
| </div>
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-info-circle"></i> Exemple/Explication</label>
|
| <textarea id="newExplanation" class="form-control" rows="2" placeholder="Ajoutez un exemple ou une explication..."></textarea>
|
| </div>
|
| <button class="btn btn-primary" onclick="addNewFlashcard()">
|
| <i class="fas fa-plus"></i> Ajouter la Flashcard
|
| </button>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="journal" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-book"></i> Journal d'Apprentissage Autorégulé</h3>
|
| <button class="btn btn-secondary" onclick="loadJournalEntries()">
|
| <i class="fas fa-history"></i> Historique
|
| </button>
|
| </div>
|
|
|
| <div class="form-row">
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-tasks"></i> Planifié (To-Do)</label>
|
| <textarea id="todo" class="form-control" rows="3" placeholder="Ce que vous prévoyez d'apprendre..."></textarea>
|
| </div>
|
| <div class="form-col">
|
| <label class="form-label"><i class="fas fa-spinner"></i> En Cours (Doing)</label>
|
| <textarea id="doing" class="form-control" rows="3" placeholder="Ce que vous faites actuellement..."></textarea>
|
| </div>
|
| </div>
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-check-circle"></i> Accompli (Done)</label>
|
| <textarea id="done" class="form-control" rows="3" placeholder="Ce que vous avez accompli..."></textarea>
|
| </div>
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-star"></i> Auto-évaluation (1-5)</label>
|
| <div class="quality-indicator">
|
| <div class="quality-dot quality-0" onclick="setEvaluation(1)">1</div>
|
| <div class="quality-dot quality-1" onclick="setEvaluation(2)">2</div>
|
| <div class="quality-dot quality-2 active" onclick="setEvaluation(3)">3</div>
|
| <div class="quality-dot quality-3" onclick="setEvaluation(4)">4</div>
|
| <div class="quality-dot" onclick="setEvaluation(5)" style="background:linear-gradient(135deg, #8b5cf6, #7c3aed)">5</div>
|
| </div>
|
| <input type="hidden" id="auto_eval" value="3">
|
| </div>
|
|
|
| <div class="form-group">
|
| <label class="form-label"><i class="fas fa-lightbulb"></i> Stratégie Métacognitive</label>
|
| <textarea id="metacognition" class="form-control" rows="3" placeholder="Quelle stratégie avez-vous utilisée ? Que changeriez-vous ?"></textarea>
|
| </div>
|
|
|
| <button class="btn btn-primary btn-large" onclick="saveJournal()">
|
| <i class="fas fa-save"></i> Sauvegarder l'Entrée
|
| </button>
|
|
|
| <div id="journalHistory" style="display: none; margin-top: 2rem;">
|
| <h4><i class="fas fa-history"></i> Dernières Entrées</h4>
|
| <div id="journalEntries"></div>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="notifications" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-bell"></i> Notifications Intelligentes par Bandits Multi-Bras</h3>
|
| <button class="btn btn-secondary" onclick="getIntelligentReminder()">
|
| <i class="fas fa-sync"></i> Nouveau Rappel
|
| </button>
|
| </div>
|
|
|
| <div id="notificationContainer">
|
| <div class="notification-card">
|
| <p><i class="fas fa-info-circle"></i> Cliquez sur "Nouveau Rappel" pour recevoir une notification personnalisée basée sur vos habitudes d'apprentissage.</p>
|
| </div>
|
| </div>
|
|
|
| <div class="visualization-container">
|
| <h4><i class="fas fa-chart-bar"></i> Efficacité des Types de Notifications</h4>
|
| <div class="chart-container">
|
| <canvas id="notificationChart"></canvas>
|
| </div>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="stats" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-chart-line"></i> Tableau de Bord d'Apprentissage</h3>
|
| <button class="btn btn-secondary" onclick="refreshStats()">
|
| <i class="fas fa-redo"></i> Actualiser
|
| </button>
|
| </div>
|
|
|
| <div id="statsContainer" class="stats-grid"></div>
|
|
|
|
|
| <div class="heatmap-container">
|
| <div class="heatmap-header">
|
| <div class="heatmap-title">
|
| <i class="fas fa-fire"></i> Heatmap d'activité
|
| </div>
|
| <div class="heatmap-controls">
|
| <button class="heatmap-period active" onclick="changeHeatmapPeriod('month')">Mois</button>
|
| <button class="heatmap-period" onclick="changeHeatmapPeriod('quarter')">Trimestre</button>
|
| <button class="heatmap-period" onclick="changeHeatmapPeriod('year')">Année</button>
|
| </div>
|
| </div>
|
|
|
| <div id="heatmapContent">
|
|
|
| </div>
|
|
|
| <div class="heatmap-legend">
|
| <div class="legend-item">
|
| <div class="legend-color heatmap-intensity-0"></div>
|
| <span>0 min</span>
|
| </div>
|
| <div class="legend-item">
|
| <div class="legend-color heatmap-intensity-1"></div>
|
| <span>15 min</span>
|
| </div>
|
| <div class="legend-item">
|
| <div class="legend-color heatmap-intensity-2"></div>
|
| <span>30 min</span>
|
| </div>
|
| <div class="legend-item">
|
| <div class="legend-color heatmap-intensity-3"></div>
|
| <span>45 min</span>
|
| </div>
|
| <div class="legend-item">
|
| <div class="legend-color heatmap-intensity-4"></div>
|
| <span>60+ min</span>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="time-management-stats">
|
| <div class="time-stat-card">
|
| <div class="time-stat-header">
|
| <div class="time-stat-icon" style="background: linear-gradient(135deg, #10b981, #059669);">
|
| <i class="fas fa-calendar-check"></i>
|
| </div>
|
| <div>
|
| <h4>Régularité du travail</h4>
|
| <div class="time-stat-description">Constance de l'activité dans le temps</div>
|
| </div>
|
| </div>
|
| <div class="progress-circle">
|
| <svg viewBox="0 0 100 100">
|
| <circle cx="50" cy="50" r="45" fill="none" stroke="#e2e8f0" stroke-width="8"/>
|
| <circle cx="50" cy="50" r="45" fill="none" stroke="#10b981" stroke-width="8"
|
| stroke-linecap="round" stroke-dasharray="283" stroke-dashoffset="28.3"/>
|
| </svg>
|
| <div class="progress-text">90%</div>
|
| </div>
|
| </div>
|
|
|
| <div class="time-stat-card">
|
| <div class="time-stat-header">
|
| <div class="time-stat-icon" style="background: linear-gradient(135deg, #3b82f6, #1d4ed8);">
|
| <i class="fas fa-clock"></i>
|
| </div>
|
| <div>
|
| <h4>Gestion des échéances</h4>
|
| <div class="time-stat-description">Capacité à respecter les délais</div>
|
| </div>
|
| </div>
|
| <div class="progress-circle">
|
| <svg viewBox="0 0 100 100">
|
| <circle cx="50" cy="50" r="45" fill="none" stroke="#e2e8f0" stroke-width="8"/>
|
| <circle cx="50" cy="50" r="45" fill="none" stroke="#3b82f6" stroke-width="8"
|
| stroke-linecap="round" stroke-dasharray="283" stroke-dashoffset="53.77"/>
|
| </svg>
|
| <div class="progress-text">81%</div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="effort-pattern">
|
| <div class="pattern-badge">🕖 Schéma d'effort</div>
|
| <h4>Effort concentré (Pic d'activité jeudi/vendredi)</h4>
|
| <div class="time-stat-description">La dynamique temporelle de l'effort</div>
|
|
|
| <div class="pattern-details">
|
| <div class="pattern-item">
|
| <div class="pattern-label">Jour de pic</div>
|
| <div class="pattern-value">Jeudi</div>
|
| </div>
|
| <div class="pattern-item">
|
| <div class="pattern-label">Heure optimale</div>
|
| <div class="pattern-value">14h-16h</div>
|
| </div>
|
| <div class="pattern-item">
|
| <div class="pattern-label">Durée moyenne</div>
|
| <div class="pattern-value">42 min</div>
|
| </div>
|
| <div class="pattern-item">
|
| <div class="pattern-label">Fréquence</div>
|
| <div class="pattern-value">5j/semaine</div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="persistence-section">
|
| <div class="persistence-header">
|
| <div class="time-stat-icon" style="background: linear-gradient(135deg, #f59e0b, #d97706);">
|
| <i class="fas fa-mountain"></i>
|
| </div>
|
| <div>
|
| <h4>Persistance et gestion des efforts</h4>
|
| <div class="persistence-level">Très élevée</div>
|
| </div>
|
| </div>
|
| <div class="time-stat-description">Efforts constants, persévère même face aux difficultés</div>
|
|
|
| <div class="persistence-details">
|
| <div class="persistence-item">
|
| <i class="fas fa-check-circle" style="color: #10b981;"></i>
|
| <span>87% des sessions complétées sans interruption</span>
|
| </div>
|
| <div class="persistence-item">
|
| <i class="fas fa-check-circle" style="color: #10b981;"></i>
|
| <span>Maintien de la concentration pendant 92% du temps</span>
|
| </div>
|
| <div class="persistence-item">
|
| <i class="fas fa-check-circle" style="color: #10b981;"></i>
|
| <span>Récupération rapide après les difficultés</span>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <div class="visualization-container">
|
| <h4><i class="fas fa-chart-area"></i> Progression de la Rétention</h4>
|
| <div class="chart-container">
|
| <canvas id="retentionChart"></canvas>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="flashcard-section">
|
| <h4><i class="fas fa-layer-group"></i> Statistiques des Flashcards</h4>
|
| <div class="flashcard-stats-summary">
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #3b82f6; font-weight: bold;" id="statsTotalCards">0</div>
|
| <div>Total Flashcards</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #10b981; font-weight: bold;" id="statsMastered">0</div>
|
| <div>Maîtrisées</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #f59e0b; font-weight: bold;" id="statsLearning">0</div>
|
| <div>En Apprentissage</div>
|
| </div>
|
| <div class="flashcard-stat-item">
|
| <div style="font-size: 2rem; color: #ef4444; font-weight: bold;" id="statsDue">0</div>
|
| <div>À Réviser</div>
|
| </div>
|
| </div>
|
|
|
| <div class="chart-container" style="height: 300px; margin-top: 2rem;">
|
| <canvas id="flashcardProgressChart"></canvas>
|
| </div>
|
| </div>
|
| </div>
|
| </section>
|
|
|
|
|
| <section id="learn" class="tab-content">
|
| <div class="card">
|
| <div class="card-header">
|
| <h3><i class="fas fa-graduation-cap"></i> Apprendre l'Algorithme SM-2</h3>
|
| </div>
|
|
|
| <div id="sm2Explanation"></div>
|
|
|
| <div class="visualization-container">
|
| <h4><i class="fas fa-chart-network"></i> Comparaison des Niveaux de Qualité SM-2</h4>
|
| <div class="chart-container">
|
| <canvas id="comparisonChart"></canvas>
|
| </div>
|
| </div>
|
| </div>
|
| </section>
|
| </div>
|
|
|
| <script>
|
|
|
| let currentChart = null;
|
| let currentQuality = 2;
|
| let previewChart = null;
|
| let selectedTimeSlots = ['morning'];
|
|
|
|
|
| let flashcards = [];
|
| let currentFlashcardIndex = 0;
|
| let flashcardStats = {
|
| totalReviewed: 0,
|
| masteryRate: 0,
|
| avgScore: 0,
|
| streak: 0,
|
| totalCards: 0,
|
| mastered: 0,
|
| learning: 0,
|
| due: 0
|
| };
|
|
|
|
|
| let heatmapData = [];
|
| let currentHeatmapPeriod = 'month';
|
|
|
|
|
| document.addEventListener('DOMContentLoaded', function() {
|
| loadCompetences();
|
| refreshStats();
|
| loadSM2Explanation();
|
| updateComparisonChart();
|
| initializeHabitsProductivity();
|
| loadFlashcards();
|
| updateFlashcardStats();
|
| generateHeatmap();
|
| });
|
|
|
|
|
| function showTab(tabId) {
|
|
|
| document.querySelectorAll('.tab-content').forEach(section => {
|
| section.classList.remove('active');
|
| });
|
|
|
|
|
| document.querySelectorAll('.nav-tab').forEach(tab => {
|
| tab.classList.remove('active');
|
| });
|
|
|
|
|
| document.getElementById(tabId).classList.add('active');
|
|
|
|
|
| const tabNames = {
|
| 'objectif': 'Objectif',
|
| 'planner': 'Planner',
|
| 'flashcards': 'Flashcards',
|
| 'journal': 'Journal',
|
| 'notifications': 'Notifications',
|
| 'stats': 'Statistiques',
|
| 'learn': 'Apprendre'
|
| };
|
|
|
| document.querySelectorAll('.nav-tab').forEach(tab => {
|
| if (tab.textContent.includes(tabNames[tabId])) {
|
| tab.classList.add('active');
|
| }
|
| });
|
|
|
|
|
| if (tabId === 'stats') {
|
| refreshStats();
|
| generateHeatmap();
|
| } else if (tabId === 'learn') {
|
| updateComparisonChart();
|
| } else if (tabId === 'flashcards') {
|
| loadFlashcards();
|
| }
|
| }
|
|
|
|
|
| function initializeHabitsProductivity() {
|
|
|
| document.getElementById('productivity_method').value = 'pomodoro';
|
|
|
|
|
| document.getElementById('energy_pattern').value = 'morning';
|
|
|
|
|
| document.getElementById('selected_time_slots').value = 'morning';
|
| }
|
|
|
|
|
| function selectProductivityMethod(method) {
|
| document.getElementById('productivity_method').value = method;
|
|
|
|
|
| document.querySelectorAll('.productivity-option').forEach(option => {
|
| option.classList.remove('selected');
|
| });
|
|
|
| document.querySelectorAll('.productivity-option').forEach(option => {
|
| if (option.textContent.includes(method === 'pomodoro' ? 'Pomodoro' : 'Deep Work')) {
|
| option.classList.add('selected');
|
| }
|
| });
|
| }
|
|
|
|
|
| function selectEnergyPattern(pattern) {
|
| document.getElementById('energy_pattern').value = pattern;
|
|
|
|
|
| document.querySelectorAll('.energy-pattern').forEach(el => {
|
| el.classList.remove('selected');
|
| });
|
|
|
| document.querySelectorAll('.energy-pattern').forEach(el => {
|
| if (el.textContent.includes(
|
| pattern === 'morning' ? 'Matinale' :
|
| pattern === 'evening' ? 'Vespérale' : 'Flexible'
|
| )) {
|
| el.classList.add('selected');
|
| }
|
| });
|
| }
|
|
|
|
|
| function toggleTimeSlot(slot) {
|
| const index = selectedTimeSlots.indexOf(slot);
|
| const element = document.querySelector(`.time-slot:nth-child(${getTimeSlotIndex(slot) + 1})`);
|
|
|
| if (index === -1) {
|
| selectedTimeSlots.push(slot);
|
| element.classList.add('selected');
|
| } else {
|
| selectedTimeSlots.splice(index, 1);
|
| element.classList.remove('selected');
|
| }
|
|
|
| document.getElementById('selected_time_slots').value = selectedTimeSlots.join(',');
|
| }
|
|
|
|
|
| function getTimeSlotIndex(slot) {
|
| const slots = ['morning', 'late_morning', 'afternoon', 'late_afternoon', 'evening'];
|
| return slots.indexOf(slot);
|
| }
|
|
|
|
|
| async function loadCompetences() {
|
| try {
|
| const response = await fetch('/api/competences');
|
| const competences = await response.json();
|
|
|
| const select = document.getElementById('competence');
|
| select.innerHTML = '<option value="">Sélectionnez une compétence...</option>';
|
|
|
| competences.forEach(comp => {
|
| const option = document.createElement('option');
|
| option.value = comp.label;
|
| option.textContent = `${comp.id} - ${comp.label} (${comp.category})`;
|
| select.appendChild(option);
|
| });
|
| } catch (error) {
|
| console.error('Erreur chargement compétences:', error);
|
| }
|
| }
|
|
|
|
|
| function selectQuality(level) {
|
| currentQuality = level;
|
|
|
|
|
| document.querySelectorAll('.quality-dot').forEach(dot => {
|
| dot.classList.remove('active');
|
| });
|
|
|
| document.querySelector(`.quality-dot[data-quality="${level}"]`).classList.add('active');
|
|
|
|
|
| const descriptions = [
|
| "Difficulté - Révision fréquente nécessaire",
|
| "Moyen - Intervalles standard recommandés",
|
| "Bon - Consolidation progressive",
|
| "Excellent - Rétention à long terme optimale"
|
| ];
|
|
|
| document.getElementById('quality-description').textContent = descriptions[level];
|
|
|
|
|
| previewSM2Curve();
|
| }
|
|
|
|
|
| async function previewSM2Curve() {
|
| const competence = document.getElementById('competence').value;
|
| if (!competence) {
|
| document.getElementById('preview').style.display = 'none';
|
| return;
|
| }
|
|
|
| try {
|
| const response = await fetch(`/api/planner/visualize?competence=${encodeURIComponent(competence)}&quality=${currentQuality}`);
|
| const data = await response.json();
|
|
|
| if (data.success) {
|
| document.getElementById('preview').style.display = 'block';
|
|
|
| const ctx = document.getElementById('intervalChart').getContext('2d');
|
|
|
| if (previewChart) {
|
| previewChart.destroy();
|
| }
|
|
|
| const labels = data.intervals.map((_, i) => `Répétition ${i + 1}`);
|
|
|
| previewChart = new Chart(ctx, {
|
| type: 'bar',
|
| data: {
|
| labels: labels,
|
| datasets: [{
|
| label: 'Intervalle (jours)',
|
| data: data.intervals,
|
| backgroundColor: '#3b82f6',
|
| borderColor: '#2563eb',
|
| borderWidth: 2
|
| }]
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| plugins: {
|
| title: {
|
| display: true,
|
| text: `Courbe SM-2 - Niveau ${currentQuality}`
|
| }
|
| },
|
| scales: {
|
| y: {
|
| beginAtZero: true,
|
| title: {
|
| display: true,
|
| text: 'Jours'
|
| }
|
| }
|
| }
|
| }
|
| });
|
| }
|
| } catch (error) {
|
| console.error('Erreur génération preview:', error);
|
| }
|
| }
|
|
|
|
|
| async function generatePlanner() {
|
| const competence = document.getElementById('competence').value;
|
| const startDate = document.getElementById('start').value;
|
| const duration = document.getElementById('duration').value;
|
| const productivityMethod = document.getElementById('productivity_method').value;
|
| const energyPattern = document.getElementById('energy_pattern').value;
|
| const timeSlots = document.getElementById('selected_time_slots').value;
|
|
|
| if (!competence || !startDate) {
|
| alert('Veuillez sélectionner une compétence et une date de début');
|
| return;
|
| }
|
|
|
| try {
|
| const response = await fetch('/api/planner/generate', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({
|
| competence: competence,
|
| start_date: startDate,
|
| session_duration: duration,
|
| repetitions: 6,
|
| quality_level: currentQuality,
|
| productivity_method: productivityMethod,
|
| energy_pattern: energyPattern,
|
| time_slots: timeSlots
|
| })
|
| });
|
|
|
| const data = await response.json();
|
|
|
| if (data.success) {
|
| displayPlanner(data.plan, data.intervals);
|
| showTab('planner');
|
| } else {
|
| alert('Erreur: ' + data.error);
|
| }
|
| } catch (error) {
|
| console.error('Erreur génération planner:', error);
|
| alert('Erreur lors de la génération du planner');
|
| }
|
| }
|
|
|
|
|
| function displayPlanner(plan, intervals) {
|
| const container = document.getElementById('plannerContent');
|
| const statsContainer = document.getElementById('plannerStats');
|
|
|
|
|
| const totalDays = intervals.reduce((a, b) => a + b, 0);
|
|
|
|
|
| statsContainer.innerHTML = `
|
| <div class="stat-card">
|
| <div class="stat-value">${plan.length}</div>
|
| <div class="stat-label">Sessions Planifiées</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${totalDays}</div>
|
| <div class="stat-label">Jours Totaux</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${currentQuality + 1}/4</div>
|
| <div class="stat-label">Niveau de Qualité</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${document.getElementById('duration').value}min</div>
|
| <div class="stat-label">Durée/Session</div>
|
| </div>
|
| `;
|
|
|
|
|
| container.innerHTML = '';
|
| plan.forEach(session => {
|
| const card = document.createElement('div');
|
| card.className = 'session-card';
|
| card.innerHTML = `
|
| <div class="session-header">
|
| <div class="session-date">
|
| <i class="fas fa-calendar-day"></i> ${session.date}
|
| <div style="font-size: 0.9rem; color: #64748b;">${session.jour}</div>
|
| </div>
|
| <div class="session-repetition">
|
| R${session.repetition}
|
| </div>
|
| </div>
|
| <h4 style="margin: 10px 0; color: #1e293b;">${session.competence}</h4>
|
| <div class="session-details">
|
| <div class="detail-item">
|
| <span><i class="fas fa-clock"></i> Durée</span>
|
| <span>${session.duree}</span>
|
| </div>
|
| <div class="detail-item">
|
| <span><i class="fas fa-sync-alt"></i> Intervalle</span>
|
| <span>${session.intervalle}</span>
|
| </div>
|
| <div class="detail-item">
|
| <span><i class="fas fa-bullseye"></i> Type</span>
|
| <span>${session.type}</span>
|
| </div>
|
| <div class="detail-item">
|
| <span><i class="fas fa-lightbulb"></i> Stratégie</span>
|
| <span>${session.strategie}</span>
|
| </div>
|
| ${session.productivity_method ? `
|
| <div class="detail-item">
|
| <span><i class="fas fa-chart-line"></i> Productivité</span>
|
| <span>${session.productivity_method === 'pomodoro' ? '🍅 Pomodoro' : '⏳ Deep Work'}</span>
|
| </div>
|
| ` : ''}
|
| ${session.optimal_time ? `
|
| <div class="detail-item">
|
| <span><i class="fas fa-sun"></i> Heure Optimale</span>
|
| <span>${session.optimal_time}</span>
|
| </div>
|
| ` : ''}
|
| </div>
|
| <div style="margin-top: 15px; padding: 10px; background: #f1f5f9; border-radius: 8px; font-size: 0.9rem;">
|
| <i class="fas fa-bell"></i> ${session.reminder || ''}
|
| </div>
|
| `;
|
| container.appendChild(card);
|
| });
|
|
|
|
|
| createSM2Visualization(intervals, plan[0]?.competence || 'Compétence');
|
| }
|
|
|
|
|
| function createSM2Visualization(intervals, competence) {
|
| const ctx = document.getElementById('sm2Visualization').getContext('2d');
|
|
|
| if (currentChart) {
|
| currentChart.destroy();
|
| }
|
|
|
| const labels = intervals.map((_, i) => `Répétition ${i + 1}`);
|
|
|
| currentChart = new Chart(ctx, {
|
| type: 'line',
|
| data: {
|
| labels: labels,
|
| datasets: [{
|
| label: 'Intervalle (jours)',
|
| data: intervals,
|
| borderColor: '#3b82f6',
|
| backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
| borderWidth: 3,
|
| fill: true,
|
| tension: 0.4
|
| }]
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| plugins: {
|
| title: {
|
| display: true,
|
| text: `Courbe de Répétition Espacée - ${competence}`,
|
| font: { size: 16 }
|
| },
|
| tooltip: {
|
| callbacks: {
|
| label: function(context) {
|
| return `Intervalle: ${context.raw} jours`;
|
| }
|
| }
|
| }
|
| },
|
| scales: {
|
| y: {
|
| beginAtZero: true,
|
| title: {
|
| display: true,
|
| text: 'Jours'
|
| }
|
| },
|
| x: {
|
| title: {
|
| display: true,
|
| text: 'Numéro de Répétition'
|
| }
|
| }
|
| }
|
| }
|
| });
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| function generateHeatmap() {
|
| const container = document.getElementById('heatmapContent');
|
|
|
|
|
| heatmapData = generateDemoHeatmapData(currentHeatmapPeriod);
|
|
|
|
|
| let html = '<div class="heatmap-grid">';
|
|
|
|
|
| const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'];
|
| html += '<div class="heatmap-day-label"></div>';
|
| days.forEach(day => {
|
| html += `<div class="heatmap-day-label">${day}</div>`;
|
| });
|
|
|
|
|
| heatmapData.forEach((week, weekIndex) => {
|
| html += `<div class="heatmap-week-label">S${weekIndex + 1}</div>`;
|
|
|
| days.forEach((day, dayIndex) => {
|
| const activity = week[dayIndex];
|
| if (activity !== null) {
|
| const intensity = getIntensityLevel(activity);
|
| const tooltip = `Jour: ${day}, Semaine ${weekIndex + 1}\nActivité: ${activity} minutes`;
|
| html += `
|
| <div class="heatmap-cell heatmap-intensity-${intensity}"
|
| title="${tooltip}"
|
| onclick="showDayDetails(${weekIndex}, ${dayIndex}, ${activity})">
|
| ${activity}
|
| </div>
|
| `;
|
| } else {
|
| html += '<div class="heatmap-cell" style="visibility: hidden;"></div>';
|
| }
|
| });
|
| });
|
|
|
| html += '</div>';
|
| container.innerHTML = html;
|
| }
|
|
|
|
|
| function generateDemoHeatmapData(period) {
|
| const data = [];
|
| let weeks = 4;
|
|
|
| if (period === 'quarter') weeks = 13;
|
| if (period === 'year') weeks = 52;
|
|
|
|
|
| for (let week = 0; week < weeks; week++) {
|
| const weekData = [];
|
| for (let day = 0; day < 7; day++) {
|
|
|
| let activity = 0;
|
|
|
|
|
| if (day === 3 || day === 4) {
|
|
|
| activity = Math.floor(Math.random() * 45) + 30;
|
| } else if (day === 0 || day === 6) {
|
|
|
| activity = Math.floor(Math.random() * 30) + 15;
|
| } else {
|
|
|
| activity = Math.floor(Math.random() * 30);
|
| }
|
|
|
|
|
| if (week % 4 === 0) {
|
|
|
| activity = Math.min(activity * 1.3, 90);
|
| } else if (week % 4 === 2) {
|
|
|
| activity = Math.max(activity * 0.7, 10);
|
| }
|
|
|
|
|
| activity = Math.round(activity);
|
|
|
| weekData.push(activity);
|
| }
|
| data.push(weekData);
|
| }
|
|
|
| return data;
|
| }
|
|
|
|
|
| function getIntensityLevel(activity) {
|
| if (activity === 0) return 0;
|
| if (activity <= 15) return 1;
|
| if (activity <= 30) return 2;
|
| if (activity <= 45) return 3;
|
| return 4;
|
| }
|
|
|
|
|
| function changeHeatmapPeriod(period) {
|
| currentHeatmapPeriod = period;
|
|
|
|
|
| document.querySelectorAll('.heatmap-period').forEach(btn => {
|
| btn.classList.remove('active');
|
| if (btn.textContent.toLowerCase().includes(period)) {
|
| btn.classList.add('active');
|
| }
|
| });
|
|
|
|
|
| generateHeatmap();
|
| }
|
|
|
|
|
| function showDayDetails(weekIndex, dayIndex, activity) {
|
| const days = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'];
|
| const dayName = days[dayIndex];
|
|
|
| let evaluation = '';
|
| if (activity >= 60) {
|
| evaluation = '🌋 Activité intense';
|
| } else if (activity >= 45) {
|
| evaluation = '🔥 Très productive';
|
| } else if (activity >= 30) {
|
| evaluation = '💪 Productive';
|
| } else if (activity >= 15) {
|
| evaluation = '🌱 Régulière';
|
| } else if (activity > 0) {
|
| evaluation = '🌼 Légère';
|
| } else {
|
| evaluation = '⏸️ Repos';
|
| }
|
|
|
| alert(`📅 ${dayName}, Semaine ${weekIndex + 1}\n⏱️ Temps d'étude: ${activity} minutes\n📊 Évaluation: ${evaluation}`);
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| async function loadFlashcards() {
|
| try {
|
| const response = await fetch('/api/flashcards');
|
| flashcards = await response.json();
|
|
|
| if (flashcards.length > 0) {
|
| currentFlashcardIndex = 0;
|
| displayFlashcard(currentFlashcardIndex);
|
| updateFlashcardStats();
|
| } else {
|
|
|
| flashcards = [
|
| {
|
| id: 1,
|
| question: "Je (être) content de te voir.",
|
| answer: "suis",
|
| explanation: "'Être' au présent: je suis, tu es, il est, nous sommes, vous êtes, ils sont",
|
| category: "conjugaison",
|
| difficulty: "moyen",
|
| score: 85,
|
| nextReview: new Date(),
|
| interval: 1,
|
| repetitions: 3
|
| },
|
| {
|
| id: 2,
|
| question: "Nous (aller) au marché demain.",
|
| answer: "allons",
|
| explanation: "'Aller' au présent: je vais, tu vas, il va, nous allons, vous allez, ils vont",
|
| category: "conjugaison",
|
| difficulty: "facile",
|
| score: 92,
|
| nextReview: new Date(),
|
| interval: 3,
|
| repetitions: 5
|
| },
|
| {
|
| id: 3,
|
| question: "Elle (faire) ses devoirs chaque soir.",
|
| answer: "fait",
|
| explanation: "'Faire' au présent: je fais, tu fais, il fait, nous faisons, vous faites, ils font",
|
| category: "conjugaison",
|
| difficulty: "moyen",
|
| score: 78,
|
| nextReview: new Date(Date.now() + 86400000),
|
| interval: 2,
|
| repetitions: 2
|
| }
|
| ];
|
| displayFlashcard(currentFlashcardIndex);
|
| updateFlashcardStats();
|
| }
|
| } catch (error) {
|
| console.error('Erreur chargement flashcards:', error);
|
|
|
| flashcards = [
|
| {
|
| id: 1,
|
| question: "Je (être) content de te voir.",
|
| answer: "suis",
|
| explanation: "'Être' au présent: je suis, tu es, il est, nous sommes, vous êtes, ils sont",
|
| category: "conjugaison",
|
| difficulty: "moyen",
|
| score: 85,
|
| nextReview: new Date(),
|
| interval: 1,
|
| repetitions: 3
|
| }
|
| ];
|
| displayFlashcard(currentFlashcardIndex);
|
| updateFlashcardStats();
|
| }
|
| }
|
|
|
|
|
| function displayFlashcard(index) {
|
| if (flashcards.length === 0) return;
|
|
|
| const card = flashcards[index];
|
| document.getElementById('flashcardQuestion').textContent = card.question;
|
| document.getElementById('flashcardAnswer').textContent = card.answer;
|
| document.getElementById('flashcardExplanation').textContent = card.explanation;
|
| document.getElementById('flashcardCategory').textContent = card.category.charAt(0).toUpperCase() + card.category.slice(1);
|
| document.getElementById('flashcardScore').textContent = `Score: ${card.score}%`;
|
| document.getElementById('flashcardDifficulty').textContent = card.difficulty.charAt(0).toUpperCase() + card.difficulty.slice(1);
|
|
|
|
|
| document.getElementById('currentCard').textContent = index + 1;
|
| document.getElementById('totalCards').textContent = flashcards.length;
|
| document.getElementById('progressFill').style.width = `${((index + 1) / flashcards.length) * 100}%`;
|
|
|
|
|
| const nextReview = new Date(card.nextReview);
|
| const today = new Date();
|
| const diffTime = nextReview - today;
|
| const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
| if (diffDays <= 0) {
|
| document.getElementById('nextReview').textContent = "Prochaine révision: Aujourd'hui";
|
| } else if (diffDays === 1) {
|
| document.getElementById('nextReview').textContent = "Prochaine révision: Demain";
|
| } else {
|
| document.getElementById('nextReview').textContent = `Prochaine révision: Dans ${diffDays} jours`;
|
| }
|
|
|
|
|
| document.getElementById('flashcardFront').style.display = 'block';
|
| document.getElementById('flashcardBack').style.display = 'none';
|
| document.getElementById('flashcard').classList.remove('flipped');
|
| }
|
|
|
|
|
| function flipCard() {
|
| const front = document.getElementById('flashcardFront');
|
| const back = document.getElementById('flashcardBack');
|
| const card = document.getElementById('flashcard');
|
|
|
| if (front.style.display !== 'none') {
|
| front.style.display = 'none';
|
| back.style.display = 'block';
|
| card.classList.add('flipped');
|
| } else {
|
| front.style.display = 'block';
|
| back.style.display = 'none';
|
| card.classList.remove('flipped');
|
| }
|
| }
|
|
|
|
|
| function showAnswer() {
|
| document.getElementById('flashcardFront').style.display = 'none';
|
| document.getElementById('flashcardBack').style.display = 'block';
|
| document.getElementById('flashcard').classList.add('flipped');
|
| }
|
|
|
|
|
| function rateCard(rating) {
|
| if (flashcards.length === 0) return;
|
|
|
| const card = flashcards[currentFlashcardIndex];
|
|
|
|
|
| let scoreChange = 0;
|
| switch(rating) {
|
| case 0:
|
| scoreChange = -10;
|
| card.interval = Math.max(1, card.interval - 1);
|
| break;
|
| case 1:
|
| scoreChange = 5;
|
| break;
|
| case 2:
|
| scoreChange = 15;
|
| card.interval = Math.min(30, card.interval + 2);
|
| break;
|
| }
|
|
|
|
|
| card.score = Math.max(0, Math.min(100, card.score + scoreChange));
|
|
|
|
|
| card.repetitions++;
|
| card.nextReview = new Date(Date.now() + card.interval * 24 * 60 * 60 * 1000);
|
|
|
|
|
| flashcardStats.totalReviewed++;
|
|
|
|
|
| const mastered = flashcards.filter(c => c.score >= 80).length;
|
| flashcardStats.masteryRate = Math.round((mastered / flashcards.length) * 100);
|
|
|
|
|
| const totalScore = flashcards.reduce((sum, c) => sum + c.score, 0);
|
| flashcardStats.avgScore = Math.round(totalScore / flashcards.length);
|
|
|
|
|
| if (rating >= 1) {
|
| flashcardStats.streak++;
|
| } else {
|
| flashcardStats.streak = 0;
|
| }
|
|
|
|
|
| updateFlashcardStatsDisplay();
|
|
|
|
|
| nextCard();
|
| }
|
|
|
|
|
| function nextCard() {
|
| if (flashcards.length === 0) return;
|
|
|
| currentFlashcardIndex = (currentFlashcardIndex + 1) % flashcards.length;
|
| displayFlashcard(currentFlashcardIndex);
|
| }
|
|
|
|
|
| function updateFlashcardStats() {
|
| if (flashcards.length === 0) {
|
| flashcardStats = {
|
| totalReviewed: 0,
|
| masteryRate: 0,
|
| avgScore: 0,
|
| streak: 0,
|
| totalCards: 0,
|
| mastered: 0,
|
| learning: 0,
|
| due: 0
|
| };
|
| } else {
|
| const today = new Date();
|
| const mastered = flashcards.filter(c => c.score >= 80).length;
|
| const due = flashcards.filter(c => new Date(c.nextReview) <= today).length;
|
| const totalScore = flashcards.reduce((sum, c) => sum + c.score, 0);
|
|
|
| flashcardStats = {
|
| totalReviewed: flashcards.reduce((sum, c) => sum + c.repetitions, 0),
|
| masteryRate: Math.round((mastered / flashcards.length) * 100),
|
| avgScore: Math.round(totalScore / flashcards.length),
|
| streak: flashcardStats.streak,
|
| totalCards: flashcards.length,
|
| mastered: mastered,
|
| learning: flashcards.length - mastered,
|
| due: due
|
| };
|
| }
|
|
|
| updateFlashcardStatsDisplay();
|
| updateFlashcardProgressChart();
|
| }
|
|
|
|
|
| function updateFlashcardStatsDisplay() {
|
| document.getElementById('totalReviewed').textContent = flashcardStats.totalReviewed;
|
| document.getElementById('masteryRate').textContent = flashcardStats.masteryRate + '%';
|
| document.getElementById('avgScore').textContent = flashcardStats.avgScore + '%';
|
| document.getElementById('cardsDue').textContent = flashcardStats.due;
|
| document.getElementById('streakCount').textContent = flashcardStats.streak;
|
|
|
|
|
| document.getElementById('statsTotalCards').textContent = flashcardStats.totalCards;
|
| document.getElementById('statsMastered').textContent = flashcardStats.mastered;
|
| document.getElementById('statsLearning').textContent = flashcardStats.learning;
|
| document.getElementById('statsDue').textContent = flashcardStats.due;
|
| }
|
|
|
|
|
| function addNewFlashcard() {
|
| const question = document.getElementById('newQuestion').value;
|
| const answer = document.getElementById('newAnswer').value;
|
| const category = document.getElementById('newCategory').value;
|
| const difficulty = document.getElementById('newDifficulty').value;
|
| const explanation = document.getElementById('newExplanation').value;
|
|
|
| if (!question || !answer) {
|
| alert('Veuillez entrer une question et une réponse');
|
| return;
|
| }
|
|
|
| const newCard = {
|
| id: flashcards.length + 1,
|
| question: question,
|
| answer: answer,
|
| explanation: explanation || 'Pas d\'explication supplémentaire',
|
| category: category,
|
| difficulty: difficulty,
|
| score: 50,
|
| nextReview: new Date(),
|
| interval: 1,
|
| repetitions: 0
|
| };
|
|
|
| flashcards.push(newCard);
|
|
|
|
|
| document.getElementById('newQuestion').value = '';
|
| document.getElementById('newAnswer').value = '';
|
| document.getElementById('newExplanation').value = '';
|
|
|
|
|
| updateFlashcardStats();
|
|
|
|
|
| currentFlashcardIndex = flashcards.length - 1;
|
| displayFlashcard(currentFlashcardIndex);
|
|
|
| alert('Flashcard ajoutée avec succès !');
|
| }
|
|
|
|
|
| function updateFlashcardProgressChart() {
|
| const ctx = document.getElementById('flashcardProgressChart').getContext('2d');
|
|
|
| if (window.flashcardProgressChart) {
|
| window.flashcardProgressChart.destroy();
|
| }
|
|
|
| const data = {
|
| labels: ['Maîtrisées', 'En Apprentissage', 'À Réviser'],
|
| datasets: [{
|
| data: [flashcardStats.mastered, flashcardStats.learning, flashcardStats.due],
|
| backgroundColor: ['#10b981', '#f59e0b', '#ef4444'],
|
| borderWidth: 2,
|
| borderColor: 'white'
|
| }]
|
| };
|
|
|
| window.flashcardProgressChart = new Chart(ctx, {
|
| type: 'doughnut',
|
| data: data,
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| plugins: {
|
| legend: {
|
| position: 'bottom'
|
| },
|
| title: {
|
| display: true,
|
| text: 'Distribution des Flashcards',
|
| font: { size: 16 }
|
| }
|
| },
|
| cutout: '60%'
|
| }
|
| });
|
| }
|
|
|
|
|
| function setEvaluation(score) {
|
| document.getElementById('auto_eval').value = score;
|
|
|
|
|
| document.querySelectorAll('.quality-indicator .quality-dot').forEach((dot, index) => {
|
| dot.classList.toggle('active', index === score - 1);
|
| });
|
| }
|
|
|
| async function saveJournal() {
|
| const entry = {
|
| todo: document.getElementById('todo').value,
|
| doing: document.getElementById('doing').value,
|
| done: document.getElementById('done').value,
|
| metacognition: document.getElementById('metacognition').value,
|
| auto_eval: document.getElementById('auto_eval').value,
|
| competence: document.getElementById('competence').value,
|
| timestamp: new Date().toISOString()
|
| };
|
|
|
| try {
|
| const response = await fetch('/api/journal', {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify(entry)
|
| });
|
|
|
| const result = await response.json();
|
|
|
| if (result.success) {
|
| alert('✅ Journal sauvegardé avec succès !');
|
| clearJournalForm();
|
| } else {
|
| alert('Erreur: ' + result.error);
|
| }
|
| } catch (error) {
|
| console.error('Erreur sauvegarde journal:', error);
|
| alert('Erreur lors de la sauvegarde');
|
| }
|
| }
|
|
|
| function clearJournalForm() {
|
| document.getElementById('todo').value = '';
|
| document.getElementById('doing').value = '';
|
| document.getElementById('done').value = '';
|
| document.getElementById('metacognition').value = '';
|
| setEvaluation(3);
|
| }
|
|
|
| async function loadJournalEntries() {
|
| try {
|
| const response = await fetch('/api/journal/entries?limit=5');
|
| const entries = await response.json();
|
|
|
| const container = document.getElementById('journalEntries');
|
| container.innerHTML = '';
|
|
|
| entries.forEach(entry => {
|
| const div = document.createElement('div');
|
| div.className = 'session-card';
|
| div.innerHTML = `
|
| <div class="session-header">
|
| <div class="session-date">
|
| ${new Date(entry.timestamp).toLocaleDateString('fr-FR')}
|
| </div>
|
| <div style="background: #10b981; color: white; padding: 5px 15px; border-radius: 20px;">
|
| ${entry.auto_eval || '?'}/5
|
| </div>
|
| </div>
|
| ${entry.competence ? `<div style="color: #3b82f6; margin: 5px 0;"><i class="fas fa-language"></i> ${entry.competence}</div>` : ''}
|
| ${entry.done ? `<div style="margin-top: 10px;"><strong>Accompli:</strong> ${entry.done.substring(0, 100)}...</div>` : ''}
|
| `;
|
| container.appendChild(div);
|
| });
|
|
|
| document.getElementById('journalHistory').style.display = 'block';
|
| } catch (error) {
|
| console.error('Erreur chargement journal:', error);
|
| }
|
| }
|
|
|
|
|
| async function getIntelligentReminder() {
|
| try {
|
| const response = await fetch('/api/notifications/intelligent');
|
| const data = await response.json();
|
|
|
| const container = document.getElementById('notificationContainer');
|
| const card = document.createElement('div');
|
| card.className = 'notification-card';
|
|
|
| const icons = {
|
| 'motivation': '🌱',
|
| 'rappel_court': '⏰',
|
| 'encouragement': '💪',
|
| 'metacognition': '🤔'
|
| };
|
|
|
| card.innerHTML = `
|
| <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 10px;">
|
| <div style="font-size: 2rem;">${icons[data.type] || '🔔'}</div>
|
| <div>
|
| <div style="font-weight: bold; color: #1e293b;">${data.type ? data.type.charAt(0).toUpperCase() + data.type.slice(1).replace('_', ' ') : 'Notification'}</div>
|
| <div style="font-size: 0.9rem; color: #64748b;">
|
| Efficacité: ${(data.effectiveness_score * 100).toFixed(1)}%
|
| </div>
|
| </div>
|
| </div>
|
| <p style="font-size: 1.1rem; line-height: 1.5;">${data.message}</p>
|
| <div style="margin-top: 10px; font-size: 0.8rem; color: #64748b; text-align: right;">
|
| ${new Date(data.timestamp).toLocaleTimeString('fr-FR')}
|
| </div>
|
| `;
|
|
|
| container.insertBefore(card, container.firstChild);
|
|
|
| } catch (error) {
|
| console.error('Erreur notification:', error);
|
| }
|
| }
|
|
|
|
|
| async function refreshStats() {
|
| try {
|
| const response = await fetch('/api/learner/stats');
|
| const stats = await response.json();
|
|
|
| const container = document.getElementById('statsContainer');
|
| container.innerHTML = `
|
| <div class="stat-card">
|
| <div class="stat-value">${stats.total_sessions}</div>
|
| <div class="stat-label">Sessions Complétées</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${stats.success_rate.toFixed(2)}/5</div>
|
| <div class="stat-label">Score Moyen</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${stats.journal_entries}</div>
|
| <div class="stat-label">Entrées Journal</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value">${stats.planned_sessions}</div>
|
| <div class="stat-label">Sessions Planifiées</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value" style="color: ${stats.engagement_level === 'high' ? '#10b981' : stats.engagement_level === 'medium' ? '#f59e0b' : '#ef4444'}">
|
| ${stats.engagement_level}
|
| </div>
|
| <div class="stat-label">Niveau d'Engagement</div>
|
| </div>
|
| <div class="stat-card">
|
| <div class="stat-value" style="color: ${stats.risk_level === 'low' ? '#10b981' : stats.risk_level === 'medium' ? '#f59e0b' : '#ef4444'}">
|
| ${stats.risk_level}
|
| </div>
|
| <div class="stat-label">Niveau de Risque</div>
|
| </div>
|
| `;
|
|
|
|
|
| updateRetentionChart(stats);
|
|
|
| } catch (error) {
|
| console.error('Erreur statistiques:', error);
|
| }
|
| }
|
|
|
| function updateRetentionChart(stats) {
|
| const ctx = document.getElementById('retentionChart').getContext('2d');
|
|
|
| if (window.retentionChart) {
|
| window.retentionChart.destroy();
|
| }
|
|
|
|
|
| const labels = ['Sem 1', 'Sem 2', 'Sem 3', 'Sem 4', 'Sem 5', 'Sem 6'];
|
| const retentionData = [0.3, 0.5, 0.65, 0.75, 0.8, 0.85];
|
| const engagementData = [0.7, 0.8, 0.75, 0.85, 0.9, 0.88];
|
|
|
| window.retentionChart = new Chart(ctx, {
|
| type: 'line',
|
| data: {
|
| labels: labels,
|
| datasets: [
|
| {
|
| label: 'Taux de Rétention',
|
| data: retentionData,
|
| borderColor: '#3b82f6',
|
| backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
| borderWidth: 3,
|
| fill: true,
|
| tension: 0.4
|
| },
|
| {
|
| label: 'Niveau d\'Engagement',
|
| data: engagementData,
|
| borderColor: '#10b981',
|
| backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
| borderWidth: 3,
|
| fill: true,
|
| tension: 0.4
|
| }
|
| ]
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| scales: {
|
| y: {
|
| beginAtZero: true,
|
| max: 1,
|
| ticks: {
|
| callback: function(value) {
|
| return (value * 100) + '%';
|
| }
|
| }
|
| }
|
| },
|
| plugins: {
|
| tooltip: {
|
| callbacks: {
|
| label: function(context) {
|
| return context.dataset.label + ': ' + (context.raw * 100).toFixed(1) + '%';
|
| }
|
| }
|
| }
|
| }
|
| }
|
| });
|
| }
|
|
|
|
|
| async function loadSM2Explanation() {
|
| try {
|
| const response = await fetch('/api/sm2/explain');
|
| const explanation = await response.json();
|
|
|
| const container = document.getElementById('sm2Explanation');
|
| container.innerHTML = `
|
| <div class="card" style="margin-bottom: 20px;">
|
| <h3 style="color: #3b82f6; margin-bottom: 15px;">${explanation.title}</h3>
|
| <p style="margin-bottom: 20px; line-height: 1.6;">${explanation.description}</p>
|
|
|
| <h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-layer-group"></i> Niveaux de Qualité</h4>
|
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-bottom: 20px;">
|
| ${Object.entries(explanation.quality_levels).map(([level, desc]) => `
|
| <div style="padding: 15px; background: #f8fafc; border-radius: 10px; border-left: 4px solid ${getQualityColor(level)};">
|
| <div style="font-weight: bold; color: ${getQualityColor(level)};">Niveau ${level}</div>
|
| <div style="font-size: 0.9rem; color: #64748b;">${desc}</div>
|
| </div>
|
| `).join('')}
|
| </div>
|
|
|
| <h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-lightbulb"></i> Principes Fondamentaux</h4>
|
| <ul style="margin-bottom: 20px; padding-left: 20px;">
|
| ${explanation.principles.map(principle => `
|
| <li style="margin-bottom: 10px; line-height: 1.5;">${principle}</li>
|
| `).join('')}
|
| </ul>
|
|
|
| <h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-check-circle"></i> Recommandations</h4>
|
| <ul style="padding-left: 20px;">
|
| ${explanation.recommendations.map(recommendation => `
|
| <li style="margin-bottom: 10px; line-height: 1.5;">${recommendation}</li>
|
| `).join('')}
|
| </ul>
|
| </div>
|
| `;
|
| } catch (error) {
|
| console.error('Erreur chargement explication SM-2:', error);
|
| }
|
| }
|
|
|
| function getQualityColor(level) {
|
| const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6'];
|
| return colors[parseInt(level)] || colors[0];
|
| }
|
|
|
| function updateComparisonChart() {
|
| const ctx = document.getElementById('comparisonChart').getContext('2d');
|
|
|
| if (window.comparisonChart) {
|
| window.comparisonChart.destroy();
|
| }
|
|
|
|
|
| const intervals = {
|
| 'Difficulté (N0)': [1, 2, 4, 8, 16, 32],
|
| 'Moyen (N1)': [1, 3, 7, 14, 30, 60],
|
| 'Bon (N2)': [2, 7, 16, 35, 70, 140],
|
| 'Excellent (N3)': [3, 10, 25, 60, 120, 240]
|
| };
|
|
|
| const labels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6'];
|
| const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6'];
|
|
|
| const datasets = Object.entries(intervals).map(([label, data], index) => ({
|
| label: label,
|
| data: data,
|
| borderColor: colors[index],
|
| backgroundColor: colors[index] + '20',
|
| borderWidth: 2,
|
| tension: 0.4,
|
| fill: false
|
| }));
|
|
|
| window.comparisonChart = new Chart(ctx, {
|
| type: 'line',
|
| data: {
|
| labels: labels,
|
| datasets: datasets
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: false,
|
| scales: {
|
| y: {
|
| beginAtZero: true,
|
| title: {
|
| display: true,
|
| text: 'Intervalle (jours)'
|
| }
|
| }
|
| },
|
| plugins: {
|
| title: {
|
| display: true,
|
| text: 'Comparaison des Courbes SM-2 par Niveau de Qualité',
|
| font: { size: 16 }
|
| },
|
| tooltip: {
|
| callbacks: {
|
| label: function(context) {
|
| return `${context.dataset.label}: ${context.raw} jours`;
|
| }
|
| }
|
| }
|
| }
|
| }
|
| });
|
| }
|
|
|
|
|
| function exportPlanner() {
|
| const plannerData = document.getElementById('plannerContent').innerText;
|
| const blob = new Blob([plannerData], { type: 'text/plain' });
|
| const url = URL.createObjectURL(blob);
|
| const a = document.createElement('a');
|
| a.href = url;
|
| a.download = 'planner-sm2.txt';
|
| document.body.appendChild(a);
|
| a.click();
|
| document.body.removeChild(a);
|
| URL.revokeObjectURL(url);
|
| }
|
| </script>
|
| </body>
|
| </html> |