Find Your Stride

From your first 5k to a marathon PR, discover the plans, techniques, and gear to achieve your running goals.

Find a Plan
Weekly Challenge

30km

Run a total of 30km this week. You can do it!

Pace Pro Calculator

Find Your Perfect Running Plan

Form Check (Click Hotspots)
Running Form
Injury Prevention

Select a common pain area for prevention tips.

Thinking...
Shoe
Gear Spotlight: Hoka Clifton 9

A max-cushion neutral shoe perfect for daily miles and recovery runs. Known for its lightweight feel and smooth ride.

Type: Neutral Weight: 8.8oz Stack: High
Read Full Review

Latest Running Articles

Cross-Training

Boost fitness and prevent injury. See our latest tips:

What Runners Say

Running FAQs

Strides are short bursts of running (20-30 seconds) at about 80-90% of your max effort. They are great for improving form and speed without adding heavy fatigue. Do 4-6 after an easy run.

Side stitches are often caused by irregular breathing or eating too close to a run. Focus on deep, rhythmic belly breathing. If one occurs, try slowing down and exhaling forcefully as the foot on the *opposite* side strikes the ground.

Get Running-Specific Updates

Subscribe for injury prevention tips, new shoe reviews, and training plans.

Loading...

// --- Fetch Cross-Training Articles (Limit 1 each) --- async function loadCrossTrainingPosts() { const container = document.getElementById('crossTrainingContainer'); if (!container) return; container.innerHTML = ''; // Clear skeleton const collections = ['swimmingPosts', 'cyclingPosts']; const icons = { swimmingPosts: 'fa-water', cyclingPosts: 'fa-bicycle' }; try { for (const collName of collections) { const q = query(collection(db, collName), orderBy("date", "desc"), limit(1)); const snap = await getDocs(q); if (!snap.empty) { const doc = snap.docs[0]; const post = doc.data(); let pageName = (collName === 'swimmingPosts') ? 'swimming-fullpost' : 'cycling-fullpost'; const url = `${pageName}.html?id=${doc.id}`; container.innerHTML += `
${collName.replace('Posts','')}
${post.title.slice(0, 30)}...
`; } } } catch(e) { console.error("Cross-training load error:", e); container.innerHTML = `

Could not load tips.

