Created
January 29, 2026 16:38
-
-
Save brandonbryant12/c55e42fffa291d87a0b481a5181ee531 to your computer and use it in GitHub Desktop.
Fitness Dashboard - Interactive workout visualization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Brandon's Fitness Dashboard</title> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); | |
| min-height: 100vh; | |
| color: #fff; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| h1 { | |
| font-size: 2.5em; | |
| background: linear-gradient(90deg, #00d9ff, #00ff88); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| margin-bottom: 10px; | |
| } | |
| .subtitle { | |
| color: #8892b0; | |
| font-size: 1.1em; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .stat-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 16px; | |
| padding: 24px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| backdrop-filter: blur(10px); | |
| transition: transform 0.3s ease; | |
| } | |
| .stat-card:hover { | |
| transform: translateY(-5px); | |
| } | |
| .stat-value { | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| background: linear-gradient(90deg, #00d9ff, #00ff88); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .stat-label { | |
| color: #8892b0; | |
| font-size: 0.9em; | |
| margin-top: 5px; | |
| } | |
| .charts-section { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .chart-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 16px; | |
| padding: 24px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .chart-card h3 { | |
| margin-bottom: 20px; | |
| color: #e6f1ff; | |
| } | |
| .workout-log { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 16px; | |
| padding: 24px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .workout-log h3 { | |
| margin-bottom: 20px; | |
| color: #e6f1ff; | |
| } | |
| .workout-day { | |
| background: rgba(255, 255, 255, 0.03); | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-bottom: 15px; | |
| border-left: 4px solid #00ff88; | |
| } | |
| .workout-date { | |
| font-size: 1.2em; | |
| font-weight: 600; | |
| color: #00d9ff; | |
| margin-bottom: 10px; | |
| } | |
| .workout-summary { | |
| color: #8892b0; | |
| font-size: 0.9em; | |
| margin-bottom: 15px; | |
| } | |
| .exercise-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .exercise-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: rgba(255, 255, 255, 0.05); | |
| padding: 12px 16px; | |
| border-radius: 8px; | |
| } | |
| .exercise-name { | |
| font-weight: 500; | |
| text-transform: capitalize; | |
| } | |
| .exercise-details { | |
| color: #8892b0; | |
| font-size: 0.9em; | |
| } | |
| .exercise-type { | |
| display: inline-block; | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| font-size: 0.75em; | |
| text-transform: uppercase; | |
| font-weight: 600; | |
| } | |
| .type-strength { | |
| background: rgba(0, 217, 255, 0.2); | |
| color: #00d9ff; | |
| } | |
| .type-sport { | |
| background: rgba(0, 255, 136, 0.2); | |
| color: #00ff88; | |
| } | |
| .type-cardio { | |
| background: rgba(255, 107, 107, 0.2); | |
| color: #ff6b6b; | |
| } | |
| @media (max-width: 768px) { | |
| .charts-section { | |
| grid-template-columns: 1fr; | |
| } | |
| h1 { | |
| font-size: 1.8em; | |
| } | |
| .stat-value { | |
| font-size: 2em; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>💪 Fitness Dashboard</h1> | |
| <p class="subtitle">Track your gains, visualize your progress</p> | |
| </header> | |
| <div class="stats-grid" id="statsGrid"> | |
| <!-- Stats populated by JS --> | |
| </div> | |
| <div class="charts-section"> | |
| <div class="chart-card"> | |
| <h3>📊 Volume by Day</h3> | |
| <canvas id="volumeChart"></canvas> | |
| </div> | |
| <div class="chart-card"> | |
| <h3>🏋️ Exercise Distribution</h3> | |
| <canvas id="exerciseChart"></canvas> | |
| </div> | |
| </div> | |
| <div class="workout-log" id="workoutLog"> | |
| <h3>📝 Recent Workouts</h3> | |
| <!-- Populated by JS --> | |
| </div> | |
| </div> | |
| <script> | |
| // Workout data from your logs | |
| const workoutData = [ | |
| { | |
| "date": "2026-01-27", | |
| "entries": [ | |
| { | |
| "id": "db-press-1", | |
| "exercise": "dumbbell press", | |
| "type": "strength", | |
| "sets": 5, | |
| "reps": [12, 12, 12, 12, 10], | |
| "weight": "40 lbs (2 sets), 60 lbs (3 sets)", | |
| "rpe": 9, | |
| "notes": "Last set failed at 10 reps" | |
| }, | |
| { | |
| "id": "lat-raise-1", | |
| "exercise": "lateral raises", | |
| "type": "strength", | |
| "sets": 5, | |
| "reps": 12, | |
| "weight": "25 lbs" | |
| }, | |
| { | |
| "id": "circuit-1", | |
| "exercise": "incline press / bench flies circuit", | |
| "type": "strength", | |
| "sets": 5, | |
| "reps": 12, | |
| "weight": "Incline: 40 lbs, Flies: 25 lbs", | |
| "notes": "5 rounds alternating incline press and flies" | |
| } | |
| ], | |
| "duration": "30 min", | |
| "summary": "Chest day - DB press, lateral raises, incline/fly circuit" | |
| }, | |
| { | |
| "date": "2026-01-28", | |
| "entries": [ | |
| { | |
| "id": "bball-1", | |
| "exercise": "basketball", | |
| "type": "sport", | |
| "duration": "90 min", | |
| "notes": "Pickup game" | |
| }, | |
| { | |
| "id": "dl-1", | |
| "exercise": "deadlift", | |
| "type": "strength", | |
| "sets": 5, | |
| "reps": 5, | |
| "weight": "135 (warmup), 225, 245, 245, 225", | |
| "rpe": 7, | |
| "notes": "Getting back into it, felt good" | |
| } | |
| ], | |
| "summary": "Basketball + deadlifts" | |
| } | |
| ]; | |
| // Calculate stats | |
| function calculateStats() { | |
| let totalWorkouts = workoutData.length; | |
| let totalExercises = workoutData.reduce((sum, day) => sum + day.entries.length, 0); | |
| let totalSets = 0; | |
| let totalMinutes = 0; | |
| workoutData.forEach(day => { | |
| day.entries.forEach(entry => { | |
| if (entry.sets) totalSets += entry.sets; | |
| if (entry.duration) { | |
| const mins = parseInt(entry.duration); | |
| if (!isNaN(mins)) totalMinutes += mins; | |
| } | |
| }); | |
| if (day.duration) { | |
| const mins = parseInt(day.duration); | |
| if (!isNaN(mins)) totalMinutes += mins; | |
| } | |
| }); | |
| return { totalWorkouts, totalExercises, totalSets, totalMinutes }; | |
| } | |
| // Render stats cards | |
| function renderStats() { | |
| const stats = calculateStats(); | |
| const statsGrid = document.getElementById('statsGrid'); | |
| const statsHtml = ` | |
| <div class="stat-card"> | |
| <div class="stat-value">${stats.totalWorkouts}</div> | |
| <div class="stat-label">Workout Days</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">${stats.totalExercises}</div> | |
| <div class="stat-label">Exercises Logged</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">${stats.totalSets}</div> | |
| <div class="stat-label">Total Sets</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value">${stats.totalMinutes}</div> | |
| <div class="stat-label">Minutes Active</div> | |
| </div> | |
| `; | |
| statsGrid.innerHTML = statsHtml; | |
| } | |
| // Render workout log | |
| function renderWorkoutLog() { | |
| const logDiv = document.getElementById('workoutLog'); | |
| let html = '<h3>📝 Recent Workouts</h3>'; | |
| workoutData.slice().reverse().forEach(day => { | |
| html += ` | |
| <div class="workout-day"> | |
| <div class="workout-date">${formatDate(day.date)}</div> | |
| <div class="workout-summary">${day.summary || ''}</div> | |
| <div class="exercise-list"> | |
| `; | |
| day.entries.forEach(entry => { | |
| const details = getExerciseDetails(entry); | |
| html += ` | |
| <div class="exercise-item"> | |
| <div> | |
| <span class="exercise-name">${entry.exercise}</span> | |
| <span class="exercise-type type-${entry.type}">${entry.type}</span> | |
| </div> | |
| <div class="exercise-details">${details}</div> | |
| </div> | |
| `; | |
| }); | |
| html += '</div></div>'; | |
| }); | |
| logDiv.innerHTML = html; | |
| } | |
| function formatDate(dateStr) { | |
| const date = new Date(dateStr + 'T12:00:00'); | |
| return date.toLocaleDateString('en-US', { | |
| weekday: 'long', | |
| month: 'short', | |
| day: 'numeric' | |
| }); | |
| } | |
| function getExerciseDetails(entry) { | |
| if (entry.duration) return entry.duration; | |
| if (entry.sets && entry.reps) { | |
| const reps = Array.isArray(entry.reps) ? entry.reps.join('/') : entry.reps; | |
| return `${entry.sets} × ${reps}`; | |
| } | |
| return ''; | |
| } | |
| // Create volume chart | |
| function createVolumeChart() { | |
| const ctx = document.getElementById('volumeChart').getContext('2d'); | |
| const labels = workoutData.map(d => { | |
| const date = new Date(d.date + 'T12:00:00'); | |
| return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); | |
| }); | |
| const setData = workoutData.map(d => | |
| d.entries.reduce((sum, e) => sum + (e.sets || 0), 0) | |
| ); | |
| new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| label: 'Total Sets', | |
| data: setData, | |
| backgroundColor: 'rgba(0, 217, 255, 0.7)', | |
| borderColor: '#00d9ff', | |
| borderWidth: 2, | |
| borderRadius: 8 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| labels: { color: '#8892b0' } | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| grid: { color: 'rgba(255, 255, 255, 0.1)' }, | |
| ticks: { color: '#8892b0' } | |
| }, | |
| x: { | |
| grid: { display: false }, | |
| ticks: { color: '#8892b0' } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Create exercise type distribution chart | |
| function createExerciseChart() { | |
| const ctx = document.getElementById('exerciseChart').getContext('2d'); | |
| const typeCounts = {}; | |
| workoutData.forEach(day => { | |
| day.entries.forEach(entry => { | |
| typeCounts[entry.type] = (typeCounts[entry.type] || 0) + 1; | |
| }); | |
| }); | |
| new Chart(ctx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: Object.keys(typeCounts).map(t => t.charAt(0).toUpperCase() + t.slice(1)), | |
| datasets: [{ | |
| data: Object.values(typeCounts), | |
| backgroundColor: [ | |
| 'rgba(0, 217, 255, 0.8)', | |
| 'rgba(0, 255, 136, 0.8)', | |
| 'rgba(255, 107, 107, 0.8)', | |
| 'rgba(255, 193, 7, 0.8)' | |
| ], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| position: 'bottom', | |
| labels: { color: '#8892b0', padding: 20 } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // Initialize | |
| renderStats(); | |
| renderWorkoutLog(); | |
| createVolumeChart(); | |
| createExerciseChart(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment