354 lines
		
	
	
		
			No EOL
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			No EOL
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| {% extends "base.html" %}
 | |
| 
 | |
| {% block title %}Biking Adventures - Blake Ridgway{% endblock %}
 | |
| 
 | |
| {% block content %}
 | |
| <div class="row">
 | |
|     <div class="col-md-8">
 | |
|         <h1>Biking Adventures</h1>
 | |
|         <p class="lead">Exploring the world on two wheels as an ultra endurance cyclist - powered by real-time Strava data!</p>
 | |
| 
 | |
|         <div class="d-flex justify-content-between align-items-center mb-4">
 | |
|             <h3><i class="fas fa-route text-primary me-2"></i>Recent Adventures</h3>
 | |
|             <button class="btn btn-outline-primary btn-sm" onclick="refreshStravaData()">
 | |
|                 <span id="refresh-icon">🔄</span> Refresh Data
 | |
|             </button>
 | |
|         </div>
 | |
| 
 | |
|         <div id="recent-activities">
 | |
|             {% if recent_activities %}
 | |
|                 {% for activity in recent_activities %}
 | |
|                 <div class="card mb-3 shadow-sm">
 | |
|                     <div class="card-body">
 | |
|                         <div class="d-flex justify-content-between align-items-start">
 | |
|                             <div>
 | |
|                                 <h5 class="card-title">
 | |
|                                     <i class="fas fa-bicycle text-success me-2"></i>{{ activity.name }}
 | |
|                                 </h5>
 | |
|                                 <p class="card-text text-muted">Latest ride from Strava - automatically synced!</p>
 | |
|                             </div>
 | |
|                             <span class="badge bg-primary">{{ activity.date }}</span>
 | |
|                         </div>
 | |
|                         <div class="row g-2 mt-2">
 | |
|                             <div class="col-6 col-md-3">
 | |
|                                 <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                                     <i class="fas fa-road text-primary"></i>
 | |
|                                     <div><strong>{{ activity.distance }}</strong></div>
 | |
|                                     <small class="text-muted">Miles</small>
 | |
|                                 </div>
 | |
|                             </div>
 | |
|                             <div class="col-6 col-md-3">
 | |
|                                 <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                                     <i class="fas fa-mountain text-warning"></i>
 | |
|                                     <div><strong>{{ activity.elevation }}</strong></div>
 | |
|                                     <small class="text-muted">Feet</small>
 | |
|                                 </div>
 | |
|                             </div>
 | |
|                             <div class="col-6 col-md-3">
 | |
|                                 <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                                     <i class="fas fa-clock text-info"></i>
 | |
|                                     <div><strong>{{ activity.time }}</strong></div>
 | |
|                                     <small class="text-muted">Duration</small>
 | |
|                                 </div>
 | |
|                             </div>
 | |
|                             <div class="col-6 col-md-3">
 | |
|                                 <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                                     <i class="fas fa-tachometer-alt text-success"></i>
 | |
|                                     <div><strong>{{ activity.avg_speed or 'N/A' }}</strong></div>
 | |
|                                     <small class="text-muted">Avg MPH</small>
 | |
|                                 </div>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 {% endfor %}
 | |
|             {% else %}
 | |
|                 <div class="alert alert-info border-start border-primary border-4">
 | |
|                     <i class="fas fa-info-circle me-2"></i>Loading Strava data...
 | |
|                 </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
| 
 | |
|         <h3 class="mt-5 mb-4"><i class="fas fa-chart-line text-success me-2"></i>2025 Cycling Performance</h3>
 | |
|         <div class="row g-4" id="cycling-stats">
 | |
|             {% if ytd_stats %}
 | |
|             <div class="col-md-6 col-lg-3">
 | |
|                 <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                     <div class="stat-icon mb-3">
 | |
|                         <i class="fas fa-road fa-2x text-primary"></i>
 | |
|                     </div>
 | |
|                     <h4 class="text-primary mb-2">{{ ytd_stats.distance }}</h4>
 | |
|                     <p class="mb-0">Miles This Year</p>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <div class="col-md-6 col-lg-3">
 | |
|                 <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                     <div class="stat-icon mb-3">
 | |
|                         <i class="fas fa-bicycle fa-2x text-success"></i>
 | |
|                     </div>
 | |
|                     <h4 class="text-success mb-2">{{ ytd_stats.count }}</h4>
 | |
|                     <p class="mb-0">Rides Completed</p>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <div class="col-md-6 col-lg-3">
 | |
|                 <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                     <div class="stat-icon mb-3">
 | |
|                         <i class="fas fa-mountain fa-2x text-warning"></i>
 | |
|                     </div>
 | |
|                     <h4 class="text-warning mb-2">{{ ytd_stats.elevation }}</h4>
 | |
|                     <p class="mb-0">Feet Climbed</p>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <div class="col-md-6 col-lg-3">
 | |
|                 <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                     <div class="stat-icon mb-3">
 | |
|                         <i class="fas fa-clock fa-2x text-info"></i>
 | |
|                     </div>
 | |
|                     <h4 class="text-info mb-2">{{ ytd_stats.time }}</h4>
 | |
|                     <p class="mb-0">Hours Riding</p>
 | |
|                 </div>
 | |
|             </div>
 | |
|             {% else %}
 | |
|             <div class="col-12">
 | |
|                 <div class="alert alert-warning border-start border-warning border-4">
 | |
|                     <i class="fas fa-exclamation-triangle me-2"></i>Unable to load Strava stats. Please check your API connection.
 | |
|                 </div>
 | |
|             </div>
 | |
|             {% endif %}
 | |
|         </div>
 | |
|     </div>
 | |
| 
 | |
|     <div class="col-md-4">
 | |
|         <div class="card mb-4 shadow-sm">
 | |
|             <div class="card-header bg-success text-white">
 | |
|                 <h5 class="mb-0"><i class="fas fa-bicycle me-2"></i>My Bikes</h5>
 | |
|             </div>
 | |
|             <div class="card-body">
 | |
|                 <div class="bike-item mb-4">
 | |
|                     <div class="d-flex align-items-center mb-2">
 | |
|                         <i class="fas fa-road fa-lg text-primary me-3"></i>
 | |
|                         <div>
 | |
|                             <h6 class="mb-1">Road Bike</h6>
 | |
|                             <p class="mb-0 text-muted">Giant TCR Advanced 2</p>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="bike-specs">
 | |
|                         <span class="badge bg-light text-dark me-1">Carbon Frame</span>
 | |
|                         <span class="badge bg-light text-dark me-1">Aero</span>
 | |
|                         <span class="badge bg-light text-dark">Racing</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
| 
 | |
|                 <div class="bike-item">
 | |
|                     <div class="d-flex align-items-center mb-2">
 | |
|                         <i class="fas fa-mountain fa-lg text-warning me-3"></i>
 | |
|                         <div>
 | |
|                             <h6 class="mb-1">Gravel Bike</h6>
 | |
|                             <p class="mb-0 text-muted">Canyon Endurace CF SL 8</p>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="bike-specs">
 | |
|                         <span class="badge bg-light text-dark me-1">Endurance</span>
 | |
|                         <span class="badge bg-light text-dark me-1">All-Road</span>
 | |
|                         <span class="badge bg-light text-dark">Adventure</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
| 
 | |
|         <div class="card mb-4 shadow-sm">
 | |
|             <div class="card-header bg-info text-white">
 | |
|                 <h5 class="mb-0"><i class="fas fa-trophy me-2"></i>Cycling Goals 2025</h5>
 | |
|             </div>
 | |
|             <div class="card-body">
 | |
|                 <div class="goal-item mb-3">
 | |
|                     <div class="d-flex justify-content-between align-items-center">
 | |