`; } } // --- Theme & Accent Logic (from Trihardmurad) --- const accentDots = document.querySelectorAll('.accent-dot'); const themeSwitch = document.getElementById('themeSwitch'); const themeSwitchDot = document.getElementById('themeSwitchDot'); const themeIcon = document.getElementById('toggleIcon'); const html = document.documentElement; function applyTheme(t){ html.setAttribute('data-theme',t); localStorage.setItem('site-theme',t); if(t==='dark'){ themeSwitchDot.style.left='23px'; themeIcon.className='fas fa-moon mr-2'; } else { themeSwitchDot.style.left='3px'; themeIcon.className='fas fa-sun mr-2'; } setPrimaryRgbVariable(); } function applyAccent(a){ html.setAttribute('data-accent',a); localStorage.setItem('site-accent',a); accentDots.forEach(d=>d.setAttribute('aria-checked', d.dataset.accent === a)); setPrimaryRgbVariable(); drawHamburger(); } function setPrimaryRgbVariable() { try { const pC=getComputedStyle(html).getPropertyValue('--primary').trim(); if(pC.startsWith('#')){const r=parseInt(pC.substring(1,3),16),g=parseInt(pC.substring(3,5),16),b=parseInt(pC.substring(5,7),16); html.style.setProperty('--primary-rgb',`${r},${g},${b}`);} else if(pC.startsWith('rgb')){const m=pC.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/); if(m)html.style.setProperty('--primary-rgb',`${m[1]},${m[2]},${m[3]}`); else html.style.setProperty('--primary-rgb',`32,201,151`);} else html.style.setProperty('--primary-rgb',`32,201,151`);}catch(e){console.warn("RGB parse error."); html.style.setProperty('--primary-rgb',`32,201,151`);}} function drawHamburger(){ const accent=getComputedStyle(document.documentElement).getPropertyValue('--primary').trim()||'#20c997'; const svg=`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath stroke='${encodeURIComponent(accent)}' stroke-width='2' d='M3 6h18M3 12h18M3 18h18' stroke-linecap='round'/%3E%3C/svg%3E`; const el=document.getElementById('hamburgerSvg'); if(el){ el.style.backgroundImage=`url("${svg}")`; el.style.backgroundSize='20px 20px'; el.style.display='inline-block'; el.style.width='24px'; el.style.height='24px'; } } if(themeSwitch) themeSwitch.addEventListener('click', () => applyTheme(html.getAttribute('data-theme')==='dark'?'light':'dark')); accentDots.forEach(dot => dot.addEventListener('click', () => applyAccent(dot.dataset.accent))); const storedTheme = localStorage.getItem('site-theme')||'light'; const storedAccent = localStorage.getItem('site-accent')||'green'; applyTheme(storedTheme); applyAccent(storedAccent); drawHamburger(); // --- Toast Notification --- const toastEl = document.getElementById('toastNotification'); let toastTimeout; function showToast(message, isError = false) { clearTimeout(toastTimeout); toastEl.textContent = message; isError ? toastEl.classList.add('error') : toastEl.classList.remove('error'); toastEl.classList.add('show'); toastTimeout = setTimeout(() => { toastEl.classList.remove('show'); }, 3000); } // --- Newsletter Form Submission --- const newsletterForm = document.getElementById('newsletterForm'); if (newsletterForm) { newsletterForm.addEventListener('submit', async (e) => { e.preventDefault(); const emailInput = document.getElementById('newsletterEmail'); const email = emailInput.value; const button = newsletterForm.querySelector('button'); button.disabled = true; button.textContent = 'Subscribing...'; try { await addDoc(collection(db, 'newsletter_subscriptions'), { email: email, source: 'Running Page', // Updated source subscribedAt: serverTimestamp() }); showToast('Subscription successful! Thank you.'); emailInput.value = ''; } catch (error) { console.error("Newsletter sub error:", error); showToast('Subscription failed. Please try again.', true); } finally { button.disabled = false; button.textContent = 'Subscribe'; } }); } // --- Footer Contact Form --- window.handleContact = async function(e, formId){ e.preventDefault(); const form = document.getElementById(formId); if (!form) return; const btn = form.querySelector('button[type="submit"]'); const originalText = btn.textContent; btn.disabled = true; btn.textContent = 'Sending...'; try { let name = form.querySelector('#cname').value; let email = form.querySelector('#cemail').value; let message = form.querySelector('#cmsg').value; await addDoc(collection(db,'contacts'), { name, email, message, source: formId, createdAt: serverTimestamp() }); showToast('Thanks!'); form.reset(); } catch(err){ showToast('Send failed.',true); } finally { btn.disabled = false; btn.textContent = originalText; } }; const footerForm = document.getElementById('mainContact'); if (footerForm) footerForm.addEventListener('submit', (e) => handleContact(e, 'mainContact')); // --- Scroll-to-Top FAB --- const scrollTopFab = document.getElementById('scrollTopFab'); if (scrollTopFab) { window.addEventListener('scroll', () => { if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) { scrollTopFab.style.display = 'flex'; scrollTopFab.style.opacity = '1'; } else { scrollTopFab.style.opacity = '0'; setTimeout(() => { if (!(document.body.scrollTop > 100 || document.documentElement.scrollTop > 100)) { scrollTopFab.style.display = 'none'; } }, 300); } }); scrollTopFab.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); } // --- Floating Quote Card (Running Focused) --- const quotes = [ { text: "Pain is temporary. Quitting lasts forever.", author: "L. Armstrong" }, { text: "Courage to start is the miracle.", author: "J. Bingham" }, { text: "Run, walk, crawl; just never give up.", author: "D. Karnazes" }, { text: "It's supposed to be hard.", author: "A League of Their Own" } ]; const quoteTextEl = document.getElementById('quoteText'); const quoteAuthorEl = document.getElementById('quoteAuthor'); let quoteIndex = 0; function updateQuote(){ if (!quoteTextEl || !quoteAuthorEl) return; quoteTextEl.style.opacity = '0'; quoteAuthorEl.style.opacity = '0'; setTimeout(() => { let newIndex = quoteIndex; while (newIndex === quoteIndex) { newIndex = Math.floor(Math.random() * quotes.length); } quoteIndex = newIndex; quoteTextEl.textContent = `"${quotes[quoteIndex].text}"`; quoteAuthorEl.textContent = `— ${quotes[quoteIndex].author}`; quoteTextEl.style.opacity = '1'; quoteAuthorEl.style.opacity = '1'; }, 500); } updateQuote(); setInterval(updateQuote, 8000); // --- Page Transitions --- document.querySelectorAll('a.nav-link, a.dropdown-item, .card-body a, .plan-card a, .race-list-item a, .blog-card a, .author-bio a, .pb-ticker-card a, .coach-spotlight a, .gear-spotlight a, .cross-train-card a').forEach(a=>{ a.addEventListener('click', (ev)=>{ const href = a.getAttribute('href'); if (a.hasAttribute('download')) return; if (!href || href.startsWith('http') || href.startsWith('mailto:') || href.startsWith('#')) return; ev.preventDefault(); document.body.style.transition = 'opacity 0.3s ease-out'; document.body.style.opacity = '0'; setTimeout(()=> { location.href = href; }, 300); }); }); // --- NEW: Pace Pro Calculator Logic --- const calcPaceProBtn = document.getElementById('calcPaceProBtn'); if (calcPaceProBtn) { calcPaceProBtn.addEventListener('click', () => { const dist = parseFloat(document.getElementById('paceDist').value); const unit = document.getElementById('paceUnit').value; const timeStr = document.getElementById('paceTime').value; // "00:50:00" const resultEl = document.getElementById('paceProResult'); if (!dist || dist <= 0) { resultEl.textContent = "Enter a valid distance."; return; } const parts = timeStr.split(':').map(Number); let totalSeconds = 0; if (parts.length === 3) totalSeconds = (parts[0] * 3600) + (parts[1] * 60) + parts[2]; else if (parts.length === 2) totalSeconds = (parts[0] * 60) + parts[1]; else { resultEl.textContent = "Use HH:MM:SS or MM:SS format."; return; } if (totalSeconds <= 0) { resultEl.textContent = "Enter a valid time."; return; } const pacePerUnit = totalSeconds / dist; const paceMinutes = Math.floor(pacePerUnit / 60); const paceSeconds = Math.round(pacePerUnit % 60); resultEl.textContent = `Req. Pace: ${paceMinutes}:${String(paceSeconds).padStart(2, '0')} /${unit}`; }); } // --- Gemini API for Injury Tip --- const geminiApiKey = ""; // API key is handled by the environment const geminiApiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${geminiApiKey}`; const geminiSystemPrompt = "You are an expert running physio and coach. A user has selected a pain area. Provide 1-2 concise, actionable *prevention* tips (e.g., 'Strengthen hips...', 'Improve cadence...'). Do not diagnose. Be helpful and direct. Do not use greetings or markdown."; async function fetchWithRetry(url, options, retries = 3, delay = 1000) { /* ... same fetch retry ... */ for(let i=0;i= 400 && r.status < 500) throw new Error(`Client error: ${r.status}`); } catch(e){} await new Promise(res => setTimeout(res, delay)); delay*=2; } throw new Error('API fetch failed.'); } async function callGeminiApi(userPrompt, systemPrompt) { const payload = { contents: [{ parts: [{ text: userPrompt }] }], systemInstruction: { parts: [{ text: systemPrompt }] }, }; const options = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }; const result = await fetchWithRetry(geminiApiUrl, options); const candidate = result.candidates?.[0]; if (candidate?.content?.parts?.[0]?.text) { return candidate.content.parts[0].text; } else { throw new Error('Invalid API response.'); } } const askInjuryBtn = document.getElementById('askInjuryBtn'); if (askInjuryBtn) { askInjuryBtn.addEventListener('click', async () => { const area = document.getElementById('injuryAreaSelect').value; const loadingEl = document.getElementById('injuryBotLoading'); const responseEl = document.getElementById('injuryBotResponse'); if (!area) return; askInjuryBtn.disabled = true; loadingEl.style.display = 'block'; responseEl.style.display = 'none'; try { const prompt = `What are 1-2 key prevention tips for ${area}?`; const answer = await callGeminiApi(prompt, geminiSystemPrompt); responseEl.innerHTML = answer.trim().replace(/\n/g, '
'); } catch(err) { responseEl.textContent = "Error loading tip. Focus on gradual mileage increase and strength training."; } finally { askInjuryBtn.disabled = false; loadingEl.style.display = 'none'; responseEl.style.display = 'block'; } }); } // --- Initial Load --- loadLatestRunningArticles(); loadCrossTrainingPosts(); $('[data-toggle="popover"]').popover({trigger: 'hover'}); // Initialize Bootstrap Popovers