runsync / index.html
kinda12's picture
Update index.html
a2532a1 verified
Raw History Blame Contribute Delete
98.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RunSync | Advanced Garmin Running Plan Integration</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Firebase SDKs -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, signInWithCustomToken, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, getDoc, setDoc, collection, query, where, onSnapshot, updateDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
let app;
let db;
let auth;
let userId; // Global to store the current user ID
let userAuthReady = false; // Flag to ensure auth is ready before Firestore ops
// Firebase configuration and initialization
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Fallback for local testing
if (Object.keys(firebaseConfig).length > 0) {
app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth(app);
// Authentication listener
onAuthStateChanged(auth, async (user) => {
// Ensure elements exist before trying to modify their classList
const loggedInProfile = document.getElementById('logged-in-profile');
const loginButton = document.getElementById('login-button');
const logoutButton = document.getElementById('logout-button');
const userNameDisplay = document.getElementById('user-name-display');
const authStatus = document.getElementById('auth-status');
const mobileUserNameDisplay = document.getElementById('mobile-user-name-display'); // Added this
if (user) {
userId = user.uid;
if (userNameDisplay) userNameDisplay.textContent = user.displayName || user.email || `User: ${userId.substring(0, 8)}...`;
if (mobileUserNameDisplay) mobileUserNameDisplay.textContent = user.displayName || user.email || `User: ${userId.substring(0, 8)}...`; // Update mobile display
if (authStatus) authStatus.textContent = 'Logged In';
if (loginButton) loginButton.classList.add('hidden');
if (logoutButton) logoutButton.classList.remove('hidden');
if (loggedInProfile) loggedInProfile.classList.remove('hidden'); // Ensure this element exists
} else {
// Sign in anonymously if no user is logged in
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
try {
await signInWithCustomToken(auth, __initial_auth_token);
} catch (error) {
console.error("Error signing in with custom token:", error);
await signInAnonymously(auth); // Fallback to anonymous
}
} else {
await signInAnonymously(auth);
}
userId = auth.currentUser?.uid || crypto.randomUUID(); // Fallback if anonymous fails too
if (userNameDisplay) userNameDisplay.textContent = `Guest: ${userId.substring(0, 8)}...`;
if (mobileUserNameDisplay) mobileUserNameDisplay.textContent = `Guest: ${userId.substring(0, 8)}...`; // Update mobile display
if (authStatus) authStatus.textContent = 'Guest Mode';
if (loginButton) loginButton.classList.remove('hidden');
if (logoutButton) logoutButton.classList.add('hidden');
if (loggedInProfile) loggedInProfile.classList.add('hidden'); // Ensure this element exists
}
userAuthReady = true;
// Once auth is ready, try to load user data/plan
loadUserData();
});
} else {
console.warn("Firebase config not found. Running in demo mode without persistence.");
// Simulate anonymous user for demo mode
userId = crypto.randomUUID();
const userNameDisplay = document.getElementById('user-name-display');
const authStatus = document.getElementById('auth-status');
const loginButton = document.getElementById('login-button');
const logoutButton = document.getElementById('logout-button');
const loggedInProfile = document.getElementById('logged-in-profile');
const mobileUserNameDisplay = document.getElementById('mobile-user-name-display');
if (userNameDisplay) userNameDisplay.textContent = `Guest: ${userId.substring(0, 8)}...`;
if (mobileUserNameDisplay) mobileUserNameDisplay.textContent = `Guest: ${userId.substring(0, 8)}...`;
if (authStatus) authStatus.textContent = 'Guest Mode (No Persistence)';
if (loginButton) loginButton.classList.remove('hidden');
if (logoutButton) logoutButton.classList.add('hidden');
if (loggedInProfile) loggedInProfile.classList.add('hidden');
userAuthReady = true; // Mark as ready for local demo
}
// --- Firestore Functions ---
async function saveUserData(data) {
if (!userAuthReady || !db || !userId) {
console.warn("Firebase not ready or userId missing. Cannot save data.");
return;
}
try {
// Private data for the user
const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data');
await setDoc(userDocRef, data, { merge: true });
console.log("User data saved successfully!");
} catch (e) {
console.error("Error saving user data: ", e);
}
}
async function loadUserData() {
if (!userAuthReady || !db || !userId) {
console.warn("Firebase not ready or userId missing. Cannot load data.");
return null;
}
try {
// Private data for the user
const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data');
const docSnap = await getDoc(userDocRef);
if (docSnap.exists()) {
console.log("User data loaded:", docSnap.data());
// This is where you would restore the user's plan if it was saved
const savedPlan = docSnap.data().currentPlan;
if (savedPlan) {
displayGeneratedPlan(savedPlan);
document.getElementById('plan-form').closest('div').classList.add('hidden');
document.getElementById('plan-display').classList.remove('hidden');
}
return docSnap.data();
} else {
console.log("No user data found.");
return null;
}
} catch (e) {
console.error("Error loading user data: ", e);
return null;
}
}
// Expose functions to global scope for use in main script
window.saveUserData = saveUserData;
window.loadUserData = loadUserData;
// userId and auth will be updated by onAuthStateChanged in the module scope
// It's safer to rely on the onAuthStateChanged callback for the most up-to-date userId and auth instance
</script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#009EE3',
secondary: '#FF6500',
dark: '#111827',
light: '#F9FAFB',
accent: '#00C389'
}
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Poppins', sans-serif;
scroll-behavior: smooth;
background: linear-gradient(180deg, #f0f9ff 0%, #ffffff 100%);
}
.workout-card {
transition: all 0.3s ease;
box-shadow: 0 4px 6px rgba(0, 158, 227, 0.1);
border-radius: 12px;
overflow: hidden;
}
.workout-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px rgba(0, 158, 227, 0.2);
}
.calendar-day {
transition: all 0.2s ease;
}
.calendar-day.selected {
border-color: #009EE3;
transform: scale(1.05);
}
.pace-selector input[type="radio"]:checked + label {
border-color: #009EE3;
background-color: rgba(0, 158, 227, 0.1);
transform: translateY(-3px);
box-shadow: 0 5px 10px rgba(0, 158, 227, 0.15);
}
.fitness-level input[type="radio"]:checked + label {
border-color: #009EE3; /* primary color */
background-color: rgba(0, 158, 227, 0.05); /* very light primary */
transform: translateY(-3px);
box-shadow: 0 5px 10px rgba(0, 158, 227, 0.1);
}
.tooltip-container:hover .tooltip {
display: block;
}
.garmin-badge {
background: linear-gradient(135deg, #009EE3 0%, #005E8C 100%);
}
.progress-bar {
background: linear-gradient(to right, #00C389, #009EE3);
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(0, 158, 227, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(0, 158, 227, 0); }
100% { box-shadow: 0 0 0 0 rgba(0, 158, 227, 0); }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.floating {
animation: float 4s ease-in-out infinite;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
z-index: 1000;
justify-content: center;
align-items: center;
}
.calendar-day.has-workout {
position: relative;
overflow: visible;
}
.calendar-day.has-workout::after {
content: '';
position: absolute;
bottom: 4px;
left: 50%;
transform: translateX(-50%);
width: 6px;
height: 6px;
background-color: #009EE3;
border-radius: 50%;
}
.streak-container {
background: linear-gradient(120deg, #009EE3, #00C389);
transform: rotate(-2deg);
border-radius: 16px;
margin-top: -10px;
margin-bottom: 10px;
}
.streak-box {
transform: rotate(2deg);
padding: 20px;
}
#mobile-menu {
transition: all 0.3s ease;
}
.dark-gradient {
background: linear-gradient(135deg, #111827, #1e293b);
}
.feature-icon {
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(0, 195, 137, 0.15);
}
.feature-card:hover .feature-icon {
transform: scale(1.1);
background: linear-gradient(135deg, #009EE3, #00C389);
}
.badge-animate {
animation: heartbeat 1.5s infinite;
}
@keyframes heartbeat {
0% { transform: scale(1); }
14% { transform: scale(1.1); }
28% { transform: scale(1); }
42% { transform: scale(1.1); }
70% { transform: scale(1); }
}
</style>
</head>
<body class="bg-gray-50 text-gray-800">
<!-- Header/Navigation -->
<nav class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<i class="fas fa-running text-primary text-2xl"></i>
<span class="ml-2 text-xl font-bold text-primary">RunSync</span>
</div>
<div class="hidden md:ml-6 md:flex md:space-x-8">
<a href="#" class="border-b-2 border-primary px-1 pt-1 text-sm font-medium text-gray-900">Plan</a>
<a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 border-b-2 px-1 pt-1 text-sm font-medium">Calendar</a>
<a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 border-b-2 px-1 pt-1 text-sm font-medium">Progress</a>
<a href="#" class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 border-b-2 px-1 pt-1 text-sm font-medium">Community</a>
</div>
</div>
<div class="flex items-center">
<div class="hidden md:flex items-center space-x-4">
<button class="text-sm font-medium text-gray-700 hover:text-primary flex items-center">
<i class="far fa-bell mr-1"></i>
<span class="absolute -mt-4 ml-3 w-2 h-2 bg-red-500 rounded-full"></span>
</button>
<button class="text-sm font-medium text-gray-700 hover:text-primary">
<i class="fas fa-trophy"></i>
</button>
<!-- Added this div to wrap the user profile elements -->
<div id="logged-in-profile" class="hidden flex items-center space-x-2">
<div class="relative">
<button type="button" class="flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" id="user-menu-button">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full border-2 border-primary" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</button>
</div>
<span id="user-name-display" class="text-sm font-medium text-gray-700">Loading...</span>
<span id="auth-status" class="text-xs text-gray-500"></span>
</div>
<button id="login-button" class="px-3 py-1 border border-primary text-primary rounded-md text-sm hover:bg-primary hover:text-white transition hidden">Login</button>
<button id="logout-button" class="px-3 py-1 border border-gray-300 text-gray-700 rounded-md text-sm hover:bg-gray-100 transition hidden">Logout</button>
</div>
<button id="mobile-menu-button" class="md:hidden text-gray-500">
<i class="fas fa-bars text-xl"></i>
</button>
</div>
</div>
</div>
<!-- Mobile menu -->
<div id="mobile-menu" class="hidden md:hidden bg-white shadow-lg absolute w-full z-50">
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
<a href="#" class="text-gray-900 block px-3 py-2 rounded-md text-base font-medium bg-gray-50">Plan</a>
<a href="#" class="text-gray-700 hover:text-gray-900 block px-3 py-2 rounded-md text-base font-medium">Calendar</a>
<a href="#" class="text-gray-700 hover:text-gray-900 block px-3 py-2 rounded-md text-base font-medium">Progress</a>
<a href="#" class="text-gray-700 hover:text-gray-900 block px-3 py-2 rounded-md text-base font-medium">Community</a>
<div class="border-t pt-2 flex justify-between items-center">
<div class="flex items-center px-3 py-2">
<img class="h-8 w-8 rounded-full border-2 border-primary" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
<span id="mobile-user-name-display" class="ml-2 text-sm font-medium text-gray-900">John Doe</span>
</div>
<button class="text-gray-500 px-4 py-2">
<i class="fas fa-cog text-xl"></i>
</button>
</div>
</div>
</div>
</nav>
<!-- Hero Section -->
<div class="bg-gradient-to-r from-primary to-[#0080b3] relative overflow-hidden">
<div class="absolute inset-0">
<div class="absolute inset-y-0 right-0 -mr-32 w-2/3 opacity-20">
<div class="grid grid-cols-5 grid-rows-8 gap-4 h-full">
<div class="bg-blue-400 rounded-full transform rotate-45"></div>
<div class="bg-blue-300 rounded-full transform rotate-12"></div>
<div class="bg-blue-200 rounded-full"></div>
<div class="bg-blue-300 rounded-full transform -rotate-12"></div>
<div class="bg-blue-400 rounded-full transform rotate-45"></div>
<div class="bg-blue-200 rounded-full transform -rotate-30"></div>
<div class="bg-blue-400 rounded-full transform rotate-12"></div>
<div class="bg-blue-300 rounded-full"></div>
<div class="bg-blue-200 rounded-full transform rotate-30"></div>
<div class="bg-blue-400 rounded-full"></div>
<!-- More elements repeating pattern -->
</div>
</div>
</div>
<div class="max-w-7xl mx-auto py-16 px-4 sm:px-6 lg:py-24 lg:px-8 lg:flex lg:items-center lg:justify-between relative">
<div class="flex-1 min-w-0 z-10">
<div class="inline-block bg-white/20 py-1 px-4 rounded-full mb-4">
<span class="text-white text-sm font-medium">NEW: Race Day Predictor Tool</span>
</div>
<h1 class="text-4xl font-extrabold tracking-tight text-white sm:text-5xl lg:text-6xl">
<span class="block">Run Your Best Race</span>
<span class="block text-primary-200 font-light">Personalized Plan, Garmin Sync</span>
</h1>
<p class="mt-6 max-w-3xl text-xl text-white">
Create science-based 80/20 training plans for any distance. Sync directly to your Garmin watch and track progress automatically.
</p>
<div class="mt-8 flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 space-y-4">
<div>
<a href="#plan-creator" class="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-md text-white bg-secondary hover:bg-orange-600 transition duration-300 transform hover:-translate-y-1">
Create My Plan <i class="fas fa-chevron-down ml-2"></i>
</a>
</div>
<div>
<button id="connect-garmin-hero" class="inline-flex items-center justify-center px-6 py-3 border-2 border-white text-base font-medium rounded-md shadow-sm text-white bg-transparent hover:bg-white/20 transition duration-300">
<i class="fab fa-garmin mr-2"></i> Connect Garmin
</button>
</div>
</div>
</div>
<div class="mt-12 lg:mt-0 z-10">
<div class="relative floating">
<img class="w-full max-w-lg" src="https://cdn.pixabay.com/photo/2014/03/25/16/24/silhouette-297348_1280.png" alt="Runner silhouette">
<div class="absolute bottom-10 -right-10 bg-white rounded-lg shadow-lg p-4 w-60 transform rotate-6">
<div class="flex justify-between items-center">
<span class="font-medium">Current streak</span>
<span class="bg-primary text-white text-xs font-bold px-2 py-1 rounded-full">3 days</span>
</div>
<div class="mt-2 flex space-x-1">
<div class="w-6 h-6 bg-green-400 rounded-full"></div>
<div class="w-6 h-6 bg-green-400 rounded-full"></div>
<div class="w-6 h-6 bg-green-400 rounded-full"></div>
<div class="w-6 h-6 bg-gray-200 rounded-full"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Featured Highlights -->
<div class="py-12 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="feature-card bg-gradient-to-br from-blue-50 to-white p-6 rounded-xl shadow-sm border border-blue-100 flex items-center transition-all duration-300 hover:shadow-md">
<div class="feature-icon w-12 h-12 bg-primary rounded-xl flex items-center justify-center text-white text-xl mr-4">
<i class="fas fa-dumbbell"></i>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900">80/20 Principle</h3>
<p class="mt-2 text-gray-600">Optimal balance between easy and hard running</p>
</div>
</div>
<div class="feature-card bg-gradient-to-br from-green-50 to-white p-6 rounded-xl shadow-sm border border-green-100 flex items-center transition-all duration-300 hover:shadow-md">
<div class="feature-icon w-12 h-12 bg-accent rounded-xl flex items-center justify-center text-white text-xl mr-4">
<i class="fas fa-sync-alt"></i>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900">Smart Auto Sync</h3>
<p class="mt-2 text-gray-600">Workouts appear on your watch automatically</p>
</div>
</div>
<div class="feature-card bg-gradient-to-br from-yellow-50 to-white p-6 rounded-xl shadow-sm border border-yellow-100 flex items-center transition-all duration-300 hover:shadow-md">
<div class="feature-icon w-12 h-12 bg-secondary rounded-xl flex items-center justify-center text-white text-xl mr-4">
<i class="fas fa-chart-line"></i>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900">Progress Analysis</h3>
<p class="mt-2 text-gray-600">Get detailed insights on your improvements</p>
</div>
</div>
</div>
</div>
</div>
<!-- Streak & Motivation -->
<div class="py-8 relative overflow-hidden">
<div class="streak-container w-full py-2">
<div class="streak-box text-center">
<div class="flex justify-center items-center">
<div class="bg-yellow-400 text-yellow-900 text-sm px-3 py-1 rounded-full font-semibold flex items-center badge-animate">
<i class="fas fa-fire mr-1"></i> <span id="streak-display">14-day streak</span>
</div>
<div class="ml-4 text-white font-medium">Your best streak: <span id="best-streak-display">28 days</span></div>
</div>
<div class="mt-2 text-blue-100 font-light">
"The miracle isn't that I finished. The miracle is that I had the courage to start." - John Bingham
</div>
</div>
</div>
</div>
<!-- Plan Creation Section -->
<div id="plan-creator" class="py-12 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="lg:text-center">
<h2 class="text-base text-primary font-semibold tracking-wide uppercase">Build Your Training</h2>
<p class="mt-2 text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
Create Your Running Plan
</p>
<p class="mt-4 max-w-2xl text-xl text-gray-500 lg:mx-auto">
Configure your training parameters below. We'll generate a personalized plan following the 80/20 rule with two quality runs per week.
</p>
</div>
<div class="mt-10 bg-gradient-to-b from-gray-50 to-gray-100 rounded-2xl shadow-lg p-8">
<form id="plan-form" class="space-y-8">
<!-- Plan Parameters -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="col-span-1">
<label for="race-distance" class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
Target Race Distance
<span class="tooltip-container ml-1 relative">
<i class="fas fa-info-circle text-gray-400 hover:text-gray-600 cursor-pointer"></i>
<div class="tooltip absolute z-10 bottom-full mb-2 w-64 p-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm transition-all" role="tooltip">
Select the distance you're training for. Your plan will be adjusted accordingly.
</div>
</span>
</label>
<select id="race-distance" class="mt-1 block w-full pl-3 pr-10 py-3 text-base border-2 border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary sm:text-sm rounded-lg transition duration-200">
<option value="5">5K (3.1 miles)</option>
<option value="10">10K (6.2 miles)</option>
<option value="13.1">Half Marathon (13.1 miles)</option>
<option value="26.2" selected>Marathon (26.2 miles)</option>
</select>
</div>
<div class="col-span-1">
<label for="weeks" class="block text-sm font-medium text-gray-700 mb-1 flex items-center">
Training Duration
<span class="tooltip-container ml-1 relative">
<i class="fas fa-info-circle text-gray-400 hover:text-gray-600 cursor-pointer"></i>
<div class="tooltip absolute z-10 bottom-full mb-2 w-64 p-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm" role="tooltip">
Recommended: 16-20 weeks for marathon, 8-12 weeks for 5K
</div>
</span>
</label>
<div class="relative">
<input type="number" id="weeks" min="6" max="20" value="16" class="mt-1 block w-full pl-3 pr-16 py-3 text-base border-2 border-gray-200 focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary sm:text-sm rounded-lg">
<div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span class="text-gray-500 sm:text-sm">weeks</span>
</div>
</div>
<div class="mt-2 text-xs text-gray-500">Adjustment: <span id="duration-status">Optimal</span></div>
</div>
<div class="col-span-1">
<label class="block text-sm font-medium text-gray-700 mb-1">
Measurement Units
</label>
<div class="flex space-x-4 mt-1">
<div class="flex items-center">
<input id="units-miles" name="units" type="radio" value="miles" checked class="focus:ring-primary h-4 w-4 text-primary border-gray-300">
<label for="units-miles" class="ml-2 block text-sm text-gray-700">Miles</label>
</div>
<div class="flex items-center">
<input id="units-km" name="units" type="radio" value="km" class="focus:ring-primary h-4 w-4 text-primary border-gray-300">
<label for="units-km" class="ml-2 block text-sm text-gray-700">Kilometers</label>
</div>
</div>
</div>
</div>
<!-- Current Fitness Level -->
<div class="border-t border-gray-200 pt-6">
<div class="flex items-center">
<h3 class="text-lg font-medium text-gray-900">Current Fitness Level</h3>
<span class="tooltip-container ml-2 relative">
<i class="fas fa-info-circle text-gray-400 hover:text-gray-600 cursor-pointer"></i>
<div class="tooltip absolute z-10 bottom-full mb-2 w-64 p-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm" role="tooltip">
Helps us set appropriate baseline distances and intensities for your plan.
</div>
</span>
</div>
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-4">
<div class="fitness-level">
<input type="radio" id="fitness-beginner" name="fitness" value="Beginner" class="hidden">
<label for="fitness-beginner" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-200 hover:bg-blue-50">
<span class="block font-medium text-gray-800">Beginner</span>
<span class="block text-sm text-gray-500 mt-1">New to structured training</span>
</label>
</div>
<div class="fitness-level">
<input type="radio" id="fitness-intermediate" name="fitness" value="Intermediate" checked class="hidden">
<label for="fitness-intermediate" class="block px-4 py-3 border-2 border-blue-300 bg-blue-50 rounded-lg text-center cursor-pointer transition duration-200 hover:shadow">
<span class="block font-medium text-gray-800">Intermediate</span>
<span class="block text-sm text-gray-500 mt-1">Regular runner, some experience</span>
</label>
</div>
<div class="fitness-level">
<input type="radio" id="fitness-advanced" name="fitness" value="Advanced" class="hidden">
<label for="fitness-advanced" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-200 hover:bg-blue-50">
<span class="block font-medium text-gray-800">Advanced</span>
<span class="block text-sm text-gray-500 mt-1">Seeking performance gains</span>
</label>
</div>
<div class="fitness-level">
<input type="radio" id="fitness-elite" name="fitness" value="Elite" class="hidden">
<label for="fitness-elite" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-200 hover:bg-blue-50">
<span class="block font-medium text-gray-800">Elite</span>
<span class="block text-sm text-gray-500 mt-1">Competitive level training</span>
</label>
</div>
</div>
</div>
<!-- Weekly Mileage -->
<div class="border-t border-gray-200 pt-6">
<div class="flex justify-between items-center">
<h3 class="text-lg font-medium text-gray-900">Weekly Mileage Goal</h3>
<span id="mileage-display" class="text-xl font-semibold text-primary">30 miles</span>
</div>
<div class="mt-4">
<input type="range" id="mileage" min="20" max="70" value="30" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>20</span>
<span>30</span>
<span>40</span>
<span>50</span>
<span>60</span>
<span>70+</span>
</div>
</div>
<div class="mt-2 text-xs text-gray-500">
<span id="mileage-note">Optimal for marathon training</span>
</div>
</div>
<!-- Goal Pace -->
<div class="border-t border-gray-200 pt-6">
<div class="flex items-center">
<h3 class="text-lg font-medium text-gray-900">Goal Race Pace</h3>
<span class="tooltip-container ml-2 relative">
<i class="fas fa-info-circle text-gray-400 hover:text-gray-600 cursor-pointer"></i>
<div class="tooltip absolute z-10 bottom-full mb-2 w-64 p-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm" role="tooltip">
Your target pace for race day (minutes per mile or per kilometer)
</div>
</span>
</div>
<div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-4">
<div class="pace-selector">
<input type="radio" id="pace-easy" name="pace" value="10:00-12:00" class="hidden">
<label for="pace-easy" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-300">
<span class="block text-sm font-medium text-gray-700">Slow</span>
<span class="block text-xs text-gray-500 mt-1">10:00-12:00 min/mile</span>
</label>
</div>
<div class="pace-selector">
<input type="radio" id="pace-moderate" name="pace" value="8:30-9:59" checked class="hidden">
<label for="pace-moderate" class="block px-4 py-3 border-2 border-primary bg-blue-50 rounded-lg text-center cursor-pointer transition duration-200">
<span class="block text-sm font-medium text-gray-700">Moderate</span>
<span class="block text-xs text-gray-500 mt-1">8:30-9:59 min/mile</span>
</label>
</div>
<div class="pace-selector">
<input type="radio" id="pace-fast" name="pace" value="7:00-8:29" class="hidden">
<label for="pace-fast" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-300">
<span class="block text-sm font-medium text-gray-700">Fast</span>
<span class="block text-xs text-gray-500 mt-1">7:00-8:29 min/mile</span>
</label>
</div>
<div class="pace-selector">
<input type="radio" id="pace-competitive" name="pace" value="6:00-6:59" class="hidden">
<label for="pace-competitive" class="block px-4 py-3 border-2 border-gray-200 rounded-lg text-center cursor-pointer transition duration-200 hover:border-blue-300">
<span class="block text-sm font-medium text-gray-700">Competitive</span>
<span class="block text-xs text-gray-500 mt-1">6:00-6:59 min/mile</span>
</label>
</div>
</div>
</div>
<div class="border-t border-gray-200 pt-6 flex justify-center">
<button id="generate-plan" type="button" class="px-8 py-4 border border-transparent text-base font-medium rounded-xl shadow-md text-white bg-gradient-to-r from-primary to-blue-700 hover:from-blue-600 hover:to-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-300 transform hover:-translate-y-1 w-full md:w-auto">
<i class="fas fa-bolt mr-2"></i> Generate Training Plan
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Training Plan Preview -->
<div class="py-12 bg-gradient-to-b from-blue-50 to-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center">
<h2 class="text-3xl font-bold text-gray-900 mb-2">How Your Plan Will Look</h2>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">Sample structure from a typical marathon training plan</p>
</div>
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="bg-white rounded-xl shadow-md overflow-hidden transition-transform duration-300 hover:-translate-y-2">
<div class="bg-gradient-to-r from-blue-500 to-blue-700 py-4 px-6">
<h3 class="text-lg font-semibold text-white">Weekly Pattern</h3>
</div>
<div class="p-6">
<ul class="space-y-3">
<li class="flex items-start">
<i class="fas fa-shoe-prints text-accent mt-1 mr-3"></i>
<span class="flex-1">2 quality workouts per week</span>
</li>
<li class="flex items-start">
<i class="fas fa-running text-secondary mt-1 mr-3"></i>
<span class="flex-1">1 long run per week</span>
</li>
<li class="flex items-start">
<i class="fas fa-walking text-gray-400 mt-1 mr-3"></i>
<span class="flex-1">3-4 easy runs</span>
</li>
<li class="flex items-start">
<i class="fas fa-couch text-gray-400 mt-1 mr-3"></i>
<span class="flex-1">1-2 rest days</span>
</li>
</ul>
</div>
</div>
<div class="bg-white rounded-xl shadow-md overflow-hidden transition-transform duration-300 hover:-translate-y-2">
<div class="bg-gradient-to-r from-accent to-emerald-700 py-4 px-6">
<h3 class="text-lg font-semibold text-white">Quality Workouts</h3>
</div>
<div class="p-6">
<ul class="space-y-3">
<li class="flex items-start">
<i class="fas fa-tachometer-alt text-blue-500 mt-1 mr-3"></i>
<span class="flex-1">Tempo runs at goal pace</span>
</li>
<li class="flex items-start">
<i class="fas fa-stopwatch text-purple-500 mt-1 mr-3"></i>
<span class="flex-1">Interval training for speed</span>
</li>
<li class="flex items-start">
<i class="fas fa-mountain text-gray-700 mt-1 mr-3"></i>
<span class="flex-1">Hill repeats for strength</span>
</li>
</ul>
</div>
</div>
<div class="bg-white rounded-xl shadow-md overflow-hidden transition-transform duration-300 hover:-translate-y-2">
<div class="bg-gradient-to-r from-secondary to-orange-700 py-4 px-6">
<h3 class="text-lg font-semibold text-white">Progress Milestones</h3>
</div>
<div class="p-6">
<ul class="space-y-3">
<li class="flex items-start">
<i class="fas fa-calendar-check text-secondary mt-1 mr-3"></i>
<span class="flex-1">Phase 1: Base building</span>
</li>
<li class="flex items-start">
<i class="fas fa-chart-line text-accent mt-1 mr-3"></i>
<span class="flex-1">Phase 2: Build phase</span>
</li>
<li class="flex items-start">
<i class="fas fa-trophy text-yellow-500 mt-1 mr-3"></i>
<span class="flex-1">Phase 3: Taper & race</span>
</li>
</ul>
</div>
</div>
<div class="bg-white rounded-xl shadow-md overflow-hidden transition-transform duration-300 hover:-translate-y-2">
<div class="bg-gradient-to-r from-purple-500 to-purple-700 py-4 px-6">
<h3 class="text-lg font-semibold text-white">Auto Adjustments</h3>
</div>
<div class="p-6">
<ul class="space-y-3">
<li class="flex items-start">
<i class="fas fa-heartbeat text-red-500 mt-1 mr-3"></i>
<span class="flex-1">Performance-based adaptations</span>
</li>
<li class="flex items-start">
<i class="fas fa-balance-scale text-gray-600 mt-1 mr-3"></i>
<span class="flex-1">Injury prevention balancing</span>
</li>
<li class="flex items-start">
<i class="fas fa-umbrella-beach text-yellow-500 mt-1 mr-3"></i>
<span class="flex-1">Schedule conflicts auto-reschedule</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Plan Display Section -->
<div id="plan-display" class="py-12 bg-gray-50 hidden">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8">
<div>
<h2 id="plan-title" class="text-2xl font-bold text-gray-900">Your Training Plan</h2>
<p id="plan-summary" class="mt-2 text-gray-600">Loading plan details...</p>
</div>
<div class="mt-4 md:mt-0 flex flex-wrap gap-3">
<button type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50" onclick="showMessage('Download Calendar', 'Download Calendar functionality needs backend to generate ICS file.')">
<i class="far fa-calendar-alt mr-2"></i> Download Calendar
</button>
<button id="sync-to-garmin-button" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-blue-700">
<i class="fab fa-garmin mr-2"></i> Sync to Garmin
</button>
<button id="calendar-toggle" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
<i class="far fa-calendar mr-2"></i> Calendar View
</button>
</div>
</div>
<div class="mb-8 bg-white rounded-lg shadow p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Plan Overview</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-blue-50 p-4 rounded-md border border-blue-100">
<p class="text-sm font-medium text-gray-500">Total weeks</p>
<p id="total-weeks-display" class="text-2xl font-bold">--</p>
</div>
<div class="bg-green-50 p-4 rounded-md border border-green-100">
<p class="text-sm font-medium text-gray-500">Total miles</p>
<p id="total-miles-display" class="text-2xl font-bold">--</p>
</div>
<div class="bg-yellow-50 p-4 rounded-md border border-yellow-100">
<p class="text-sm font-medium text-gray-500">Longest run</p>
<p id="longest-run-display" class="text-2xl font-bold">--</p>
</div>
<div class="bg-purple-50 p-4 rounded-md border border-purple-100">
<p class="text-sm font-medium text-gray-500">Quality days/week</p>
<p id="quality-days-display" class="text-2xl font-bold">--</p>
</div>
</div>
<div class="mt-6 flex items-center">
<div class="flex-1">
<div class="h-2 w-full bg-gray-200 rounded-full overflow-hidden">
<div class="progress-bar h-full rounded-full" style="width: 0%"></div>
</div>
</div>
<div id="plan-progress-display" class="ml-4 text-sm font-medium text-primary">Week 0/0</div>
</div>
</div>
<!-- Calendar View -->
<div id="calendar-view" class="hidden bg-white rounded-lg shadow p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<div id="calendar-month-year" class="text-xl font-bold text-gray-800"></div>
<div class="flex space-x-2">
<button id="prev-month" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left text-gray-600"></i>
</button>
<button id="next-month" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right text-gray-600"></i>
</button>
</div>
</div>
<div class="grid grid-cols-7 gap-1 mb-1">
<div class="text-center text-sm font-medium py-2 text-gray-500">Sun</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Mon</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Tue</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Wed</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Thu</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Fri</div>
<div class="text-center text-sm font-medium py-2 text-gray-500">Sat</div>
</div>
<div id="calendar-grid" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be inserted here by JS -->
</div>
</div>
<!-- Weekly tabs -->
<div id="weekly-tabs-container" class="bg-white rounded-lg shadow overflow-hidden">
<div class="border-b border-gray-200">
<nav id="weekly-tabs" class="-mb-px flex overflow-x-auto" aria-label="Tabs">
<!-- Week tabs will be inserted here by JS -->
</nav>
</div>
<div id="weekly-workouts-container" class="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
<!-- Workout cards will be inserted here by JS -->
</div>
<div class="px-6 py-4 bg-gray-50 border-t border-gray-200 flex items-center">
<div class="flex-1">
<p class="text-sm font-medium text-gray-700">Total miles this week: <span id="current-week-total-miles">0</span></p>
</div>
<div class="flex justify-end space-x-3">
<button id="prev-week" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50">
<i class="fas fa-arrow-left mr-2"></i> Previous week
</button>
<button id="next-week" type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-blue-700">
Next week <i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Garmin Integration Section -->
<div class="py-12 bg-gradient-to-r from-[#005e8c] to-primary">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="lg:grid lg:grid-cols-2 lg:gap-16 items-center">
<div class="mb-12 lg:mb-0">
<h2 class="text-3xl font-extrabold tracking-tight text-white sm:text-4xl">
Seamless Garmin Integration
</h2>
<p class="mt-4 text-lg leading-6 text-blue-100">
Sync your training schedule directly to your Garmin device with just one click. Your workouts will appear in your Garmin Calendar with all the details.
</p>
<div class="mt-8">
<div class="flex items-center mb-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center h-8 w-8 rounded-full bg-blue-500">
<i class="fas fa-sync-alt text-white"></i>
</div>
</div>
<div class="ml-4">
<h5 class="text-lg font-medium text-white">Real-time Sync</h5>
<p class="mt-1 text-blue-100">Workouts appear on your watch immediately after syncing</p>
</div>
</div>
<div class="flex items-center mb-4">
<div class="flex-shrink-0">
<div class="flex items-center justify-center h-8 w-8 rounded-full bg-blue-500">
<i class="fas fa-bell text-white"></i>
</div>
</div>
<div class="ml-4">
<h5 class="text-lg font-medium text-white">Workout Notifications</h5>
<p class="mt-1 text-blue-100">Get reminders on your device when it's time to train</p>
</div>
</div>
<div class="flex items-center">
<div class="flex-shrink-0">
<div class="flex items-center justify-center h-8 w-8 rounded-full bg-blue-500">
<i class="fas fa-chart-line text-white"></i>
</div>
</div>
<div class="ml-4">
<h5 class="text-lg font-medium text-white">Automatic Tracking</h5>
<p class="mt-1 text-blue-100">Runs automatically recorded in Garmin Connect with metrics</p>
</div>
</div>
</div>
<div class="mt-10">
<button id="connect-garmin-badge" class="garmin-badge pulse inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-md text-white bg-opacity-90 hover:scale-105 transition">
<i class="fab fa-garmin text-2xl mr-3"></i>
<span>Connect Your Garmin Account</span>
</button>
</div>
</div>
<div class="relative">
<div class="relative w-full rounded-xl shadow-xl overflow-hidden">
<div class="relative h-96 bg-gray-800 rounded-xl flex items-center justify-center">
<div class="absolute inset-0 opacity-20">
<div class="grid grid-cols-3 grid-rows-3 gap-4 h-full p-4">
<div class="bg-gradient-to-br from-primary to-blue-900 rounded-lg transform rotate-3"></div>
<div class="bg-gradient-to-br from-green-500 to-emerald-900 rounded-lg transform -rotate-2"></div>
<div class="bg-gradient-to-br from-blue-500 to-blue-900 rounded-lg transform rotate-1"></div>
<div class="bg-gradient-to-br from-primary to-blue-900 rounded-lg transform -rotate-3"></div>
<div class="bg-gradient-to-br from-green-500 to-emerald-900 rounded-lg transform rotate-2"></div>
<div class="bg-gradient-to-br from-primary to-blue-900 rounded-lg transform -rotate-1"></div>
<div class="bg-gradient-to-br from-green-500 to-emerald-900 rounded-lg transform rotate-1"></div>
<div class="bg-gradient-to-br from-blue-500 to-blue-900 rounded-lg transform -rotate-3"></div>
<div class="bg-gradient-to-br from-primary to-blue-900 rounded-lg transform rotate-2"></div>
</div>
</div>
<div class="relative bg-gray-900 w-52 h-80 rounded-3xl border-8 border-gray-800 shadow-2xl overflow-hidden flex flex-col">
<div class="h-8 bg-black flex justify-center items-center">
<div class="w-10 h-1 rounded bg-gray-700"></div>
</div>
<div class="flex-1 bg-gray-900 p-3">
<div class="text-white text-center font-semibold mb-2">TODAY'S RUN</div>
<div class="bg-gray-800 rounded-lg p-3">
<div class="text-center text-primary font-medium">Long Run</div>
<div class="flex justify-between items-center mt-2">
<div>
<div class="text-xs text-gray-400">Distance</div>
<div class="text-white">16 miles</div>
</div>
<div>
<div class="text-xs text-gray-400">Target Pace</div>
<div class="text-white">8:45-9:30</div>
</div>
</div>
<div class="mt-3 flex justify-center">
<button class="bg-primary rounded-full px-3 py-1 text-white text-xs">START</button>
</div>
</div>
<div class="mt-4 text-xs text-gray-500 text-center">Synced from RunSync</div>
</div>
<div class="h-8 bg-black flex items-center justify-center">
<i class="fas fa-circle text-green-500 animate-pulse"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Progress Tracking -->
<div class="py-12 bg-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="lg:text-center">
<h2 class="text-base text-primary font-semibold tracking-wide uppercase">Track Your Progress</h2>
<p class="mt-2 text-3xl leading-8 font-extrabold tracking-tight text-gray-900 sm:text-4xl">
Weekly Performance Metrics
</p>
<p class="mt-4 max-w-2xl text-xl text-gray-500 lg:mx-auto">
See how your fitness improves over the training period with data synced from your Garmin device.
</p>
</div>
<div class="mt-10">
<div class="bg-gray-50 rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<div>
<h3 class="text-lg font-medium text-gray-900">Training Load & Fitness</h3>
<p class="text-sm text-gray-500">Based on your Garmin activity data</p>
</div>
<div class="relative">
<select class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-primary focus:border-primary sm:text-sm rounded-md bg-white">
<option>Last 4 weeks</option>
<option>Last 8 weeks</option>
<option>Full plan</option>
</select>
</div>
</div>
<!-- Graph Container -->
<div class="h-80 rounded-lg bg-white p-4 grid grid-cols-12 items-end">
<div class="col-span-1 flex flex-col justify-between h-full text-xs text-gray-500">
<div>100</div>
<div>80</div>
<div>60</div>
<div>40</div>
<div>20</div>
<div>0</div>
</div>
<div class="col-span-11 grid grid-cols-7 gap-4 items-end h-full">
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 30%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 70%"></div>
<div class="text-xs text-gray-500 mt-2">W1</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 40%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 65%"></div>
<div class="text-xs text-gray-500 mt-2">W2</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 55%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 70%"></div>
<div class="text-xs text-gray-500 mt-2">W3</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 50%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 75%"></div>
<div class="text-xs text-gray-500 mt-2">W4</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 65%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 85%"></div>
<div class="text-xs text-gray-500 mt-2">W5</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 60%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 80%"></div>
<div class="text-xs text-gray-500 mt-2">W6</div>
</div>
<div class="flex flex-col items-center justify-end">
<div class="w-10 bg-blue-200 rounded-t-md" style="height: 70%"></div>
<div class="w-10 bg-primary rounded-t-md" style="height: 90%"></div>
<div class="text-xs text-gray-500 mt-2">W7</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modals (for simulating login/messages) -->
<div id="auth-modal" class="modal">
<div class="bg-white p-8 rounded-lg shadow-xl max-w-sm w-full text-center">
<h3 class="text-2xl font-bold text-gray-900 mb-6">Login / Register</h3>
<div class="space-y-4">
<input type="email" id="auth-email" placeholder="Email" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-primary focus:border-primary">
<input type="password" id="auth-password" placeholder="Password" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-primary focus:border-primary">
<button id="auth-login-btn" class="w-full px-4 py-3 bg-primary text-white font-semibold rounded-lg hover:bg-blue-700 transition">Login</button>
<button id="auth-register-btn" class="w-full px-4 py-3 bg-gray-200 text-gray-700 font-semibold rounded-lg hover:bg-gray-300 transition">Register</button>
<button id="auth-close-btn" class="w-full px-4 py-3 bg-red-500 text-white font-semibold rounded-lg hover:bg-red-600 transition">Close</button>
</div>
</div>
</div>
<div id="message-modal" class="modal">
<div class="bg-white p-8 rounded-lg shadow-xl max-w-sm w-full text-center">
<h3 id="message-title" class="text-xl font-bold text-gray-900 mb-4"></h3>
<p id="message-text" class="text-gray-700 mb-6"></p>
<button id="message-close-btn" class="px-6 py-3 bg-primary text-white font-semibold rounded-lg hover:bg-blue-700 transition">OK</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const generatePlanButton = document.getElementById('generate-plan');
const planFormContainer = document.querySelector('#plan-creator .mt-10'); // Get the div containing the form
const planDisplaySection = document.getElementById('plan-display');
const calendarView = document.getElementById('calendar-view');
const calendarToggleButton = document.getElementById('calendar-toggle');
const mileageSlider = document.getElementById('mileage');
const mileageDisplay = document.getElementById('mileage-display');
const mileageNote = document.getElementById('mileage-note');
const raceDistanceSelect = document.getElementById('race-distance');
const weeksInput = document.getElementById('weeks');
const durationStatus = document.getElementById('duration-status');
const weeklyTabsContainer = document.getElementById('weekly-tabs');
const weeklyWorkoutsContainer = document.getElementById('weekly-workouts-container');
const currentWeekTotalMiles = document.getElementById('current-week-total-miles');
const prevWeekButton = document.getElementById('prev-week');
const nextWeekButton = document.getElementById('next-week');
const calendarGrid = document.getElementById('calendar-grid');
const calendarMonthYear = document.getElementById('calendar-month-year');
const prevMonthButton = document.getElementById('prev-month');
const nextMonthButton = document.getElementById('next-month');
const connectGarminHeroButton = document.getElementById('connect-garmin-hero');
const connectGarminBadgeButton = document.getElementById('connect-garmin-badge');
const syncToGarminButton = document.getElementById('sync-to-garmin-button');
// Auth UI elements
const loginButton = document.getElementById('login-button');
const logoutButton = document.getElementById('logout-button');
const authModal = document.getElementById('auth-modal');
const authEmail = document.getElementById('auth-email');
const authPassword = document.getElementById('auth-password');
const authLoginBtn = document.getElementById('auth-login-btn');
const authRegisterBtn = document.getElementById('auth-register-btn');
const authCloseBtn = document.getElementById('auth-close-btn');
// Message modal elements
const messageModal = document.getElementById('message-modal');
const messageTitle = document.getElementById('message-title');
const messageText = document.getElementById('message-text');
const messageCloseBtn = document.getElementById('message-close-btn');
let currentPlan = null;
let currentWeekIndex = 0; // 0-indexed week
// Mock Garmin state (for client-side demo)
let isGarminConnected = false;
// --- Utility Functions ---
// Show custom message modal
function showMessage(title, text) {
messageTitle.textContent = title;
messageText.textContent = text;
messageModal.style.display = 'flex';
}
// Hide custom message modal
messageCloseBtn.addEventListener('click', () => {
messageModal.style.display = 'none';
});
// --- Authentication Logic ---
loginButton.addEventListener('click', () => {
authModal.style.display = 'flex';
});
authCloseBtn.addEventListener('click', () => {
authModal.style.display = 'none';
authEmail.value = '';
authPassword.value = '';
});
authLoginBtn.addEventListener('click', async () => {
const email = authEmail.value;
const password = authPassword.value;
// Check if window.auth is available before using it
if (window.auth && email && password) {
try {
const { signInWithEmailAndPassword } = await import("https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js");
await signInWithEmailAndPassword(window.auth, email, password);
showMessage('Success', 'Logged in successfully!');
authModal.style.display = 'none';
} catch (error) {
showMessage('Login Failed', error.message);
}
} else {
showMessage('Login Error', 'Please enter email and password. Firebase not initialized or fields are empty.');
}
});
authRegisterBtn.addEventListener('click', async () => {
const email = authEmail.value;
const password = authPassword.value;
// Check if window.auth is available before using it
if (window.auth && email && password) {
try {
const { createUserWithEmailAndPassword } = await import("https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js");
await createUserWithEmailAndPassword(window.auth, email, password);
showMessage('Success', 'Registered and logged in successfully!');
authModal.style.display = 'none';
} catch (error) {
showMessage('Registration Failed', error.message);
}
} else {
showMessage('Registration Error', 'Please enter email and password. Firebase not initialized or fields are empty.');
}
});
logoutButton.addEventListener('click', async () => {
if (window.auth) {
try {
const { signOut } = await import("https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js");
await signOut(window.auth);
showMessage('Logged Out', 'You have been logged out.');
// Clear any displayed plan or user-specific data
planFormContainer.classList.remove('hidden');
planDisplaySection.classList.add('hidden');
const userNameDisplay = document.getElementById('user-name-display');
const mobileUserNameDisplay = document.getElementById('mobile-user-name-display');
if (userNameDisplay) userNameDisplay.textContent = `Guest: ${window.userId ? window.userId.substring(0, 8) : 'unknown'}...`;
if (mobileUserNameDisplay) mobileUserNameDisplay.textContent = `Guest: ${window.userId ? window.userId.substring(0, 8) : 'unknown'}...`;
} catch (error) {
showMessage('Logout Failed', error.message);
}
}
});
// --- UI Updates based on Form Inputs ---
mobileMenuButton.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
// Initial update for mileage display
mileageDisplay.textContent = `${mileageSlider.value} miles`;
updateMileageNote(mileageSlider.value);
updateDurationStatus(weeksInput.value, raceDistanceSelect.value);
// Update mileage display on slider change
mileageSlider.addEventListener('input', () => {
mileageDisplay.textContent = `${mileageSlider.value} ${document.querySelector('input[name="units"]:checked').value}`;
updateMileageNote(mileageSlider.value);
});
// Update duration status on weeks or race distance change
weeksInput.addEventListener('input', () => {
updateDurationStatus(weeksInput.value, raceDistanceSelect.value);
});
raceDistanceSelect.addEventListener('change', () => {
updateDurationStatus(weeksInput.value, raceDistanceSelect.value);
});
document.querySelectorAll('input[name="units"]').forEach(radio => {
radio.addEventListener('change', () => {
mileageDisplay.textContent = `${mileageSlider.value} ${document.querySelector('input[name="units"]:checked').value}`;
// Re-run plan generation or update to reflect unit changes if plan exists
if (currentPlan) {
displayGeneratedPlan(currentPlan); // Redraw with new units
}
});
});
function updateMileageNote(mileage) {
let note = "";
const distance = raceDistanceSelect.value;
if (distance === "26.2") { // Marathon
if (mileage < 30) note = "Consider increasing mileage for marathon";
else if (mileage >= 30 && mileage <= 50) note = "Optimal for marathon training";
else note = "High mileage, ensure adequate recovery";
} else if (distance === "13.1") { // Half Marathon
if (mileage < 20) note = "Might be low for half marathon";
else if (mileage >= 20 && mileage <= 40) note = "Good for half marathon";
else note = "Higher end for half marathon, strong base needed";
} else { // 5K/10K
if (mileage < 15) note = "Good for building base";
else if (mileage >= 15 && mileage <= 30) note = "Optimal for 5K/10K";
else note = "High mileage for shorter distances, focus on quality";
}
mileageNote.textContent = note;
}
function updateDurationStatus(weeks, distance) {
let status = "Optimal";
if (distance === "26.2") { // Marathon
if (weeks < 16) status = "Short for marathon, good base needed";
else if (weeks > 20) status = "Long, consider shorter phases";
} else if (distance === "13.1") { // Half Marathon
if (weeks < 10) status = "Short for half marathon";
else if (weeks > 14) status = "Longer than typical for half marathon";
} else { // 5K/10K
if (weeks < 8) status = "Short for 5K/10K";
else if (weeks > 12) status = "Longer than typical for 5K/10K";
}
durationStatus.textContent = status;
}
// --- Plan Generation & Display Logic ---
generatePlanButton.addEventListener('click', async () => {
const planDetails = {
raceDistance: raceDistanceSelect.value,
weeks: parseInt(weeksInput.value),
units: document.querySelector('input[name="units"]:checked').value,
fitnessLevel: document.querySelector('input[name="fitness"]:checked').value,
weeklyMileageGoal: parseInt(mileageSlider.value),
goalPace: document.querySelector('input[name="pace"]:checked').value
};
// Simulate plan generation (replace with actual backend call)
currentPlan = generateMockPlan(planDetails);
// Save the generated plan to Firestore
if (window.saveUserData) {
await window.saveUserData({ currentPlan: currentPlan });
}
displayGeneratedPlan(currentPlan);
planFormContainer.classList.add('hidden');
planDisplaySection.classList.remove('hidden');
// Ensure initial week is displayed
currentWeekIndex = 0;
displayWeek(currentWeekIndex);
});
function generateMockPlan(details) {
const plan = {
...details,
weeksData: []
};
const totalMiles = details.weeklyMileageGoal * details.weeks;
const longestRunBase = details.raceDistance === "26.2" ? 20 : (details.raceDistance === "13.1" ? 10 : 6);
let currentMileage = details.weeklyMileageGoal * 0.7; // Start a bit lower
for (let i = 0; i < details.weeks; i++) {
const weekNum = i + 1;
const week = {
weekNum: weekNum,
totalMiles: 0,
workouts: []
};
const easyRunBase = currentMileage / 5;
const qualityRunBase = currentMileage / 3;
const longRunBase = currentMileage / 2.5;
// Example workouts for a week (simplistic)
week.workouts.push({ day: 'Monday', type: 'Rest Day', distance: 0, unit: details.units, pace: 'N/A', description: 'Rest or light stretching' });
week.workouts.push({ day: 'Tuesday', type: 'Easy Run', distance: Math.round(easyRunBase * 10) / 10, unit: details.units, pace: details.goalPace, description: 'Easy pace for recovery' });
week.workouts.push({ day: 'Wednesday', type: 'Tempo Run', distance: Math.round(qualityRunBase * 0.8 * 10) / 10, unit: details.units, pace: details.goalPace, description: `Tempo run with warm-up/cool-down` });
week.workouts.push({ day: 'Thursday', type: 'Easy Run', distance: Math.round(easyRunBase * 0.7 * 10) / 10, unit: details.units, pace: details.goalPace, description: 'Easy pace' });
week.workouts.push({ day: 'Friday', type: 'Rest Day', distance: 0, unit: details.units, pace: 'N/A', description: 'Rest or cross-train' });
week.workouts.push({ day: 'Saturday', type: 'Speed Work', distance: Math.round(qualityRunBase * 0.7 * 10) / 10, unit: details.units, pace: details.goalPace.split('-')[0], description: 'Intervals or hill repeats' });
week.workouts.push({ day: 'Sunday', type: 'Long Run', distance: Math.round(longRunBase * 10) / 10, unit: details.units, pace: details.goalPace, description: 'Steady long run' });
week.totalMiles = week.workouts.reduce((sum, wo) => sum + wo.distance, 0);
currentMileage = Math.min(details.weeklyMileageGoal * (1 + i * 0.05), details.weeklyMileageGoal); // Gradually increase
plan.weeksData.push(week);
}
// Calculate longest run and total miles for the plan overview
plan.longestRun = Math.max(...plan.weeksData.flatMap(week => week.workouts.filter(wo => wo.type === 'Long Run').map(wo => wo.distance)));
plan.totalPlanMiles = plan.weeksData.reduce((sum, week) => sum + week.totalMiles, 0);
return plan;
}
function displayGeneratedPlan(plan) {
currentPlan = plan; // Store the plan globally
// Update plan overview
document.getElementById('plan-title').textContent = `Your ${plan.raceDistance === "26.2" ? "Marathon" : plan.raceDistance + "K/Half"} Training Plan`;
document.getElementById('plan-summary').textContent = `${plan.weeks} weeks | Goal: ${plan.goalPace} min/${plan.units} | Avg. ${plan.weeklyMileageGoal} ${plan.units}/week`;
document.getElementById('total-weeks-display').textContent = plan.weeks;
document.getElementById('total-miles-display').textContent = `${Math.round(plan.totalPlanMiles)}`;
document.getElementById('longest-run-display').textContent = `${plan.longestRun} ${plan.units}`;
document.getElementById('quality-days-display').textContent = '2'; // Based on initial description
// Populate weekly tabs
weeklyTabsContainer.innerHTML = '';
for (let i = 0; i < plan.weeks; i++) {
const tab = document.createElement('a');
tab.href = `#`; // No actual link, just for styling
tab.classList.add('whitespace-nowrap', 'py-4', 'px-4', 'border-b-2', 'border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300', 'font-medium', 'text-sm');
tab.textContent = `Week ${i + 1}`;
tab.dataset.weekIndex = i;
tab.addEventListener('click', (e) => {
e.preventDefault();
currentWeekIndex = i;
displayWeek(currentWeekIndex);
});
weeklyTabsContainer.appendChild(tab);
}
// Initial display of the first week
displayWeek(currentWeekIndex);
}
function displayWeek(weekIndex) {
if (!currentPlan || !currentPlan.weeksData || weekIndex < 0 || weekIndex >= currentPlan.weeksData.length) {
console.error("Invalid week index or plan data missing.");
return;
}
const weekData = currentPlan.weeksData[weekIndex];
// Update weekly tabs active state
Array.from(weeklyTabsContainer.children).forEach((tab, index) => {
if (index === weekIndex) {
tab.classList.add('border-primary', 'text-primary');
tab.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
} else {
tab.classList.remove('border-primary', 'text-primary');
tab.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'hover:border-gray-300');
}
});
// Update progress bar
const progressBar = planDisplaySection.querySelector('.progress-bar');
const progressPercentage = ((weekIndex + 1) / currentPlan.weeks) * 100;
progressBar.style.width = `${progressPercentage}%`;
document.getElementById('plan-progress-display').textContent = `Week ${weekIndex + 1}/${currentPlan.weeks}`;
// Populate workout cards for the current week
weeklyWorkoutsContainer.innerHTML = '';
weekData.workouts.forEach((workout, dayIndex) => {
const workoutCard = document.createElement('div');
workoutCard.classList.add('workout-card', 'bg-white', 'rounded-lg', 'border', 'border-gray-200', 'p-4');
let tagColor = 'blue';
let tagText = 'Run';
let icon = 'fas fa-shoe-prints';
if (workout.type.includes('Rest')) {
tagColor = 'yellow';
tagText = 'Rest Day';
icon = 'fas fa-couch';
} else if (workout.type.includes('Tempo') || workout.type.includes('Speed')) {
tagColor = 'purple';
tagText = workout.type;
icon = 'fas fa-tachometer-alt';
} else if (workout.type.includes('Long')) {
tagColor = 'pink';
tagText = workout.type;
icon = 'fas fa-running';
}
workoutCard.innerHTML = `
<div class="flex justify-between items-start">
<div>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-${tagColor}-100 text-${tagColor}-800">
${workout.day}
</span>
<h4 class="mt-1 text-lg font-medium text-gray-900">${workout.type}</h4>
</div>
${workout.distance > 0 ? `
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
<i class="${icon} mr-1"></i> ${workout.distance} ${workout.unit}
</span>` : ''}
</div>
<p class="mt-2 text-gray-600">${workout.description}</p>
<div class="mt-4 flex justify-between">
<button type="button" class="inline-flex items-center px-3 py-1 border border-gray-300 rounded-md text-xs text-gray-700 bg-white hover:bg-gray-50" onclick="showMessage('Schedule Workout', 'Scheduling functionality needs to be implemented. This would typically involve Google Calendar API or an internal calendar.')">
<i class="far fa-clock mr-1"></i> Schedule
</button>
${workout.distance > 0 ? `
<button type="button" class="send-to-garmin-btn inline-flex items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-white bg-primary hover:bg-blue-700" data-workout-day="${dayIndex}" data-week-index="${weekIndex}">
<i class="fab fa-garmin mr-1"></i> Send to Garmin
</button>` : `
<button type="button" class="w-full inline-flex justify-center items-center px-3 py-1 border border-transparent text-xs font-medium rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200" onclick="showMessage('Cross-Training', 'Adding cross-training would allow you to log non-running activities.')">
<i class="fas fa-dumbbell mr-1"></i> Add Cross-Training
</button>`}
</div>
`;
weeklyWorkoutsContainer.appendChild(workoutCard);
});
// Update current week total miles
currentWeekTotalMiles.textContent = Math.round(weekData.totalMiles);
// Update calendar if visible
if (!calendarView.classList.contains('hidden')) {
renderCalendar(new Date()); // Render current month initially, later this could be tied to plan dates
}
}
// Calendar Navigation
calendarToggleButton.addEventListener('click', () => {
calendarView.classList.toggle('hidden');
document.getElementById('weekly-tabs-container').classList.toggle('hidden');
if (!calendarView.classList.contains('hidden')) {
renderCalendar(new Date()); // Render current month when calendar view is shown
}
});
let currentCalendarDate = new Date(); // Tracks the currently displayed month in calendar
function renderCalendar(date) {
calendarGrid.innerHTML = '';
const month = date.getMonth();
const year = date.getFullYear();
calendarMonthYear.textContent = new Date(year, month).toLocaleString('default', { month: 'long', year: 'numeric' });
const firstDayOfMonth = new Date(year, month, 1).getDay(); // 0 for Sunday, 1 for Monday
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Fill leading empty days
for (let i = 0; i < firstDayOfMonth; i++) {
const emptyDay = document.createElement('div');
emptyDay.classList.add('calendar-day', 'text-center', 'py-2', 'rounded-lg', 'text-gray-400');
// Calculate previous month's day
const prevMonthLastDay = new Date(year, month, 0).getDate();
emptyDay.textContent = prevMonthLastDay - (firstDayOfMonth - 1 - i);
calendarGrid.appendChild(emptyDay);
}
// Fill days of the month
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.classList.add('calendar-day', 'text-center', 'py-2', 'rounded-lg', 'cursor-pointer', 'border-2', 'border-transparent', 'hover:border-blue-200', 'hover:bg-blue-50');
dayElement.textContent = day;
// Check for workouts on this day in the current plan
if (currentPlan && currentPlan.weeksData) {
currentPlan.weeksData.forEach(week => {
week.workouts.forEach(workout => {
// This is a simplified check. A robust solution would map workout dates.
// For demo, we just mark days if there's any workout.
// In a real app, you'd match the specific date.
if (workout.distance > 0 && Math.random() > 0.5) { // Simulate some days having workouts
dayElement.classList.add('has-workout', 'bg-blue-50', 'border-blue-100');
}
});
});
}
calendarGrid.appendChild(dayElement);
}
}
prevMonthButton.addEventListener('click', () => {
currentCalendarDate.setMonth(currentCalendarDate.getMonth() - 1);
renderCalendar(currentCalendarDate);
});
nextMonthButton.addEventListener('click', () => {
currentCalendarDate.setMonth(currentCalendarDate.getMonth() + 1);
renderCalendar(currentCalendarDate);
});
prevWeekButton.addEventListener('click', () => {
if (currentPlan && currentWeekIndex > 0) {
currentWeekIndex--;
displayWeek(currentWeekIndex);
}
});
nextWeekButton.addEventListener('click', () => {
if (currentPlan && currentWeekIndex < currentPlan.weeksData.length - 1) {
currentWeekIndex++;
displayWeek(currentWeekIndex);
}
});
// --- Garmin Integration Simulation ---
// General function for Garmin connect (used by hero and badge buttons)
function initiateGarminConnect() {
if (isGarminConnected) {
showMessage('Garmin Connected', 'Your Garmin account is already connected!');
return;
}
// Simulate OAuth redirection and success
showMessage('Connecting to Garmin...', 'You would be redirected to Garmin Connect for authorization. This requires backend integration and Garmin Developer API access.');
setTimeout(() => {
// After a delay, simulate successful connection
isGarminConnected = true;
showMessage('Garmin Connected!', 'Successfully linked your Garmin account. Now you can sync workouts!');
// Update UI to reflect connection status if you had a visual indicator
}, 3000);
}
connectGarminHeroButton.addEventListener('click', initiateGarminConnect);
connectGarminBadgeButton.addEventListener('click', initiateGarminConnect);
// "Send to Garmin" button within workout cards
document.body.addEventListener('click', (event) => {
if (event.target.matches('.send-to-garmin-btn')) {
if (!isGarminConnected) {
showMessage('Garmin Not Connected', 'Please connect your Garmin account first to sync workouts.');
return;
}
const weekIdx = parseInt(event.target.dataset.weekIndex);
const dayIdx = parseInt(event.target.dataset.workoutDay);
const workout = currentPlan.weeksData[weekIdx].workouts[dayIdx];
showMessage('Syncing Workout to Garmin...', `Attempting to send "${workout.type} - ${workout.distance} ${workout.unit}" to Garmin. This requires backend integration.`);
// Simulate API call to backend for sending workout
setTimeout(() => {
showMessage('Workout Synced!', `"${workout.type} - ${workout.distance} ${workout.unit}" should now appear in your Garmin Connect calendar.`);
}, 2000);
}
});
// "Sync to Garmin" button on plan display section (for overall plan or pulling data)
syncToGarminButton.addEventListener('click', () => {
if (!isGarminConnected) {
showMessage('Garmin Not Connected', 'Please connect your Garmin account first to sync data.');
return;
}
showMessage('Syncing All Data with Garmin...', 'This would involve pushing your entire plan to Garmin and pulling your recent activities. This requires extensive backend integration with Garmin Connect APIs (Activity Upload/Download, Training Plan Upload).');
// Simulate fetching progress data from Garmin
setTimeout(() => {
// Update mock streak data
document.getElementById('streak-display').textContent = '21-day streak';
document.getElementById('best-streak-display').textContent = '35 days';
showMessage('Sync Complete!', 'Your plan and recent activities have been synced with Garmin (simulated).');
}, 3000);
});
// Initialize display on page load
// The onAuthStateChanged listener in the module script will call loadUserData
// once auth is ready, so no need to call it again here.
});
</script>
</body>
</html>