|                         <span><i class="fas fa-road text-primary me-2"></i>Annual Distance</span>
 | |
|                         <span class="badge bg-primary">5,000 mi</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div class="goal-item mb-3">
 | |
|                     <div class="d-flex justify-content-between align-items-center">
 | |
|                         <span><i class="fas fa-mountain text-warning me-2"></i>Elevation Gain</span>
 | |
|                         <span class="badge bg-warning">250,000 ft</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div class="goal-item mb-3">
 | |
|                     <div class="d-flex justify-content-between align-items-center">
 | |
|                         <span><i class="fas fa-bicycle text-success me-2"></i>Total Rides</span>
 | |
|                         <span class="badge bg-success">200 rides</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div class="goal-item">
 | |
|                     <div class="d-flex justify-content-between align-items-center">
 | |
|                         <span><i class="fas fa-medal text-danger me-2"></i>Ultra Events</span>
 | |
|                         <span class="badge bg-danger">3 events</span>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
| 
 | |
|         <div class="card shadow-sm">
 | |
|             <div class="card-header bg-secondary text-white">
 | |
|                 <h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>Data Source</h5>
 | |
|             </div>
 | |
|             <div class="card-body">
 | |
|                 <p class="mb-2">
 | |
|                     <i class="fab fa-strava text-danger me-2"></i>
 | |
|                     <strong>Powered by Strava API</strong>
 | |
|                 </p>
 | |
|                 <p class="small text-muted mb-3">
 | |
|                     Real-time cycling data automatically synced from my Strava account.
 | |
|                 </p>
 | |
|                 <p class="small text-muted mb-0">
 | |
|                     <strong>Last updated:</strong> <span id="last-updated">Just now</span>
 | |
|                 </p>
 | |
|             </div>
 | |
|         </div>
 | |
|     </div>
 | |
| </div>
 | |
| 
 | |
| <script>
 | |
| async function refreshStravaData() {
 | |
|     const refreshIcon = document.getElementById('refresh-icon');
 | |
|     refreshIcon.style.animation = 'spin 1s linear infinite';
 | |
| 
 | |
|     try {
 | |
|         const response = await fetch('/api/strava-stats');
 | |
|         const data = await response.json();
 | |
| 
 | |
|         // Update stats
 | |
|         if (data.ytd_stats) {
 | |
|             updateStats(data.ytd_stats);
 | |
|         }
 | |
| 
 | |
|         // Update recent activities
 | |
|         if (data.recent_activities) {
 | |
|             updateRecentActivities(data.recent_activities);
 | |
|         }
 | |
| 
 | |
|         // Update timestamp
 | |
|         document.getElementById('last-updated').textContent = new Date().toLocaleString();
 | |
| 
 | |
|     } catch (error) {
 | |
|         console.error('Error refreshing Strava data:', error);
 | |
|     } finally {
 | |
|         refreshIcon.style.animation = '';
 | |
|     }
 | |
| }
 | |
| 
 | |
