Skip to content

Instantly share code, notes, and snippets.

@brandonbryant12
Created January 29, 2026 16:38
Show Gist options
  • Select an option

  • Save brandonbryant12/c55e42fffa291d87a0b481a5181ee531 to your computer and use it in GitHub Desktop.

Select an option

Save brandonbryant12/c55e42fffa291d87a0b481a5181ee531 to your computer and use it in GitHub Desktop.
Fitness Dashboard - Interactive workout visualization
<!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