OUAREDAEK commited on
Commit
4ba3531
·
verified ·
1 Parent(s): d3a685e

Upload templates/index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. templates/index.html +274 -201
templates/index.html CHANGED
@@ -1,3 +1,5 @@
 
 
1
  <!DOCTYPE html>
2
  <html lang="fr">
3
  <head>
@@ -126,7 +128,6 @@
126
  .btn-secondary:hover { background: var(--gray-50); }
127
  .btn-large { padding: 12px 24px; font-size: 0.9rem; }
128
 
129
- /* Planning Stats Cards */
130
  .planning-stats {
131
  display: grid;
132
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
@@ -154,7 +155,6 @@
154
  margin-top: 4px;
155
  }
156
 
157
- /* Planning Table */
158
  .planning-sessions-table {
159
  width: 100%;
160
  border-collapse: collapse;
@@ -288,7 +288,6 @@
288
  .tab-content { display: none; }
289
  .tab-content.active { display: block; }
290
 
291
- /* Spaced Repetition Curve */
292
  .spaced-repetition-container {
293
  background: linear-gradient(135deg, #fafafa 0%, #f0f0f0 100%);
294
  border-radius: 16px;
@@ -328,7 +327,6 @@
328
  .export-btn { background: white; border: 1px solid var(--gray-200); padding: 8px 16px; border-radius: 8px; cursor: pointer; }
329
  .export-btn:hover { background: var(--gray-50); }
330
 
331
- /* Profile specific styles */
332
  .profile-progress {
333
  background: var(--gray-100);
334
  border-radius: 12px;
@@ -396,7 +394,6 @@
396
  .profile-mini-card .value { font-size: 1.5rem; font-weight: 700; color: var(--primary); }
397
  .flex-between { display: flex; justify-content: space-between; align-items: center; }
398
 
399
- /* DASHBOARD PROFESSIONNEL : HEATMAP MODERNE & ELEGANT */
400
  .dashboard-two-columns {
401
  display: flex;
402
  flex-wrap: wrap;
@@ -504,7 +501,6 @@
504
  filter: brightness(0.96);
505
  box-shadow: 0 4px 10px rgba(0,0,0,0.05);
506
  }
507
- /* Palette élégante en niveaux de gris */
508
  .heatmap-cell.level-0 { background-color: #f5f5f5; }
509
  .heatmap-cell.level-1 { background-color: #d6d6d6; }
510
  .heatmap-cell.level-2 { background-color: #a0a0a0; }
@@ -544,7 +540,6 @@
544
  .legend-color-box.level-3 { background-color: #6e6e6e; }
545
  .legend-color-box.level-4 { background-color: #2c2c2c; }
546
 
547
- /* Courbe symétrique */
548
  .symmetry-curve-card {
549
  background: white;
550
  border-radius: 20px;
@@ -577,7 +572,6 @@
577
  border-radius: 16px;
578
  }
579
 
580
- /* Indicateurs modernes */
581
  .indicator-grid {
582
  display: grid;
583
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
@@ -621,7 +615,6 @@
621
  letter-spacing: -0.2px;
622
  }
623
 
624
- /* JaUgES circulaires */
625
  .circular-progress {
626
  position: relative;
627
  width: 140px;
@@ -716,7 +709,6 @@
716
  border-color: var(--primary);
717
  }
718
 
719
- /* Journal Section - Professionnel Noir & Blanc */
720
  .journal-section {
721
  background: var(--gray-50);
722
  border-radius: 12px;
@@ -822,7 +814,6 @@
822
  margin-top: 10px;
823
  }
824
 
825
- /* ========== STYLES POUR LE MESSAGE RÉFLEXIF IA ========== */
826
  .ia-reflective-message {
827
  background: linear-gradient(135deg, #f8f9fa 0%, #eef2f5 100%);
828
  border-left: 5px solid var(--primary);
@@ -908,6 +899,137 @@
908
  .ia-reflective-message { padding: 16px 20px; }
909
  .ia-message-content { font-size: 0.85rem; }
910
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
  </style>
912
  </head>
913
  <body>
@@ -957,53 +1079,73 @@
957
  <!-- PLANNING -->
958
  <section id="planner" class="tab-content"><div class="card"><div class="card-header"><h3><i class="fas fa-calendar-alt"></i> Planner Personnalisé</h3><button class="export-btn" onclick="exportPlanning()"><i class="fas fa-download"></i> Exporter</button></div><div class="planning-stats" id="planningStats"><div class="planning-stat-item"><div class="planning-stat-number" id="statSessions">6</div><div class="planning-stat-label">Sessions Planifiées</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statTotalDays">270</div><div class="planning-stat-label">Jours Totaux</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statQuality">3/4</div><div class="planning-stat-label">Niveau de Qualité</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statDuration">20min</div><div class="planning-stat-label">Durée/Session</div></div></div><div style="overflow-x:auto;"><table class="planning-sessions-table" id="sessionsTable"><thead><tr><th>Date</th><th>Session</th><th>Compétence</th><th>Durée</th><th>Intervalle</th><th>Type</th><th>Stratégie</th><th>Productivité</th><th>Heure Optimale</th></tr></thead><tbody id="sessionsTableBody"></tbody></table></div><div class="spaced-repetition-container"><h4><i class="fas fa-chart-line"></i> Courbe de Répétition Espacée</h4><div class="curve-legend"><div class="legend-item"><div class="legend-color" style="background:#1e1e1e;"></div><span>Seuil de rétention</span></div><div class="legend-item"><div class="legend-color square" style="background:#424242;"></div><span>Sessions</span></div></div><canvas id="spacedRepetitionCurve" width="100%" height="200" style="max-height:200px; width:100%;"></canvas><div id="intervalDatesCurve" style="font-size:0.7rem; text-align:center; margin-top:8px;"></div></div></div></section>
959
 
960
- <!-- DASHBOARD (avec radar à côté heatmap + message réflexif IA) -->
961
  <section id="dashboard" class="tab-content">
962
  <div class="card">
963
  <div class="card-header"><h3><i class="fas fa-chart-simple"></i> Analyse de la persistance et du management de l'effort (persévérance)</h3><div class="dashboard-view-toggle"><button class="view-btn active" data-view="combined" onclick="setDashboardView('combined')">📊 Vue d'ensemble</button></div></div>
964
 
965
- <!-- ========== MESSAGE RÉFLEXIF IA ========== -->
966
  <div class="ia-reflective-message">
967
  <div class="ia-message-header">
968
- <div class="ia-avatar">
969
- <i class="fas fa-brain"></i>
970
- </div>
971
- <div>
972
- <div class="ia-title">Message Réflexif </div>
973
- </div>
974
  </div>
975
  <div class="ia-message-content">
976
- Ton engagement est très positif : tu démarres <strong class="ia-stat-highlight">85%</strong> des activités, interagis fortement avec les ressources (<strong class="ia-stat-highlight">80%</strong>) et termines <strong class="ia-stat-highlight">74%</strong> des tâches. Ta régularité de travail (<strong class="ia-stat-highlight">90%</strong>) et ta gestion des échéances (<strong class="ia-stat-highlight">84%</strong>) montrent une excellente persévérance. Cette persévérance constitue un véritable atout pour progresser.
977
  </div>
978
  </div>
979
- <!-- ========== FIN DU MESSAGE RÉFLEXIF ========== -->
980
 
981
- <!-- SECTION HEATMAP + RADAR CÔTE À CÔTE -->
982
  <div class="dashboard-two-columns">
983
- <!-- Heatmap (gauche ou 1.2) -->
984
  <div class="heatmap-wrapper">
985
  <div class="heatmap-container">
986
- <div class="heatmap-title">
987
- <h4><i class="fas fa-fire" style="color:#f97316;"></i> Heatmap d'activité – 4 semaines</h4>
988
- <span class="heatmap-sub">Intensité d'engagement quotidien</span>
 
 
 
 
 
 
989
  </div>
990
- <div class="heatmap-grid-wrapper">
991
- <div class="heatmap-grid" id="heatmapGrid"></div>
 
 
 
 
 
 
 
 
 
 
992
  </div>
993
- <div class="legend-heatmap" id="heatmapLegend"></div>
994
  </div>
995
  </div>
996
 
997
- <!-- Radar (droite) -->
998
  <div class="radar-wrapper">
999
- <div class="radar-title">
1000
- <i class="fas fa-chart-pie"></i> Analyse de l'engagement : Indicateurs de comportement d'apprentissage
1001
- </div>
1002
- <div class="radar-container">
1003
- <canvas id="engagementRadarChart" width="400" height="400" style="max-width:100%; height:auto;"></canvas>
1004
- </div>
1005
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 16px; font-size: 0.7rem; color: var(--gray-600);">
1006
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1007
  </div>
1008
  </div>
1009
  </div>
@@ -1018,42 +1160,15 @@
1018
  </div>
1019
  </section>
1020
 
1021
- <!-- MON JOURNAL AVEC SELECTION DE DATE & ICONES PRO -->
1022
  <section id="journal" class="tab-content">
1023
  <div class="card">
1024
  <div class="card-header"><h3><i class="far fa-edit"></i> Mon Journal d'apprentissage</h3></div>
1025
-
1026
- <div class="date-selector">
1027
- <label><i class="far fa-calendar-alt"></i> Date de l'entrée :</label>
1028
- <input type="date" id="journalDate" class="form-control" style="width: auto;" value="">
1029
- </div>
1030
-
1031
- <div class="journal-section">
1032
- <h4><i class="fas fa-tasks"></i> Planifié (To-Do)</h4>
1033
- <textarea id="plannedText" rows="2" placeholder="Ce que vous prévoyez d'apprendre..."></textarea>
1034
- </div>
1035
- <div class="journal-section">
1036
- <h4><i class="fas fa-sync-alt"></i> En Cours (Doing)</h4>
1037
- <textarea id="doingText" rows="2" placeholder="Ce que vous faites actuellement..."></textarea>
1038
- </div>
1039
- <div class="journal-section">
1040
- <h4><i class="fas fa-check-circle"></i> Accompli (Done)</h4>
1041
- <textarea id="doneText" rows="2" placeholder="Ce que vous avez accompli..."></textarea>
1042
- <div class="self-rating-group">
1043
- <span><i class="fas fa-star"></i> Auto-évaluation (1-5) :</span>
1044
- <div class="rating-stars" id="selfRatingGroup">
1045
- <button type="button" class="star-btn" data-rating="1">1</button>
1046
- <button type="button" class="star-btn" data-rating="2">2</button>
1047
- <button type="button" class="star-btn" data-rating="3">3</button>
1048
- <button type="button" class="star-btn" data-rating="4">4</button>
1049
- <button type="button" class="star-btn" data-rating="5">5</button>
1050
- </div>
1051
- </div>
1052
- </div>
1053
- <div class="journal-actions">
1054
- <button class="btn btn-secondary" onclick="clearJournalForm()"><i class="fas fa-eraser"></i> Effacer le formulaire</button>
1055
- <button class="btn btn-primary" onclick="saveJournalEntry()"><i class="fas fa-save"></i> Sauvegarder l'entrée</button>
1056
- </div>
1057
  <div class="journal-entries-list" id="journalEntriesList"></div>
1058
  </div>
1059
  </section>
@@ -1063,7 +1178,10 @@
1063
  </div>
1064
 
1065
  <script>
1066
- // Fonctions métiers existantes (conservées)
 
 
 
1067
  const competencesData = [
1068
  { "id": "D-MCONJ", "label": "Marques de la conjugaison", "category": "Discours" },
1069
  { "id": "D-PDV", "label": "Points de vue adoptés", "category": "Discours" },
@@ -1084,157 +1202,97 @@
1084
  let profileData = { extraversion: 3, openness: 3, conscientiousness: 4, agreeableness: 3, neuroticism: 3, intrinsic: 3, introjected: 4 };
1085
  const eventTemplates = [{ type: "Rappel d'étude", title: "Session de révision", message: "Votre session est prévue aujourd'hui.", priority: "high" },{ type: "Conseil IA", title: "Optimisation", message: "Révisez les conjugaisons.", priority: "medium" },{ type: "Encouragement", title: "Progression", message: "80% des objectifs atteints.", priority: "low" }];
1086
  let journalEntries = [];
 
 
1087
 
1088
- // Données pour le radar (8 indicateurs)
1089
- const radarIndicators = [
1090
- "Démarrage des activités", "Achèvement des activités",
1091
- "Taux d'erreur", "Tentatives de réponses",
1092
- "Éléments des connaissances validées", "Interactions avec les ressources",
1093
- "Score", "Niveau moyen des compétences"
1094
- ];
1095
- const radarValues = [85, 74, 65, 78, 72, 80, 70, 68]; // Pourcentages
1096
- // Paires inversées pour l'affichage symétrique dans le titre
1097
-
1098
- // Fonction pour créer ou mettre à jour le radar chart
1099
  function initRadarChart() {
1100
  const ctx = document.getElementById('engagementRadarChart')?.getContext('2d');
1101
  if(!ctx) return;
1102
  if(radarChart) radarChart.destroy();
1103
  radarChart = new Chart(ctx, {
1104
  type: 'radar',
1105
- data: {
1106
- labels: radarIndicators,
1107
- datasets: [{
1108
- label: 'Taux (%)',
1109
- data: radarValues,
1110
- backgroundColor: 'rgba(30, 30, 30, 0.2)',
1111
- borderColor: '#1e1e1e',
1112
- borderWidth: 2,
1113
- pointBackgroundColor: '#1e1e1e',
1114
- pointBorderColor: '#ffffff',
1115
- pointRadius: 5,
1116
- pointHoverRadius: 7,
1117
- fill: true
1118
- }]
1119
- },
1120
- options: {
1121
- responsive: true,
1122
- maintainAspectRatio: true,
1123
- scales: {
1124
- r: {
1125
- beginAtZero: true,
1126
- max: 100,
1127
- ticks: { stepSize: 20, backdropColor: 'transparent' },
1128
- grid: { color: '#e0e0e0' },
1129
- angleLines: { color: '#e0e0e0' },
1130
- pointLabels: { font: { size: 10, weight: '500' }, color: '#424242' }
1131
- }
1132
- },
1133
- plugins: {
1134
- tooltip: { callbacks: { label: (ctx) => `${ctx.label}: ${ctx.raw}%` } },
1135
- legend: { display: false }
1136
- }
1137
- }
1138
  });
1139
  }
1140
 
1141
- // JOURNAL : NOUVELLES FONCTIONS AVEC DATE
1142
- function loadJournalEntries() {
1143
- const saved = localStorage.getItem('perseverAI_journal');
1144
- if(saved) journalEntries = JSON.parse(saved);
1145
- renderJournalEntries();
 
 
 
 
 
 
 
 
1146
  }
1147
 
1148
- function saveJournalEntry() {
1149
- const selectedDate = document.getElementById('journalDate')?.value || new Date().toISOString().split('T')[0];
1150
- const planned = document.getElementById('plannedText')?.value.trim() || "";
1151
- const doing = document.getElementById('doingText')?.value.trim() || "";
1152
- const done = document.getElementById('doneText')?.value.trim() || "";
1153
- const activeRating = document.querySelector('#selfRatingGroup .star-btn.active');
1154
- let selfRating = activeRating ? parseInt(activeRating.dataset.rating) : null;
1155
- if(!planned && !doing && !done) { showToast("Veuillez renseigner au moins un champ"); return; }
1156
- const newEntry = {
1157
- id: Date.now(),
1158
- date: selectedDate,
1159
- dateFormatted: new Date(selectedDate).toLocaleDateString('fr-FR'),
1160
- planned,
1161
- doing,
1162
- done,
1163
- selfRating
1164
- };
1165
- journalEntries.unshift(newEntry);
1166
- localStorage.setItem('perseverAI_journal', JSON.stringify(journalEntries));
1167
- document.getElementById('plannedText').value = "";
1168
- document.getElementById('doingText').value = "";
1169
- document.getElementById('doneText').value = "";
1170
- document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => btn.classList.remove('active'));
1171
- renderJournalEntries();
1172
- showToast("Journal sauvegardé");
1173
  }
1174
 
1175
- function clearJournalForm() {
1176
- document.getElementById('plannedText').value = "";
1177
- document.getElementById('doingText').value = "";
1178
- document.getElementById('doneText').value = "";
1179
- document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => btn.classList.remove('active'));
1180
- const today = new Date().toISOString().split('T')[0];
1181
- document.getElementById('journalDate').value = today;
1182
- showToast("Formulaire effacé");
 
1183
  }
1184
 
1185
- function renderJournalEntries() {
1186
- const container = document.getElementById('journalEntriesList');
1187
- if(!container) return;
1188
- if(journalEntries.length === 0) {
1189
- container.innerHTML = '<div style="text-align:center; color:var(--gray-500); padding:20px;"><i class="fas fa-book-open"></i> Aucune entrée pour le moment</div>';
1190
- return;
1191
- }
1192
- container.innerHTML = journalEntries.map(entry => `<div class="journal-entry-item">
1193
- <div class="journal-entry-date"><i class="far fa-calendar-alt"></i> ${entry.dateFormatted || entry.date}</div>
1194
- ${entry.planned ? `<div><span class="journal-badge"><i class="fas fa-tasks"></i> Planifié</span> ${escapeHtml(entry.planned)}</div>` : ''}
1195
- ${entry.doing ? `<div><span class="journal-badge"><i class="fas fa-sync-alt"></i> En cours</span> ${escapeHtml(entry.doing)}</div>` : ''}
1196
- ${entry.done ? `<div><span class="journal-badge"><i class="fas fa-check-circle"></i> Accompli</span> ${escapeHtml(entry.done)}</div>` : ''}
1197
- ${entry.selfRating ? `<div><span class="journal-badge"><i class="fas fa-star"></i> Auto-évaluation</span> ${entry.selfRating}/5</div>` : ''}
1198
- </div>`).join('');
1199
  }
1200
 
1201
- function escapeHtml(str) { return str.replace(/[&<>]/g, function(m){if(m==='&') return '&amp;'; if(m==='<') return '&lt;'; if(m==='>') return '&gt;'; return m;}); }
 
 
 
1202
 
1203
- function setupRatingStars() {
1204
- document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => {
1205
- btn.addEventListener('click', function() {
1206
- document.querySelectorAll('#selfRatingGroup .star-btn').forEach(b => b.classList.remove('active'));
1207
- this.classList.add('active');
1208
- });
1209
- });
1210
  }
1211
 
1212
- // Autres fonctions existantes
 
1213
  function generateHeatmapData() { const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; const weeks = 4; let matrix = []; for(let w = 0; w < weeks; w++) { let week = []; for(let d = 0; d < 7; d++) { let base = (w * 7 + d) % 10; let level = 0; if(base < 3) level = 1; else if(base < 6) level = 2; else if(base < 8) level = 3; else level = 4; if(d === 3 || d === 4) level = Math.min(4, level+1); week.push(level); } matrix.push(week); } return { days, weeks: matrix }; }
1214
- function renderHeatmap() { const container = document.getElementById('heatmapGrid'); if(!container) return; const { days, weeks } = generateHeatmapData(); container.innerHTML = ''; for(let i = 0; i < days.length; i++) { let dayDiv = document.createElement('div'); dayDiv.className = 'heatmap-day-label'; dayDiv.innerText = days[i]; container.appendChild(dayDiv); } for(let w = 0; w < weeks.length; w++) { for(let d = 0; d < weeks[w].length; d++) { let cell = document.createElement('div'); cell.className = `heatmap-cell level-${weeks[w][d]}`; cell.title = `${days[d]} Semaine ${w+1} : Niveau ${weeks[w][d]}`; container.appendChild(cell); } } const legendContainer = document.getElementById('heatmapLegend'); if(legendContainer) { legendContainer.innerHTML = `<div class="legend-heatmap-item"><div class="legend-color-box level-0"></div><span>Aucune activité</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-1"></div><span>Faible</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-2"></div><span>Moyenne</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-3"></div><span>Élevée</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-4"></div><span>Intense</span></div>`; } }
1215
- function drawSymmetryCurve() { const canvas = document.getElementById('symmetryHeatmapCurve'); if(!canvas) return; const ctx = canvas.getContext('2d'); const container = canvas.parentElement; const width = container.clientWidth - 40; const height = 160; canvas.width = width; canvas.height = height; ctx.clearRect(0, 0, width, height); const { days, weeks } = generateHeatmapData(); let avgIntensity = new Array(7).fill(0); for(let w = 0; w < weeks.length; w++) for(let d = 0; d < 7; d++) avgIntensity[d] += weeks[w][d]; avgIntensity = avgIntensity.map(v => v / weeks.length); let reversed = [...avgIntensity].reverse(); let combined = avgIntensity.map((v,i)=> (v + reversed[i])/2); const points = combined.map((val, idx) => ({ x: (idx / 6) * width, y: height - (val / 4) * height })); ctx.beginPath(); ctx.strokeStyle = '#1e1e1e'; ctx.lineWidth = 2.5; ctx.setLineDash([]); points.forEach((p,i) => i===0 ? ctx.moveTo(p.x,p.y) : ctx.lineTo(p.x,p.y)); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#616161'; ctx.lineWidth = 2; ctx.setLineDash([6,8]); let mirrorPoints = points.map(p => ({ x: width - p.x, y: p.y })); mirrorPoints.forEach((p,i)=> i===0 ? ctx.moveTo(p.x,p.y) : ctx.lineTo(p.x,p.y)); ctx.stroke(); ctx.setLineDash([]); points.forEach(p => { ctx.beginPath(); ctx.fillStyle = '#1e1e1e'; ctx.arc(p.x, p.y, 4, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, 2*Math.PI); ctx.fill(); }); ctx.fillStyle = '#475569'; ctx.font = '9px Inter'; days.forEach((d,i) => { let x = (i/6)*width; ctx.fillText(d, x-8, height-6); }); }
1216
- function updateCircularProgress(elementId, percent) { const circle = document.getElementById(elementId); if(!circle) return; const radius = 54; const circumference = 2 * Math.PI * radius; const offset = circumference - (percent / 100) * circumference; circle.style.strokeDasharray = `${circumference}`; circle.style.strokeDashoffset = offset; }
1217
  function updateIndicators() { document.getElementById('regularityPercent').innerText = '90%'; document.getElementById('deadlinePercent').innerText = '88%'; updateCircularProgress('regularityCircleProgress', 90); updateCircularProgress('deadlineCircleProgress', 88); document.getElementById('effortPattern').innerHTML = "Effort concentré (Pic d'activité jeudi/vendredi)"; document.getElementById('persistenceScore').innerHTML = "Très élevée"; }
1218
  function setDashboardView(view) { if(view === 'combined') { renderHeatmap(); drawSymmetryCurve(); updateIndicators(); initRadarChart(); } }
1219
- function initCompetencesSelect() { const select = document.getElementById('competence'); if(select) select.innerHTML = '<option value="comprendre_mots">Comprendre les mots</option>' + competencesData.map(c => `<option value="${c.id}">${c.label}</option>`).join(''); }
1220
- function setupRatingListeners() { document.querySelectorAll('.big5-rating .rating-btn').forEach(btn => btn.addEventListener('click', function() { const container = this.closest('.big5-rating'); const trait = container.dataset.trait; const value = parseInt(this.dataset.value); profileData[trait] = value; updateRatingUI(container, value); updateProfileUI(); saveProfileToStorage(); })); document.querySelectorAll('.motivation-rating .rating-btn').forEach(btn => btn.addEventListener('click', function() { const container = this.closest('.motivation-rating'); const motivation = container.dataset.motivation; const value = parseInt(this.dataset.value); profileData[motivation] = value; updateRatingUI(container, value); updateProfileUI(); saveProfileToStorage(); })); }
1221
- function updateRatingUI(container, value) { container.querySelectorAll('.rating-btn').forEach(btn => { btn.classList.remove('active'); if(parseInt(btn.dataset.value) === value) btn.classList.add('active'); }); }
1222
- function updateProfileUI() { document.getElementById('summaryExtraversion').innerText = profileData.extraversion; document.getElementById('summaryOpenness').innerText = profileData.openness; document.getElementById('summaryConscientiousness').innerText = profileData.conscientiousness; document.getElementById('summaryIntrinsic').innerText = profileData.intrinsic; document.getElementById('summaryIntrojected').innerText = profileData.introjected; document.getElementById('neuroticismScore').innerText = profileData.neuroticism; document.getElementById('introjectedScore').innerText = profileData.introjected; let completed = Object.values(profileData).filter(v => v).length; const percent = Math.round((completed / 7) * 100); document.getElementById('profileCompletionPercent').innerText = percent + '%'; document.getElementById('profileProgressFill').style.width = percent + '%'; document.getElementById('completedCount').innerText = Math.min(completed, 5); let level = "Débutant"; if(profileData.conscientiousness >= 4 && profileData.intrinsic >= 4) level = "Avancé"; else if(profileData.conscientiousness >= 3) level = "Intermédiaire"; document.getElementById('profileLevel').innerText = level; const recos = ["◼ Qualité du sommeil: Maintenez 7-8 heures de sommeil de qualité.", "◼ Planification: Renforcez votre organisation avec des rappels."]; document.getElementById('recommendationsContainer').innerHTML = recos.map(r => `<p>${r}</p>`).join(''); }
1223
- function autoFillProfile() { profileData = { extraversion: 4, openness: 4, conscientiousness: 5, agreeableness: 4, neuroticism: 2, intrinsic: 5, introjected: 3 }; document.querySelectorAll('.big5-rating').forEach(container => { if(profileData[container.dataset.trait]) updateRatingUI(container, profileData[container.dataset.trait]); }); document.querySelectorAll('.motivation-rating').forEach(container => { if(profileData[container.dataset.motivation]) updateRatingUI(container, profileData[container.dataset.motivation]); }); updateProfileUI(); saveProfileToStorage(); showToast("Profil automatiquement rempli !"); }
1224
- function saveProfileData() { saveProfileToStorage(); showToast("Profil sauvegardé !"); }
1225
- function finalizeProfile() { saveProfileToStorage(); showToast("Profil finalisé !"); }
1226
- function saveProfileToStorage() { localStorage.setItem('perseverAI_profile', JSON.stringify(profileData)); }
1227
- function loadProfileFromStorage() { const saved = localStorage.getItem('perseverAI_profile'); if(saved) Object.assign(profileData, JSON.parse(saved)); updateProfileUI(); }
1228
- function retentionRate(days, strength = 0.5) { return Math.exp(-days / (strength * 30)) * 100; }
1229
- function drawSpacedRepetitionCurve(sessions) { if(!sessions.length) return; const canvas = document.getElementById('spacedRepetitionCurve'); if(!canvas) return; const ctx = canvas.getContext('2d'); const container = canvas.parentElement; const width = container.clientWidth - 40; const height = 180; canvas.width = width; canvas.height = height; ctx.clearRect(0,0,width,height); const startDate = sessions[0].date.getTime(); const endDate = sessions[sessions.length-1].date.getTime(); const totalDays = (endDate - startDate) / (86400000); ctx.beginPath(); ctx.strokeStyle = '#1e1e1e'; ctx.lineWidth = 2.5; let first = true; for(let x = 0; x <= width; x++) { const dayProg = (x/width)*totalDays; const ret = retentionRate(dayProg,0.4); const y = height - (ret/100)*height; if(first) { ctx.moveTo(x,y); first=false; } else ctx.lineTo(x,y); } ctx.stroke(); sessions.forEach(s => { const x = ((s.date.getTime() - startDate) / (endDate - startDate)) * width; const dayProg = (s.date.getTime() - startDate)/86400000; const retAt = retentionRate(dayProg,0.4); const y = height - (retAt/100)*height; ctx.beginPath(); ctx.fillStyle = '#424242'; ctx.arc(x, y, 5, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(x, y, 2, 0, 2*Math.PI); ctx.fill(); }); const datesDiv = document.getElementById('intervalDatesCurve'); if(datesDiv) datesDiv.innerHTML = sessions.map(s => `<span style="margin:0 8px">${s.dateStr}</span>`).join(''); }
1230
- function generatePlanner() { const competence = document.getElementById('competence')?.value || "comprendre_mots"; const competenceLabel = competencesData.find(c=>c.id===competence)?.label || "Comprendre les mots"; const startDate = new Date(document.getElementById('start')?.value || "2024-01-15"); const sessionDur = document.getElementById('sessionDuration')?.value || "20"; const method = document.querySelector('input[name="productivityMethod"]:checked')?.value || "pomodoro"; const timeSlot = document.querySelector('#timeSlotsGroup input:checked')?.value || "08:00-10:00"; const retention = document.getElementById('retentionQuality')?.value || "2"; const intervals = [1,7,16,35,70,140]; const sessions = []; for(let i=0;i<intervals.length;i++) { let currentDate = new Date(startDate); if(i>0) currentDate.setDate(startDate.getDate() + intervals[i-1]); else currentDate = new Date(startDate); const sessionTypes = ["Apprentissage","Consolidation","Consolidation","Maintien","Maintien","Maintien"]; const strategies = ["Focus intense","Révision active","Révision active","Révision active","Révision active","Révision active"]; sessions.push({ id:i+1, date:new Date(currentDate), dateStr:currentDate.toLocaleDateString('fr-FR'), dayName:currentDate.toLocaleDateString('fr-FR',{weekday:'long'}), competence:competenceLabel, duration:sessionDur+" minutes", interval:intervals[i]+" jours", type:sessionTypes[i], strategy:strategies[i], productivity:method==='pomodoro'?'Pomodoro':'Deep Work', optimalTime:timeSlot }); } currentSessions = sessions; renderPlanningTable(sessions); updatePlanningStats(sessions, retention, sessionDur); drawSpacedRepetitionCurve(sessions); showToast("Planning personnalisé généré !"); showTab('planner'); }
1231
- function generateDefaultPlanning() { const startDate = new Date("2024-01-17"); const intervals = [1,7,16,35,70,140]; const sessions = []; for(let i=0;i<intervals.length;i++) { let currentDate = new Date(startDate); if(i>0) currentDate.setDate(startDate.getDate() + (i===1?1:intervals[i-1])); else currentDate = new Date(startDate); const sessionTypes = ["Apprentissage","Consolidation","Consolidation","Maintien","Maintien","Maintien"]; sessions.push({ id:i+1, date:new Date(currentDate), dateStr:currentDate.toLocaleDateString('fr-FR'), dayName:currentDate.toLocaleDateString('fr-FR',{weekday:'long'}), competence:"comprendre_mots", duration:"20 minutes", interval:intervals[i]+" jours", type:sessionTypes[i], strategy:"Révision active", productivity:"Pomodoro", optimalTime:"08:00-10:00" }); } currentSessions = sessions; renderPlanningTable(sessions); updatePlanningStats(sessions,"2","20"); drawSpacedRepetitionCurve(sessions); }
1232
- function renderPlanningTable(sessions) { const tbody = document.getElementById('sessionsTableBody'); if(!tbody) return; tbody.innerHTML = sessions.map(s => `<tr><td><strong>${s.dateStr}</strong><br><span style="font-size:0.7rem;">${s.dayName}</span></td><td><strong>R${s.id}</strong></td><td>${s.competence}</td><td>${s.duration}</td><td>${s.interval}</td><td><span class="session-type-badge ${s.type==='Apprentissage'?'type-apprentissage':(s.type==='Consolidation'?'type-consolidation':'type-maintien')}">${s.type}</span></td><td>${s.strategy}</td><td>${s.productivity}</td><td>${s.optimalTime}</td></tr>`).join(''); }
1233
- function updatePlanningStats(sessions, quality, duration) { document.getElementById('statSessions').innerText = sessions.length; if(sessions.length > 0) { const lastDate = sessions[sessions.length-1].date; const firstDate = sessions[0].date; const totalDays = Math.ceil((lastDate - firstDate) / (86400000)); document.getElementById('statTotalDays').innerText = totalDays; } const qualityMap = {'0':'1/4','1':'2/4','2':'3/4','3':'4/4'}; document.getElementById('statQuality').innerText = qualityMap[quality] || "3/4"; document.getElementById('statDuration').innerText = duration + "min"; }
1234
- function exportPlanning() { let csv = "Date,Session,Compétence,Durée,Intervalle,Type,Stratégie,Productivité,Heure Optimale\n"; currentSessions.forEach(s => { csv += `"${s.dateStr}","R${s.id}","${s.competence}","${s.duration}","${s.interval}","${s.type}","${s.strategy}","${s.productivity}","${s.optimalTime}"\n`; }); const blob = new Blob([csv], {type: 'text/csv'}); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'planning_perseverai.csv'; link.click(); showToast("Planning exporté en CSV"); }
1235
  function generateRandomEvent() { const template = eventTemplates[Math.floor(Math.random() * eventTemplates.length)]; const newEvent = { id: Date.now() + Math.random(), date: new Date().toLocaleString(), type: template.type, title: template.title, message: template.message, priority: template.priority, status: 'pending' }; eventsList.unshift(newEvent); saveEvents(); renderEventsTable(); updateEventStats(); showToast(`Nouvel événement: ${template.title}`); }
1236
- function executeAction(eventId, actionType, actionLabel) { const eventIndex = eventsList.findIndex(e => e.id == eventId); if(eventIndex === -1) return; eventsList[eventIndex].status = actionType === 'ACCEPT' ? 'accepted' : (actionType === 'REJECT' ? 'rejected' : 'hold'); renderEventsTable(); updateEventStats(); saveEvents(); showToast(`${actionLabel}`); }
1237
- function renderEventsTable() { const tbody = document.getElementById('notificationsTableBody'); if(!tbody) return; if(eventsList.length === 0) { tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;">Aucun événement</td></tr>'; return; } tbody.innerHTML = eventsList.map(event => `<tr><td>${event.date}</td><td>${event.type}</td><td><strong>${event.title}</strong></td><td>${event.message}</td><td><span class="priority-${event.priority}">${event.priority}</span></td><td><span class="status-badge ${event.status === 'accepted' ? 'status-accepted' : 'status-pending'}">${event.status === 'accepted' ? 'Accepté' : 'En attente'}</span></td><td class="action-cell"><button class="action-icon-btn action-accept" onclick="executeAction(${event.id}, 'ACCEPT', 'Accepté')">✓ Accepter</button><button class="action-icon-btn action-reject" onclick="executeAction(${event.id}, 'REJECT', 'Rejeté')">✗ Rejeter</button><button class="action-icon-btn action-hold" onclick="executeAction(${event.id}, 'HOLD', 'En attente')">⏸ Attente</button><button class="action-icon-btn action-help" onclick="executeAction(${event.id}, 'HELP', 'Aide demandée')">🆘 Aide</button></td></tr>`).join(''); }
1238
  function updateEventStats() { document.getElementById('totalEvents').innerText = eventsList.length; document.getElementById('pendingEvents').innerText = eventsList.filter(e => e.status === 'pending').length; document.getElementById('acceptedEvents').innerText = eventsList.filter(e => e.status === 'accepted').length; if(efficiencyChart) efficiencyChart.data.datasets[0].data = [eventsList.filter(e=>e.status==='accepted').length, eventsList.filter(e=>e.status==='rejected').length, eventsList.filter(e=>e.status==='pending').length]; efficiencyChart?.update(); }
1239
  let efficiencyChart = null;
1240
  function createChart() { const ctx = document.getElementById('notificationEfficiencyChart')?.getContext('2d'); if(ctx) efficiencyChart = new Chart(ctx, { type: 'bar', data: { labels: ['Acceptés', 'Rejetés', 'En attente'], datasets: [{ label: 'Actions', data: [0,0,0], backgroundColor: ['#2e7d32','#c62828','#b76e0e'] }] }, options: { responsive: true } }); }
@@ -1242,6 +1300,20 @@
1242
  function loadSavedData() { const saved = localStorage.getItem('eventsList'); if(saved) eventsList = JSON.parse(saved); }
1243
  function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); document.getElementById(tabId).classList.add('active'); document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active')); if(window.event?.target) window.event.target.classList.add('active'); if(tabId === 'dashboard') { renderHeatmap(); drawSymmetryCurve(); updateIndicators(); initRadarChart(); } if(tabId === 'planner') { if(currentSessions.length === 0) generateDefaultPlanning(); else drawSpacedRepetitionCurve(currentSessions); } if(tabId === 'notifications') { renderEventsTable(); updateEventStats(); } if(tabId === 'profile') { loadProfileFromStorage(); updateProfileUI(); } if(tabId === 'journal') { loadJournalEntries(); } }
1244
  function showToast(msg) { let div = document.createElement('div'); div.style.cssText = `position:fixed; bottom:20px; right:20px; background:#1e293b; color:white; padding:12px 20px; border-radius:8px; z-index:1000;`; div.innerHTML = msg; document.body.appendChild(div); setTimeout(()=>div.remove(), 2500); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1245
 
1246
  document.addEventListener('DOMContentLoaded', () => {
1247
  initCompetencesSelect();
@@ -1262,12 +1334,13 @@
1262
  updateCircularProgress('regularityCircleProgress', 90);
1263
  updateCircularProgress('deadlineCircleProgress', 88);
1264
  const todayDate = new Date().toISOString().split('T')[0];
1265
- const journalDateInput = document.getElementById('journalDate');
1266
- if(journalDateInput) journalDateInput.value = todayDate;
1267
  initRadarChart();
 
1268
  });
1269
  function initProfileModule() { loadProfileFromStorage(); setupRatingListeners(); updateProfileUI(); }
1270
  window.addEventListener('resize', () => { if(document.getElementById('dashboard').classList.contains('active')) { drawSymmetryCurve(); if(radarChart) radarChart.resize(); } if(document.getElementById('planner').classList.contains('active') && currentSessions.length > 0) drawSpacedRepetitionCurve(currentSessions); });
1271
  </script>
1272
  </body>
1273
- </html>
 
 
1
+ Voici le code HTML global modifié. J'ai uniquement retravaillé les deux sections "Analyse audio" (Heatmap d'activité et Radar d'engagement) pour les rendre plus élégantes et professionnelles, tout en réduisant la taille des auteurs (crédits) qui sont désormais plus discrets.
2
+ ```html
3
  <!DOCTYPE html>
4
  <html lang="fr">
5
  <head>
 
128
  .btn-secondary:hover { background: var(--gray-50); }
129
  .btn-large { padding: 12px 24px; font-size: 0.9rem; }
130
 
 
131
  .planning-stats {
132
  display: grid;
133
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 
155
  margin-top: 4px;
156
  }
157
 
 
158
  .planning-sessions-table {
159
  width: 100%;
160
  border-collapse: collapse;
 
288
  .tab-content { display: none; }
289
  .tab-content.active { display: block; }
290
 
 
291
  .spaced-repetition-container {
292
  background: linear-gradient(135deg, #fafafa 0%, #f0f0f0 100%);
293
  border-radius: 16px;
 
327
  .export-btn { background: white; border: 1px solid var(--gray-200); padding: 8px 16px; border-radius: 8px; cursor: pointer; }
328
  .export-btn:hover { background: var(--gray-50); }
329
 
 
330
  .profile-progress {
331
  background: var(--gray-100);
332
  border-radius: 12px;
 
394
  .profile-mini-card .value { font-size: 1.5rem; font-weight: 700; color: var(--primary); }
395
  .flex-between { display: flex; justify-content: space-between; align-items: center; }
396
 
 
397
  .dashboard-two-columns {
398
  display: flex;
399
  flex-wrap: wrap;
 
501
  filter: brightness(0.96);
502
  box-shadow: 0 4px 10px rgba(0,0,0,0.05);
503
  }
 
504
  .heatmap-cell.level-0 { background-color: #f5f5f5; }
505
  .heatmap-cell.level-1 { background-color: #d6d6d6; }
506
  .heatmap-cell.level-2 { background-color: #a0a0a0; }
 
540
  .legend-color-box.level-3 { background-color: #6e6e6e; }
541
  .legend-color-box.level-4 { background-color: #2c2c2c; }
542
 
 
543
  .symmetry-curve-card {
544
  background: white;
545
  border-radius: 20px;
 
572
  border-radius: 16px;
573
  }
574
 
 
575
  .indicator-grid {
576
  display: grid;
577
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
 
615
  letter-spacing: -0.2px;
616
  }
617
 
 
618
  .circular-progress {
619
  position: relative;
620
  width: 140px;
 
709
  border-color: var(--primary);
710
  }
711
 
 
712
  .journal-section {
713
  background: var(--gray-50);
714
  border-radius: 12px;
 
814
  margin-top: 10px;
815
  }
816
 
 
817
  .ia-reflective-message {
818
  background: linear-gradient(135deg, #f8f9fa 0%, #eef2f5 100%);
819
  border-left: 5px solid var(--primary);
 
899
  .ia-reflective-message { padding: 16px 20px; }
900
  .ia-message-content { font-size: 0.85rem; }
901
  }
902
+
903
+ /* Styles élégants et professionnels pour les lecteurs audio (réduction auteurs) */
904
+ .audio-modern-card {
905
+ background: #ffffff;
906
+ border-radius: 20px;
907
+ padding: 12px 16px;
908
+ margin-top: 20px;
909
+ border: 1px solid #eaedf2;
910
+ transition: all 0.2s ease;
911
+ box-shadow: 0 1px 2px rgba(0,0,0,0.02);
912
+ }
913
+ .audio-modern-card:hover {
914
+ border-color: #d0d5dd;
915
+ box-shadow: 0 4px 12px rgba(0,0,0,0.03);
916
+ }
917
+ .audio-modern-header {
918
+ display: flex;
919
+ align-items: center;
920
+ gap: 10px;
921
+ margin-bottom: 12px;
922
+ }
923
+ .audio-modern-header i {
924
+ font-size: 0.9rem;
925
+ color: #3a3a3a;
926
+ background: #f5f7fa;
927
+ width: 30px;
928
+ height: 30px;
929
+ display: flex;
930
+ align-items: center;
931
+ justify-content: center;
932
+ border-radius: 30px;
933
+ }
934
+ .audio-modern-header span {
935
+ font-weight: 500;
936
+ font-size: 0.75rem;
937
+ color: #2c3e50;
938
+ letter-spacing: -0.1px;
939
+ }
940
+ .audio-modern-controls {
941
+ display: flex;
942
+ align-items: center;
943
+ gap: 12px;
944
+ flex-wrap: wrap;
945
+ }
946
+ .audio-play-btn-mini {
947
+ background: #1e1e1e;
948
+ border: none;
949
+ width: 36px;
950
+ height: 36px;
951
+ border-radius: 36px;
952
+ color: white;
953
+ cursor: pointer;
954
+ transition: all 0.2s;
955
+ display: flex;
956
+ align-items: center;
957
+ justify-content: center;
958
+ font-size: 0.8rem;
959
+ }
960
+ .audio-play-btn-mini:hover {
961
+ background: #000000;
962
+ transform: scale(0.98);
963
+ }
964
+ .audio-progress-modern {
965
+ flex: 1;
966
+ min-width: 140px;
967
+ }
968
+ .audio-progress-bar-modern {
969
+ width: 100%;
970
+ height: 4px;
971
+ background: #e2e8f0;
972
+ border-radius: 10px;
973
+ cursor: pointer;
974
+ position: relative;
975
+ overflow: hidden;
976
+ }
977
+ .audio-progress-fill-modern {
978
+ width: 0%;
979
+ height: 100%;
980
+ background: #1e1e1e;
981
+ border-radius: 10px;
982
+ transition: width 0.1s linear;
983
+ }
984
+ .audio-time-modern {
985
+ font-size: 0.65rem;
986
+ color: #5b6e8c;
987
+ font-family: monospace;
988
+ min-width: 70px;
989
+ }
990
+ .audio-volume-mini {
991
+ display: flex;
992
+ align-items: center;
993
+ gap: 6px;
994
+ }
995
+ .audio-volume-mini i {
996
+ font-size: 0.7rem;
997
+ color: #5b6e8c;
998
+ cursor: pointer;
999
+ }
1000
+ .volume-slider-mini {
1001
+ width: 60px;
1002
+ height: 3px;
1003
+ -webkit-appearance: none;
1004
+ background: #e2e8f0;
1005
+ border-radius: 3px;
1006
+ }
1007
+ .volume-slider-mini::-webkit-slider-thumb {
1008
+ -webkit-appearance: none;
1009
+ width: 8px;
1010
+ height: 8px;
1011
+ border-radius: 50%;
1012
+ background: #1e1e1e;
1013
+ cursor: pointer;
1014
+ }
1015
+ .audio-credit-micro {
1016
+ text-align: right;
1017
+ font-size: 0.55rem;
1018
+ color: #94a3b8;
1019
+ margin-top: 6px;
1020
+ letter-spacing: 0.2px;
1021
+ border-top: none;
1022
+ padding-top: 2px;
1023
+ opacity: 0.6;
1024
+ }
1025
+ .audio-credit-micro i {
1026
+ font-size: 0.5rem;
1027
+ margin-right: 2px;
1028
+ }
1029
+ @media (max-width: 640px) {
1030
+ .audio-modern-controls { flex-direction: column; align-items: stretch; }
1031
+ .audio-progress-modern { width: 100%; }
1032
+ }
1033
  </style>
1034
  </head>
1035
  <body>
 
1079
  <!-- PLANNING -->
1080
  <section id="planner" class="tab-content"><div class="card"><div class="card-header"><h3><i class="fas fa-calendar-alt"></i> Planner Personnalisé</h3><button class="export-btn" onclick="exportPlanning()"><i class="fas fa-download"></i> Exporter</button></div><div class="planning-stats" id="planningStats"><div class="planning-stat-item"><div class="planning-stat-number" id="statSessions">6</div><div class="planning-stat-label">Sessions Planifiées</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statTotalDays">270</div><div class="planning-stat-label">Jours Totaux</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statQuality">3/4</div><div class="planning-stat-label">Niveau de Qualité</div></div><div class="planning-stat-item"><div class="planning-stat-number" id="statDuration">20min</div><div class="planning-stat-label">Durée/Session</div></div></div><div style="overflow-x:auto;"><table class="planning-sessions-table" id="sessionsTable"><thead><tr><th>Date</th><th>Session</th><th>Compétence</th><th>Durée</th><th>Intervalle</th><th>Type</th><th>Stratégie</th><th>Productivité</th><th>Heure Optimale</th></tr></thead><tbody id="sessionsTableBody"></tbody></table></div><div class="spaced-repetition-container"><h4><i class="fas fa-chart-line"></i> Courbe de Répétition Espacée</h4><div class="curve-legend"><div class="legend-item"><div class="legend-color" style="background:#1e1e1e;"></div><span>Seuil de rétention</span></div><div class="legend-item"><div class="legend-color square" style="background:#424242;"></div><span>Sessions</span></div></div><canvas id="spacedRepetitionCurve" width="100%" height="200" style="max-height:200px; width:100%;"></canvas><div id="intervalDatesCurve" style="font-size:0.7rem; text-align:center; margin-top:8px;"></div></div></div></section>
1081
 
1082
+ <!-- DASHBOARD (version élégante avec audio credit réduit) -->
1083
  <section id="dashboard" class="tab-content">
1084
  <div class="card">
1085
  <div class="card-header"><h3><i class="fas fa-chart-simple"></i> Analyse de la persistance et du management de l'effort (persévérance)</h3><div class="dashboard-view-toggle"><button class="view-btn active" data-view="combined" onclick="setDashboardView('combined')">📊 Vue d'ensemble</button></div></div>
1086
 
 
1087
  <div class="ia-reflective-message">
1088
  <div class="ia-message-header">
1089
+ <div class="ia-avatar"><i class="fas fa-brain"></i></div>
1090
+ <div><div class="ia-title">Message Réflexif </div></div>
 
 
 
 
1091
  </div>
1092
  <div class="ia-message-content">
1093
+ Ton engagement est très positif : tu démarres <strong class="ia-stat-highlight">85%</strong> des activités, interagis fortement avec les ressources (<strong class="ia-stat-highlight">80%</strong>) et termines <strong class="ia-stat-highlight">74%</strong> des tâches. Ta régularité de travail (<strong class="ia-stat-highlight">90%</strong>) et ta gestion des échéances (<strong class="ia-stat-highlight">84%</strong>) montrent une excellente persévérance.
1094
  </div>
1095
  </div>
 
1096
 
 
1097
  <div class="dashboard-two-columns">
 
1098
  <div class="heatmap-wrapper">
1099
  <div class="heatmap-container">
1100
+ <div class="heatmap-title"><h4><i class="fas fa-fire" style="color:#f97316;"></i> Heatmap d'activité – 4 semaines</h4><span class="heatmap-sub">Intensité d'engagement quotidien</span></div>
1101
+ <div class="heatmap-grid-wrapper"><div class="heatmap-grid" id="heatmapGrid"></div></div>
1102
+ <div class="legend-heatmap" id="heatmapLegend"></div>
1103
+ </div>
1104
+ <!-- Lecteur audio Heatmap élégant et discret (crédit réduit) -->
1105
+ <div class="audio-modern-card" id="heatmapAudioCard">
1106
+ <div class="audio-modern-header">
1107
+ <i class="fas fa-chart-line"></i>
1108
+ <span>Analyse audio · Heatmap d'activité</span>
1109
  </div>
1110
+ <div class="audio-modern-controls">
1111
+ <button class="audio-play-btn-mini" onclick="toggleAudio('heatmapAudio')"><i class="fas fa-play" id="heatmapPlayIcon"></i></button>
1112
+ <div class="audio-progress-modern">
1113
+ <div class="audio-progress-bar-modern" id="heatmapProgressBar" onclick="seekAudio('heatmapAudio', event)">
1114
+ <div class="audio-progress-fill-modern" id="heatmapProgressFill"></div>
1115
+ </div>
1116
+ </div>
1117
+ <div class="audio-time-modern"><span id="heatmapCurrentTime">0:00</span> / <span id="heatmapDuration">0:00</span></div>
1118
+ <div class="audio-volume-mini">
1119
+ <i class="fas fa-volume-up" onclick="toggleMute('heatmapAudio')"></i>
1120
+ <input type="range" class="volume-slider-mini" id="heatmapVolume" min="0" max="1" step="0.01" value="0.7" onchange="setVolume('heatmapAudio', this.value)">
1121
+ </div>
1122
  </div>
 
1123
  </div>
1124
  </div>
1125
 
 
1126
  <div class="radar-wrapper">
1127
+ <div class="radar-title"><i class="fas fa-chart-pie"></i> Analyse de l'engagement : Indicateurs de comportement d'apprentissage</div>
1128
+ <div class="radar-container"><canvas id="engagementRadarChart" width="400" height="400" style="max-width:100%; height:auto;"></canvas></div>
1129
+ <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 16px; font-size: 0.7rem; color: var(--gray-600);"></div>
1130
+ <!-- Lecteur audio Radar élégant -->
1131
+ <div class="audio-modern-card" id="radarAudioCard">
1132
+ <div class="audio-modern-header">
1133
+ <i class="fas fa-chart-pie"></i>
1134
+ <span>Analyse audio · Radar d'engagement</span>
1135
+ </div>
1136
+ <div class="audio-modern-controls">
1137
+ <button class="audio-play-btn-mini" onclick="toggleAudio('radarAudio')"><i class="fas fa-play" id="radarPlayIcon"></i></button>
1138
+ <div class="audio-progress-modern">
1139
+ <div class="audio-progress-bar-modern" id="radarProgressBar" onclick="seekAudio('radarAudio', event)">
1140
+ <div class="audio-progress-fill-modern" id="radarProgressFill"></div>
1141
+ </div>
1142
+ </div>
1143
+ <div class="audio-time-modern"><span id="radarCurrentTime">0:00</span> / <span id="radarDuration">0:00</span></div>
1144
+ <div class="audio-volume-mini">
1145
+ <i class="fas fa-volume-up" onclick="toggleMute('radarAudio')"></i>
1146
+ <input type="range" class="volume-slider-mini" id="radarVolume" min="0" max="1" step="0.01" value="0.7" onchange="setVolume('radarAudio', this.value)">
1147
+ </div>
1148
+ </div>
1149
  </div>
1150
  </div>
1151
  </div>
 
1160
  </div>
1161
  </section>
1162
 
1163
+ <!-- MON JOURNAL -->
1164
  <section id="journal" class="tab-content">
1165
  <div class="card">
1166
  <div class="card-header"><h3><i class="far fa-edit"></i> Mon Journal d'apprentissage</h3></div>
1167
+ <div class="date-selector"><label><i class="far fa-calendar-alt"></i> Date de l'entrée :</label><input type="date" id="journalDate" class="form-control" style="width: auto;" value=""></div>
1168
+ <div class="journal-section"><h4><i class="fas fa-tasks"></i> Planifié (To-Do)</h4><textarea id="plannedText" rows="2" placeholder="Ce que vous prévoyez d'apprendre..."></textarea></div>
1169
+ <div class="journal-section"><h4><i class="fas fa-sync-alt"></i> En Cours (Doing)</h4><textarea id="doingText" rows="2" placeholder="Ce que vous faites actuellement..."></textarea></div>
1170
+ <div class="journal-section"><h4><i class="fas fa-check-circle"></i> Accompli (Done)</h4><textarea id="doneText" rows="2" placeholder="Ce que vous avez accompli..."></textarea><div class="self-rating-group"><span><i class="fas fa-star"></i> Auto-évaluation (1-5) :</span><div class="rating-stars" id="selfRatingGroup"><button type="button" class="star-btn" data-rating="1">1</button><button type="button" class="star-btn" data-rating="2">2</button><button type="button" class="star-btn" data-rating="3">3</button><button type="button" class="star-btn" data-rating="4">4</button><button type="button" class="star-btn" data-rating="5">5</button></div></div></div>
1171
+ <div class="journal-actions"><button class="btn btn-secondary" onclick="clearJournalForm()"><i class="fas fa-eraser"></i> Effacer le formulaire</button><button class="btn btn-primary" onclick="saveJournalEntry()"><i class="fas fa-save"></i> Sauvegarder l'entrée</button></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172
  <div class="journal-entries-list" id="journalEntriesList"></div>
1173
  </div>
1174
  </section>
 
1178
  </div>
1179
 
1180
  <script>
1181
+ // Variables globales pour les lecteurs audio
1182
+ let heatmapAudio = null;
1183
+ let radarAudio = null;
1184
+
1185
  const competencesData = [
1186
  { "id": "D-MCONJ", "label": "Marques de la conjugaison", "category": "Discours" },
1187
  { "id": "D-PDV", "label": "Points de vue adoptés", "category": "Discours" },
 
1202
  let profileData = { extraversion: 3, openness: 3, conscientiousness: 4, agreeableness: 3, neuroticism: 3, intrinsic: 3, introjected: 4 };
1203
  const eventTemplates = [{ type: "Rappel d'étude", title: "Session de révision", message: "Votre session est prévue aujourd'hui.", priority: "high" },{ type: "Conseil IA", title: "Optimisation", message: "Révisez les conjugaisons.", priority: "medium" },{ type: "Encouragement", title: "Progression", message: "80% des objectifs atteints.", priority: "low" }];
1204
  let journalEntries = [];
1205
+ const radarIndicators = ["Démarrage des activités", "Achèvement des activités","Taux d'erreur", "Tentatives de réponses","Éléments des connaissances validées", "Interactions avec les ressources","Score", "Niveau moyen des compétences"];
1206
+ const radarValues = [85, 74, 65, 78, 72, 80, 70, 68];
1207
 
 
 
 
 
 
 
 
 
 
 
 
1208
  function initRadarChart() {
1209
  const ctx = document.getElementById('engagementRadarChart')?.getContext('2d');
1210
  if(!ctx) return;
1211
  if(radarChart) radarChart.destroy();
1212
  radarChart = new Chart(ctx, {
1213
  type: 'radar',
1214
+ data: { labels: radarIndicators, datasets: [{ label: 'Taux (%)', data: radarValues, backgroundColor: 'rgba(30, 30, 30, 0.2)', borderColor: '#1e1e1e', borderWidth: 2, pointBackgroundColor: '#1e1e1e', pointBorderColor: '#ffffff', pointRadius: 5, fill: true }] },
1215
+ options: { responsive: true, maintainAspectRatio: true, scales: { r: { beginAtZero: true, max: 100, ticks: { stepSize: 20 }, grid: { color: '#e0e0e0' }, pointLabels: { font: { size: 10 } } } }, plugins: { tooltip: { callbacks: { label: (ctx) => `${ctx.label}: ${ctx.raw}%` } }, legend: { display: false } } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1216
  });
1217
  }
1218
 
1219
+ function initAudioPlayers() {
1220
+ heatmapAudio = new Audio('heatmap.mp3');
1221
+ radarAudio = new Audio('radar.mp3');
1222
+ heatmapAudio.addEventListener('timeupdate', () => updateProgress('heatmap'));
1223
+ heatmapAudio.addEventListener('loadedmetadata', () => document.getElementById('heatmapDuration').innerText = formatTime(heatmapAudio.duration));
1224
+ heatmapAudio.addEventListener('ended', () => { const icon = document.getElementById('heatmapPlayIcon'); if(icon) icon.className = 'fas fa-play'; });
1225
+ radarAudio.addEventListener('timeupdate', () => updateProgress('radar'));
1226
+ radarAudio.addEventListener('loadedmetadata', () => document.getElementById('radarDuration').innerText = formatTime(radarAudio.duration));
1227
+ radarAudio.addEventListener('ended', () => { const icon = document.getElementById('radarPlayIcon'); if(icon) icon.className = 'fas fa-play'; });
1228
+ const heatVol = document.getElementById('heatmapVolume');
1229
+ const radVol = document.getElementById('radarVolume');
1230
+ if(heatVol) heatmapAudio.volume = parseFloat(heatVol.value);
1231
+ if(radVol) radarAudio.volume = parseFloat(radVol.value);
1232
  }
1233
 
1234
+ function toggleAudio(audioId) {
1235
+ let audio = audioId === 'heatmapAudio' ? heatmapAudio : radarAudio;
1236
+ let playIcon = document.getElementById(audioId === 'heatmapAudio' ? 'heatmapPlayIcon' : 'radarPlayIcon');
1237
+ if(audio.paused) {
1238
+ if(audioId === 'heatmapAudio' && radarAudio && !radarAudio.paused) { radarAudio.pause(); const rIcon = document.getElementById('radarPlayIcon'); if(rIcon) rIcon.className = 'fas fa-play'; }
1239
+ else if(audioId === 'radarAudio' && heatmapAudio && !heatmapAudio.paused) { heatmapAudio.pause(); const hIcon = document.getElementById('heatmapPlayIcon'); if(hIcon) hIcon.className = 'fas fa-play'; }
1240
+ audio.play();
1241
+ if(playIcon) playIcon.className = 'fas fa-pause';
1242
+ } else { audio.pause(); if(playIcon) playIcon.className = 'fas fa-play'; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  }
1244
 
1245
+ function updateProgress(audioType) {
1246
+ let audio = audioType === 'heatmap' ? heatmapAudio : radarAudio;
1247
+ let fill = document.getElementById(audioType === 'heatmap' ? 'heatmapProgressFill' : 'radarProgressFill');
1248
+ let timeSpan = document.getElementById(audioType === 'heatmap' ? 'heatmapCurrentTime' : 'radarCurrentTime');
1249
+ if(audio && !isNaN(audio.duration) && audio.duration > 0) {
1250
+ let percent = (audio.currentTime / audio.duration) * 100;
1251
+ if(fill) fill.style.width = percent + '%';
1252
+ if(timeSpan) timeSpan.innerText = formatTime(audio.currentTime);
1253
+ }
1254
  }
1255
 
1256
+ function seekAudio(audioId, event) {
1257
+ let audio = audioId === 'heatmapAudio' ? heatmapAudio : radarAudio;
1258
+ let rect = event.currentTarget.getBoundingClientRect();
1259
+ let x = event.clientX - rect.left;
1260
+ let percent = x / rect.width;
1261
+ if(audio && !isNaN(audio.duration) && audio.duration > 0) audio.currentTime = percent * audio.duration;
 
 
 
 
 
 
 
 
1262
  }
1263
 
1264
+ function setVolume(audioId, value) {
1265
+ let audio = audioId === 'heatmapAudio' ? heatmapAudio : radarAudio;
1266
+ if(audio) audio.volume = parseFloat(value);
1267
+ }
1268
 
1269
+ function toggleMute(audioId) {
1270
+ let audio = audioId === 'heatmapAudio' ? heatmapAudio : radarAudio;
1271
+ let slider = document.getElementById(audioId === 'heatmapAudio' ? 'heatmapVolume' : 'radarVolume');
1272
+ if(audio) {
1273
+ if(audio.volume > 0) { audio.dataset.prevVolume = audio.volume; audio.volume = 0; if(slider) slider.value = 0; }
1274
+ else { audio.volume = parseFloat(audio.dataset.prevVolume || 0.7); if(slider) slider.value = audio.volume; }
1275
+ }
1276
  }
1277
 
1278
+ function formatTime(seconds) { if(isNaN(seconds)) return "0:00"; let mins = Math.floor(seconds / 60); let secs = Math.floor(seconds % 60); return mins + ":" + (secs < 10 ? "0" + secs : secs); }
1279
+
1280
  function generateHeatmapData() { const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; const weeks = 4; let matrix = []; for(let w = 0; w < weeks; w++) { let week = []; for(let d = 0; d < 7; d++) { let base = (w * 7 + d) % 10; let level = 0; if(base < 3) level = 1; else if(base < 6) level = 2; else if(base < 8) level = 3; else level = 4; if(d === 3 || d === 4) level = Math.min(4, level+1); week.push(level); } matrix.push(week); } return { days, weeks: matrix }; }
1281
+ function renderHeatmap() { const container = document.getElementById('heatmapGrid'); if(!container) return; const { days, weeks } = generateHeatmapData(); container.innerHTML = ''; for(let i = 0; i < days.length; i++) { let dayDiv = document.createElement('div'); dayDiv.className = 'heatmap-day-label'; dayDiv.innerText = days[i]; container.appendChild(dayDiv); } for(let w = 0; w < weeks.length; w++) { for(let d = 0; d < weeks[w].length; d++) { let cell = document.createElement('div'); cell.className = `heatmap-cell level-${weeks[w][d]}`; cell.title = `${days[d]} Semaine ${w+1} : Niveau ${weeks[w][d]}`; container.appendChild(cell); } } const legendContainer = document.getElementById('heatmapLegend'); if(legendContainer) legendContainer.innerHTML = `<div class="legend-heatmap-item"><div class="legend-color-box level-0"></div><span>Aucune activité</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-1"></div><span>Faible</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-2"></div><span>Moyenne</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-3"></div><span>Élevée</span></div><div class="legend-heatmap-item"><div class="legend-color-box level-4"></div><span>Intense</span></div>`; }
1282
+ function drawSymmetryCurve() { const canvas = document.getElementById('symmetryHeatmapCurve'); if(!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.parentElement.clientWidth - 40; const height = 160; canvas.width = width; canvas.height = height; ctx.clearRect(0,0,width,height); const { days, weeks } = generateHeatmapData(); let avgIntensity = new Array(7).fill(0); for(let w = 0; w < weeks.length; w++) for(let d = 0; d < 7; d++) avgIntensity[d] += weeks[w][d]; avgIntensity = avgIntensity.map(v => v / weeks.length); let reversed = [...avgIntensity].reverse(); let combined = avgIntensity.map((v,i)=> (v + reversed[i])/2); const points = combined.map((val, idx) => ({ x: (idx / 6) * width, y: height - (val / 4) * height })); ctx.beginPath(); ctx.strokeStyle = '#1e1e1e'; ctx.lineWidth = 2.5; points.forEach((p,i)=> i===0 ? ctx.moveTo(p.x,p.y) : ctx.lineTo(p.x,p.y)); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#616161'; ctx.setLineDash([6,8]); let mirrorPoints = points.map(p => ({ x: width - p.x, y: p.y })); mirrorPoints.forEach((p,i)=> i===0 ? ctx.moveTo(p.x,p.y) : ctx.lineTo(p.x,p.y)); ctx.stroke(); points.forEach(p => { ctx.beginPath(); ctx.fillStyle = '#1e1e1e'; ctx.arc(p.x, p.y, 4, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, 2*Math.PI); ctx.fill(); }); }
1283
+ function updateCircularProgress(elementId, percent) { const circle = document.getElementById(elementId); if(!circle) return; const r = 54; const circ = 2 * Math.PI * r; const offset = circ - (percent / 100) * circ; circle.style.strokeDasharray = `${circ}`; circle.style.strokeDashoffset = offset; }
1284
  function updateIndicators() { document.getElementById('regularityPercent').innerText = '90%'; document.getElementById('deadlinePercent').innerText = '88%'; updateCircularProgress('regularityCircleProgress', 90); updateCircularProgress('deadlineCircleProgress', 88); document.getElementById('effortPattern').innerHTML = "Effort concentré (Pic d'activité jeudi/vendredi)"; document.getElementById('persistenceScore').innerHTML = "Très élevée"; }
1285
  function setDashboardView(view) { if(view === 'combined') { renderHeatmap(); drawSymmetryCurve(); updateIndicators(); initRadarChart(); } }
1286
+ function initCompetencesSelect() { const select = document.getElementById('competence'); if(select) select.innerHTML = competencesData.map(c => `<option value="${c.id}">${c.label}</option>`).join(''); }
1287
+ function generatePlanner() { const competence = document.getElementById('competence')?.value || "comprendre_mots"; const competenceLabel = competencesData.find(c=>c.id===competence)?.label || "Comprendre les mots"; const startDate = new Date(document.getElementById('start')?.value || "2024-01-15"); const sessionDur = document.getElementById('sessionDuration')?.value || "20"; const method = document.querySelector('input[name="productivityMethod"]:checked')?.value || "pomodoro"; const timeSlot = document.querySelector('#timeSlotsGroup input:checked')?.value || "08:00-10:00"; const retention = document.getElementById('retentionQuality')?.value || "2"; const intervals = [1,7,16,35,70,140]; const sessions = []; for(let i=0;i<intervals.length;i++) { let currentDate = new Date(startDate); if(i>0) currentDate.setDate(startDate.getDate() + intervals[i-1]); else currentDate = new Date(startDate); const sessionTypes = ["Apprentissage","Consolidation","Consolidation","Maintien","Maintien","Maintien"]; sessions.push({ id:i+1, date:new Date(currentDate), dateStr:currentDate.toLocaleDateString('fr-FR'), competence:competenceLabel, duration:sessionDur+" minutes", interval:intervals[i]+" jours", type:sessionTypes[i], strategy:"Révision active", productivity:method==='pomodoro'?'Pomodoro':'Deep Work', optimalTime:timeSlot }); } currentSessions = sessions; renderPlanningTable(sessions); updatePlanningStats(sessions, retention, sessionDur); drawSpacedRepetitionCurve(sessions); showToast("Planning généré"); showTab('planner'); }
1288
+ function generateDefaultPlanning() { const startDate = new Date("2024-01-17"); const intervals = [1,7,16,35,70,140]; const sessions = []; for(let i=0;i<intervals.length;i++) { let currentDate = new Date(startDate); if(i>0) currentDate.setDate(startDate.getDate() + (i===1?1:intervals[i-1])); else currentDate = new Date(startDate); sessions.push({ id:i+1, date:new Date(currentDate), dateStr:currentDate.toLocaleDateString('fr-FR'), competence:"Comprendre les mots", duration:"20 minutes", interval:intervals[i]+" jours", type:i===0?"Apprentissage":"Consolidation", strategy:"Révision active", productivity:"Pomodoro", optimalTime:"08:00-10:00" }); } currentSessions = sessions; renderPlanningTable(sessions); updatePlanningStats(sessions,"2","20"); drawSpacedRepetitionCurve(sessions); }
1289
+ function renderPlanningTable(sessions) { const tbody = document.getElementById('sessionsTableBody'); if(tbody) tbody.innerHTML = sessions.map(s => `<tr><td><strong>${s.dateStr}</strong></td><td>R${s.id}</td><td>${s.competence}</td><td>${s.duration}</td><td>${s.interval}</td><td><span class="session-type-badge">${s.type}</span></td><td>${s.strategy}</td><td>${s.productivity}</td><td>${s.optimalTime}</td></tr>`).join(''); }
1290
+ function updatePlanningStats(sessions, quality, duration) { document.getElementById('statSessions').innerText = sessions.length; if(sessions.length) { const totalDays = Math.ceil((sessions[sessions.length-1].date - sessions[0].date) / 86400000); document.getElementById('statTotalDays').innerText = totalDays; } const qualityMap = {'0':'1/4','1':'2/4','2':'3/4','3':'4/4'}; document.getElementById('statQuality').innerText = qualityMap[quality] || "3/4"; document.getElementById('statDuration').innerText = duration + "min"; }
1291
+ function drawSpacedRepetitionCurve(sessions) { if(!sessions.length) return; const canvas = document.getElementById('spacedRepetitionCurve'); if(!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.parentElement.clientWidth - 40; const height = 180; canvas.width = width; canvas.height = height; ctx.clearRect(0,0,width,height); const start = sessions[0].date.getTime(); const end = sessions[sessions.length-1].date.getTime(); const totalDays = (end - start) / 86400000; ctx.beginPath(); ctx.strokeStyle = '#1e1e1e'; ctx.lineWidth = 2.5; let first = true; for(let x = 0; x <= width; x++) { const dayProg = (x/width)*totalDays; const ret = Math.exp(-dayProg / (0.4*30)) * 100; const y = height - (ret/100)*height; if(first) { ctx.moveTo(x,y); first=false; } else ctx.lineTo(x,y); } ctx.stroke(); sessions.forEach(s => { const x = ((s.date.getTime() - start) / (end - start)) * width; const dayProg = (s.date.getTime() - start)/86400000; const retAt = Math.exp(-dayProg / (0.4*30)) * 100; const y = height - (retAt/100)*height; ctx.beginPath(); ctx.fillStyle = '#424242'; ctx.arc(x, y, 5, 0, 2*Math.PI); ctx.fill(); ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(x, y, 2, 0, 2*Math.PI); ctx.fill(); }); }
1292
+ function exportPlanning() { let csv = "Date,Session,Compétence,Durée,Intervalle,Type,Stratégie,Productivité,Heure Optimale\n"; currentSessions.forEach(s => { csv += `"${s.dateStr}","R${s.id}","${s.competence}","${s.duration}","${s.interval}","${s.type}","${s.strategy}","${s.productivity}","${s.optimalTime}"\n`; }); const blob = new Blob([csv], {type: 'text/csv'}); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = 'planning_perseverai.csv'; link.click(); showToast("Exporté CSV"); }
 
 
 
 
 
 
 
 
 
1293
  function generateRandomEvent() { const template = eventTemplates[Math.floor(Math.random() * eventTemplates.length)]; const newEvent = { id: Date.now() + Math.random(), date: new Date().toLocaleString(), type: template.type, title: template.title, message: template.message, priority: template.priority, status: 'pending' }; eventsList.unshift(newEvent); saveEvents(); renderEventsTable(); updateEventStats(); showToast(`Nouvel événement: ${template.title}`); }
1294
+ function executeAction(eventId, actionType, actionLabel) { const idx = eventsList.findIndex(e => e.id == eventId); if(idx === -1) return; eventsList[idx].status = actionType === 'ACCEPT' ? 'accepted' : (actionType === 'REJECT' ? 'rejected' : 'hold'); renderEventsTable(); updateEventStats(); saveEvents(); showToast(`${actionLabel}`); }
1295
+ function renderEventsTable() { const tbody = document.getElementById('notificationsTableBody'); if(!tbody) return; if(eventsList.length === 0) { tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;">Aucun événement</td></tr>'; return; } tbody.innerHTML = eventsList.map(e => `<tr><td>${e.date}</td><td>${e.type}</td><td><strong>${e.title}</strong></td><td>${e.message}</td><td><span class="priority-${e.priority}">${e.priority}</span></td><td><span class="status-badge ${e.status === 'accepted' ? 'status-accepted' : 'status-pending'}">${e.status === 'accepted' ? 'Accepté' : 'En attente'}</span></td><td class="action-cell"><button class="action-icon-btn action-accept" onclick="executeAction(${e.id}, 'ACCEPT', 'Accepté')">✓ Accepter</button><button class="action-icon-btn action-reject" onclick="executeAction(${e.id}, 'REJECT', 'Rejeté')">✗ Rejeter</button><button class="action-icon-btn action-hold" onclick="executeAction(${e.id}, 'HOLD', 'En attente')">⏸ Attente</button></td></tr>`).join(''); }
1296
  function updateEventStats() { document.getElementById('totalEvents').innerText = eventsList.length; document.getElementById('pendingEvents').innerText = eventsList.filter(e => e.status === 'pending').length; document.getElementById('acceptedEvents').innerText = eventsList.filter(e => e.status === 'accepted').length; if(efficiencyChart) efficiencyChart.data.datasets[0].data = [eventsList.filter(e=>e.status==='accepted').length, eventsList.filter(e=>e.status==='rejected').length, eventsList.filter(e=>e.status==='pending').length]; efficiencyChart?.update(); }
1297
  let efficiencyChart = null;
1298
  function createChart() { const ctx = document.getElementById('notificationEfficiencyChart')?.getContext('2d'); if(ctx) efficiencyChart = new Chart(ctx, { type: 'bar', data: { labels: ['Acceptés', 'Rejetés', 'En attente'], datasets: [{ label: 'Actions', data: [0,0,0], backgroundColor: ['#2e7d32','#c62828','#b76e0e'] }] }, options: { responsive: true } }); }
 
1300
  function loadSavedData() { const saved = localStorage.getItem('eventsList'); if(saved) eventsList = JSON.parse(saved); }
1301
  function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); document.getElementById(tabId).classList.add('active'); document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active')); if(window.event?.target) window.event.target.classList.add('active'); if(tabId === 'dashboard') { renderHeatmap(); drawSymmetryCurve(); updateIndicators(); initRadarChart(); } if(tabId === 'planner') { if(currentSessions.length === 0) generateDefaultPlanning(); else drawSpacedRepetitionCurve(currentSessions); } if(tabId === 'notifications') { renderEventsTable(); updateEventStats(); } if(tabId === 'profile') { loadProfileFromStorage(); updateProfileUI(); } if(tabId === 'journal') { loadJournalEntries(); } }
1302
  function showToast(msg) { let div = document.createElement('div'); div.style.cssText = `position:fixed; bottom:20px; right:20px; background:#1e293b; color:white; padding:12px 20px; border-radius:8px; z-index:1000;`; div.innerHTML = msg; document.body.appendChild(div); setTimeout(()=>div.remove(), 2500); }
1303
+ function setupRatingListeners() { document.querySelectorAll('.big5-rating .rating-btn, .motivation-rating .rating-btn').forEach(btn => btn.addEventListener('click', function() { const container = this.closest('.big5-rating, .motivation-rating'); const trait = container.dataset.trait || container.dataset.motivation; const value = parseInt(this.dataset.value); profileData[trait] = value; updateRatingUI(container, value); updateProfileUI(); saveProfileToStorage(); })); }
1304
+ function updateRatingUI(container, value) { container.querySelectorAll('.rating-btn').forEach(btn => { btn.classList.remove('active'); if(parseInt(btn.dataset.value) === value) btn.classList.add('active'); }); }
1305
+ function updateProfileUI() { document.getElementById('summaryExtraversion').innerText = profileData.extraversion; document.getElementById('summaryOpenness').innerText = profileData.openness; document.getElementById('summaryConscientiousness').innerText = profileData.conscientiousness; document.getElementById('summaryIntrinsic').innerText = profileData.intrinsic; document.getElementById('summaryIntrojected').innerText = profileData.introjected; document.getElementById('neuroticismScore').innerText = profileData.neuroticism; document.getElementById('introjectedScore').innerText = profileData.introjected; let completed = Object.values(profileData).filter(v => v).length; const percent = Math.round((completed / 7) * 100); document.getElementById('profileCompletionPercent').innerText = percent + '%'; document.getElementById('profileProgressFill').style.width = percent + '%'; document.getElementById('completedCount').innerText = Math.min(completed, 5); let level = "Débutant"; if(profileData.conscientiousness >= 4 && profileData.intrinsic >= 4) level = "Avancé"; else if(profileData.conscientiousness >= 3) level = "Intermédiaire"; document.getElementById('profileLevel').innerText = level; document.getElementById('recommendationsContainer').innerHTML = ["◼ Qualité du sommeil: Maintenez 7-8 heures.", "◼ Planification: Renforcez votre organisation."].map(r => `<p>${r}</p>`).join(''); }
1306
+ function autoFillProfile() { profileData = { extraversion: 4, openness: 4, conscientiousness: 5, agreeableness: 4, neuroticism: 2, intrinsic: 5, introjected: 3 }; document.querySelectorAll('.big5-rating, .motivation-rating').forEach(container => { const key = container.dataset.trait || container.dataset.motivation; if(profileData[key]) updateRatingUI(container, profileData[key]); }); updateProfileUI(); saveProfileToStorage(); showToast("Profil auto-rempli"); }
1307
+ function saveProfileData() { saveProfileToStorage(); showToast("Profil sauvegardé"); }
1308
+ function finalizeProfile() { saveProfileToStorage(); showToast("Profil finalisé"); }
1309
+ function saveProfileToStorage() { localStorage.setItem('perseverAI_profile', JSON.stringify(profileData)); }
1310
+ function loadProfileFromStorage() { const saved = localStorage.getItem('perseverAI_profile'); if(saved) Object.assign(profileData, JSON.parse(saved)); updateProfileUI(); }
1311
+ function loadJournalEntries() { const saved = localStorage.getItem('perseverAI_journal'); if(saved) journalEntries = JSON.parse(saved); renderJournalEntries(); }
1312
+ function saveJournalEntry() { const selectedDate = document.getElementById('journalDate')?.value || new Date().toISOString().split('T')[0]; const planned = document.getElementById('plannedText')?.value.trim() || ""; const doing = document.getElementById('doingText')?.value.trim() || ""; const done = document.getElementById('doneText')?.value.trim() || ""; const activeRating = document.querySelector('#selfRatingGroup .star-btn.active'); let selfRating = activeRating ? parseInt(activeRating.dataset.rating) : null; if(!planned && !doing && !done) { showToast("Renseignez au moins un champ"); return; } const newEntry = { id: Date.now(), date: selectedDate, dateFormatted: new Date(selectedDate).toLocaleDateString('fr-FR'), planned, doing, done, selfRating }; journalEntries.unshift(newEntry); localStorage.setItem('perseverAI_journal', JSON.stringify(journalEntries)); document.getElementById('plannedText').value = ""; document.getElementById('doingText').value = ""; document.getElementById('doneText').value = ""; document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => btn.classList.remove('active')); renderJournalEntries(); showToast("Journal sauvegardé"); }
1313
+ function clearJournalForm() { document.getElementById('plannedText').value = ""; document.getElementById('doingText').value = ""; document.getElementById('doneText').value = ""; document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => btn.classList.remove('active')); document.getElementById('journalDate').value = new Date().toISOString().split('T')[0]; showToast("Formulaire effacé"); }
1314
+ function renderJournalEntries() { const container = document.getElementById('journalEntriesList'); if(!container) return; if(journalEntries.length === 0) { container.innerHTML = '<div style="text-align:center; color:var(--gray-500); padding:20px;"><i class="fas fa-book-open"></i> Aucune entrée</div>'; return; } container.innerHTML = journalEntries.map(entry => `<div class="journal-entry-item"><div class="journal-entry-date"><i class="far fa-calendar-alt"></i> ${entry.dateFormatted || entry.date}</div>${entry.planned ? `<div><span class="journal-badge"><i class="fas fa-tasks"></i> Planifié</span> ${escapeHtml(entry.planned)}</div>` : ''}${entry.doing ? `<div><span class="journal-badge"><i class="fas fa-sync-alt"></i> En cours</span> ${escapeHtml(entry.doing)}</div>` : ''}${entry.done ? `<div><span class="journal-badge"><i class="fas fa-check-circle"></i> Accompli</span> ${escapeHtml(entry.done)}</div>` : ''}${entry.selfRating ? `<div><span class="journal-badge"><i class="fas fa-star"></i> Auto-évaluation</span> ${entry.selfRating}/5</div>` : ''}</div>`).join(''); }
1315
+ function escapeHtml(str) { return str.replace(/[&<>]/g, function(m){ if(m==='&') return '&amp;'; if(m==='<') return '&lt;'; if(m==='>') return '&gt;'; return m;}); }
1316
+ function setupRatingStars() { document.querySelectorAll('#selfRatingGroup .star-btn').forEach(btn => { btn.addEventListener('click', function() { document.querySelectorAll('#selfRatingGroup .star-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); }); }); }
1317
 
1318
  document.addEventListener('DOMContentLoaded', () => {
1319
  initCompetencesSelect();
 
1334
  updateCircularProgress('regularityCircleProgress', 90);
1335
  updateCircularProgress('deadlineCircleProgress', 88);
1336
  const todayDate = new Date().toISOString().split('T')[0];
1337
+ if(document.getElementById('journalDate')) document.getElementById('journalDate').value = todayDate;
 
1338
  initRadarChart();
1339
+ initAudioPlayers();
1340
  });
1341
  function initProfileModule() { loadProfileFromStorage(); setupRatingListeners(); updateProfileUI(); }
1342
  window.addEventListener('resize', () => { if(document.getElementById('dashboard').classList.contains('active')) { drawSymmetryCurve(); if(radarChart) radarChart.resize(); } if(document.getElementById('planner').classList.contains('active') && currentSessions.length > 0) drawSpacedRepetitionCurve(currentSessions); });
1343
  </script>
1344
  </body>
1345
+ </html>
1346
+ ```