| function updateStats(stats) {
 | |
|     const statsContainer = document.getElementById('cycling-stats');
 | |
|     statsContainer.innerHTML = `
 | |
|         <div class="col-md-6 col-lg-3">
 | |
|             <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                 <div class="stat-icon mb-3">
 | |
|                     <i class="fas fa-road fa-2x text-primary"></i>
 | |
|                 </div>
 | |
|                 <h4 class="text-primary mb-2">${stats.distance}</h4>
 | |
|                 <p class="mb-0">Miles This Year</p>
 | |
|             </div>
 | |
|         </div>
 | |
|         <div class="col-md-6 col-lg-3">
 | |
|             <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                 <div class="stat-icon mb-3">
 | |
|                     <i class="fas fa-bicycle fa-2x text-success"></i>
 | |
|                 </div>
 | |
|                 <h4 class="text-success mb-2">${stats.count}</h4>
 | |
|                 <p class="mb-0">Rides Completed</p>
 | |
|             </div>
 | |
|         </div>
 | |
|         <div class="col-md-6 col-lg-3">
 | |
|             <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                 <div class="stat-icon mb-3">
 | |
|                     <i class="fas fa-mountain fa-2x text-warning"></i>
 | |
|                 </div>
 | |
|                 <h4 class="text-warning mb-2">${stats.elevation}</h4>
 | |
|                 <p class="mb-0">Feet Climbed</p>
 | |
|             </div>
 | |
|         </div>
 | |
|         <div class="col-md-6 col-lg-3">
 | |
|             <div class="skill-card text-center p-4 bg-light rounded-3 shadow-sm h-100">
 | |
|                 <div class="stat-icon mb-3">
 | |
|                     <i class="fas fa-clock fa-2x text-info"></i>
 | |
|                 </div>
 | |
|                 <h4 class="text-info mb-2">${stats.time}</h4>
 | |
|                 <p class="mb-0">Hours Riding</p>
 | |
|             </div>
 | |
|         </div>
 | |
|     `;
 | |
| }
 | |
| 
 | |
| function updateRecentActivities(activities) {
 | |
|     const container = document.getElementById('recent-activities');
 | |
|     if (activities.length === 0) {
 | |
|         container.innerHTML = '<div class="alert alert-info border-start border-primary border-4"><i class="fas fa-info-circle me-2"></i>No recent activities found.</div>';
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     container.innerHTML = activities.map(activity => `
 | |
|         <div class="card mb-3 shadow-sm">
 | |
|             <div class="card-body">
 | |
|                 <div class="d-flex justify-content-between align-items-start">
 | |
|                     <div>
 | |
|                         <h5 class="card-title">
 | |
|                             <i class="fas fa-bicycle text-success me-2"></i>${activity.name}
 | |
|                         </h5>
 | |
|                         <p class="card-text text-muted">Latest ride from Strava - automatically synced!</p>
 | |
|                     </div>
 | |
|                     <span class="badge bg-primary">${activity.date}</span>
 | |
|                 </div>
 | |
|                 <div class="row g-2 mt-2">
 | |
|                     <div class="col-6 col-md-3">
 | |
|                         <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                             <i class="fas fa-road text-primary"></i>
 | |
|                             <div><strong>${activity.distance}</strong></div>
 | |
|                             <small class="text-muted">Miles</small>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="col-6 col-md-3">
 | |
|                         <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                             <i class="fas fa-mountain text-warning"></i>
 | |
|                             <div><strong>${activity.elevation}</strong></div>
 | |
|                             <small class="text-muted">Feet</small>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="col-6 col-md-3">
 | |
|                         <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                             <i class="fas fa-clock text-info"></i>
 | |
|                             <div><strong>${activity.time}</strong></div>
 | |
|                             <small class="text-muted">Duration</small>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div class="col-6 col-md-3">
 | |
|                         <div class="stat-box p-2 bg-light rounded text-center">
 | |
|                             <i class="fas fa-tachometer-alt text-success"></i>
 | |
|                             <div><strong>${activity.avg_speed || 'N/A'}</strong></div>
 | |
|                             <small class="text-muted">Avg MPH</small>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|     `).join('');
 | |
| }
 | |
| 
 | |
| // Auto-refresh every 5 minutes
 | |
| setInterval(refreshStravaData, 300000);
 | |
| </script>
 | |
| 
 | |
| <style>
 | |
| @keyframes spin {
 | |
|     from { transform: rotate(0deg); }
 | |
|     to { transform: rotate(360deg); }
 | |
| }
 | |
| 
 | |
| .stat-box {
 | |
|     transition: transform 0.2s ease;
 | |
| }
 | |
| 
 | |
| .stat-box:hover {
 | |
|     transform: translateY(-2px);
 | |
| }
 | |
| </style>
 | |
| {% endblock %} | 
