Hearts (6♥) is a research-based Progressive Web App designed specifically for teenage muscle building and fitness tracking. The application combines evidence-based exercise programming with intuitive progress tracking, creating a comprehensive fitness companion that works both online and offline.
// Core Technologies
- Framework: React 18 (via CDN)
- Styling: Custom CSS with CSS Variables
- State Management: React Hooks (useState, useEffect, useCallback)
- Build Process: No build step - direct browser execution
- PWA: Custom service worker implementation
// Firebase Integration
- Database: Firestore (NoSQL document database)
- Authentication: None (single-user design)
- Storage: Firebase Storage (for progress photos)
- Hosting: GitHub Pages
- Offline Storage: Service Worker caching + localStorage
// sw.js - Core Functionality
const CACHE_NAME = 'fittrack-pro-v1';
const urlsToCache = [
'./',
'./index.html',
'./manifest.json',
// External dependencies cached for offline use
];
// Features:
- Resource caching for offline functionality
- Push notification scheduling
- Background sync capabilities
- Daily notification system (23:59 schedule)
{
"name": "6♥",
"short_name": "6♥",
"description": "Your personal fitness tracking companion",
"display": "standalone",
"background_color": "#FAFAFA",
"theme_color": "#3B82F6",
"orientation": "portrait-primary"
}
The workout logging system provides real-time exercise tracking with integrated timers and form guidance.
// Exercise detail popup includes:
- Exercise name and muscle targets
- Form instruction carousel
- Weight input with rounded styling
- Set/rep tracking interface
- Rest timer integration
- Progress notes section
// Firebase structure for exercise weights
Collection: 'stats'
Document: '{exercise_name}'
Fields: {
starting_weight: number,
current_weight: number,
last_updated: timestamp
}
// Auto-populated from previous sessions
const loadExerciseWeight = async (exerciseName) => {
const doc = await db.collection('stats').doc(exerciseName).get();
return doc.exists ? doc.data().current_weight : '';
};
// Daily metrics tracked:
- Workout completion percentage
- Water intake (ml consumed vs target)
- Hydration goal progress
- Calorie tracking with visual indicators
- Exercise weight progressions
- Workout duration vs targets
// Dashboard cards include:
1. Daily Summary Card
- Date display with day name
- Quick stats overview
- Progress percentage rings
2. Hydration Tracking Card
- 250ml increment buttons
- Visual progress bar
- Daily target (4000ml default)
- Streak tracking
3. Calorie Tracking Card
- Meal logging interface
- Daily target progress
- Quick-add common foods
4. Notification Settings Card
- Push notification toggle
- Daily reminder settings
- Permission management
The app implements a sophisticated notification system using service workers for persistent notifications.
// Service worker notification scheduling
function scheduleNextNotification() {
const now = new Date();
const target = new Date();
target.setHours(23, 59, 0, 0);
if (now > target) {
target.setDate(target.getDate() + 1);
}
const timeUntilNotification = target.getTime() - now.getTime();
notificationTimeout = setTimeout(() => {
sendDailyNotification();
scheduleNextNotification(); // Reschedule for next day
}, timeUntilNotification);
}
// Daily summary notification format
const notificationData = {
title: `${dateString} Summary`,
body: 'Check your daily progress!',
icon: './icon-192x192.png',
badge: './icon-192x192.png',
tag: 'daily-summary',
requireInteraction: false,
actions: [{
action: 'open',
title: 'View Progress'
}]
};
The Hearts app includes a comprehensive exercise database organized by muscle categories, each with detailed form guidance and progression tracking.
// Five main exercise categories:
1. Arms (arms_exercises.json)
2. Shoulders (shoulders_exercises.json)
3. Chest (chest_exercises.json)
4. Core (core_exercises.json)
5. Back (back_exercises.json)
// Each exercise includes:
{
"exercise_name": {
"name": "Display name",
"primary_muscles": ["target muscle groups"],
"secondary_muscles": ["supporting muscles"],
"type": "compound|isolation",
"difficulty": "beginner|intermediate|advanced",
"home_compatible": boolean,
"gym_compatible": boolean,
"equipment_needed": {
"home": ["required equipment"],
"gym": ["gym equipment options"]
},
"sets_reps": {
"week_1": { "sets": 3, "reps": "8-10", "rest_seconds": 90 },
"week_2": { "sets": 4, "reps": "10-12", "rest_seconds": 90 }
},
"form_cues": ["detailed form instructions"],
"common_mistakes": ["what to avoid"],
"progression_options": ["advancement strategies"],
"regression_options": ["easier variations"],
"youtube_tutorial": "video URL",
"research_notes": "scientific backing",
"safety_tips": ["injury prevention"],
"breath_pattern": "breathing instruction",
"tempo": "timing guidance"
}
}
Each exercise includes embedded YouTube tutorials from certified fitness professionals, ensuring proper form education and injury prevention.
Hearts implements a research-based two-week priority muscle building program designed specifically for teenagers.
// Core principles:
- Session Duration: 30 minutes maximum
- Weekly Frequency: 4 training days + 1 integration day
- Rest Days: 2 complete rest days
- Priority Muscles: Arms, shoulders, abs, chest
- Superset Focus: 50% time reduction strategy
- Volume Distribution: 10-20 sets per muscle weekly
// 7-day program cycle:
Day 1: Arms & Shoulders Priority (28 minutes)
Day 2: Chest & Abs Priority (26 minutes)
Day 3: Active Recovery (15-20 minutes)
Day 4: Arms & Chest Focus (29 minutes)
Day 5: Shoulders & Abs Focus (27 minutes)
Day 6: Full Upper Body Integration (30 minutes)
Day 7: Complete Rest
// Example Day 1 Structure:
Superset 1: EZ Bar Curls + Overhead Tricep Extension
- 3 sets x 8-10 reps each
- 15 seconds rest between exercises
- 90 seconds rest between rounds
Superset 2: Dumbbell Shoulder Press + Lateral Raises
- 3 sets x 8-10 + 12-15 reps
- 15 seconds rest between exercises
- 90 seconds rest between rounds
Superset 3: Hammer Curls + Rear Delt Flyes
- 2 sets x 10-12 + 12-15 reps
- 10 seconds rest between exercises
- 60 seconds rest between rounds
The program provides equivalent home and gym versions for each workout:
// Week 1 to Week 2 progression:
General Rules:
- Reps: Add 2-3 reps when possible
- Sets: Add 1 set for key exercises
- Weight: Increase 2.5-5% if completing all reps easily
- Rest: Maintain 1-2 minutes for time efficiency
Specific Exercise Progressions:
- Dumbbell Curls: 3x8-10 → 4x10-12
- Triangle Push-ups: 3x6-8 → 4x8-10
- Plank: 2x30-45s → 3x45-60s
Hearts includes a comprehensive nutrition tracking system with pre-designed recipes optimized for teenage muscle building.
The app includes 25+ recipes across 9 categories, each with complete macro breakdowns and preparation instructions.
// Recipe organization:
pre_workout: ["banana_oats", "energy_bites", "peanut_butter_toast"]
post_workout: ["protein_smoothie", "chocolate_milk", "recovery_shake"]
high_protein_snack: ["greek_yogurt_berries", "protein_muffin", "protein_bar_homemade"]
high_calorie_snack: ["nuts_dates", "trail_mix"]
bedtime_protein: ["cottage_cheese_fruit", "casein_pudding"]
balanced_snack: ["avocado_toast", "hummus_veggies", "apple_almond_butter"]
breakfast_replacement: ["smoothie_bowl", "overnight_oats", "protein_pancakes"]
meal_replacement: ["quinoa_salad"]
evening_treat: ["milk_cookies"]
{
"name": "Post-Workout Protein Smoothie",
"prep_time_minutes": 3,
"nutrition": {
"calories": 520,
"protein_g": 35,
"carbs_g": 58,
"fat_g": 12,
"fiber_g": 6
},
"ingredients": [
{"item": "Whey protein powder", "amount": "30g", "notes": "Vanilla or chocolate"},
{"item": "Frozen banana", "amount": "1 large", "notes": "Natural sweetness and carbs"},
{"item": "Whole milk", "amount": "300ml", "notes": "Additional protein"},
{"item": "Rolled oats", "amount": "30g", "notes": "Complex carbs"},
{"item": "Peanut butter", "amount": "1 tbsp", "notes": "Healthy fats"}
],
"timing_notes": "Consume within 30-60 minutes post-workout",
"research_basis": "25g+ protein triggers muscle protein synthesis"
}
// Optimized for 69kg teenager:
daily_protein_goal_g: "233-251"
daily_calories_goal: "3730-4030"
post_workout_protein_g: "20-30"
post_workout_carbs_g: "30-50"
pre_workout_timing_minutes: 30
post_workout_timing_minutes: 60
// Optimized nutrient timing:
Pre-workout (30 min): Fast-digesting carbs, minimal fat
Post-workout (60 min): High protein + moderate carbs
Evening: Slow-digesting protein (casein)
Throughout day: Balanced macro distribution
Hearts is designed as a comprehensive PWA with native app-like functionality.
// App installation triggers:
1. Custom install prompt after 3 visits
2. Add to homescreen prompt for mobile
3. Desktop installation via browser
4. Automatic service worker registration
// Cached resources for offline use:
- Complete application shell
- Exercise database (all JSON files)
- Essential images and icons
- Recent workout data
- User progress history
// Background sync capabilities:
- Queue workout logs when offline
- Sync data when connection restored
- Persist user inputs locally
- Automatic conflict resolution
// Device capabilities utilized:
- Push notifications for daily reminders
- Camera access for progress photos
- Touch/haptic feedback for interactions
- Fullscreen mode for immersive workouts
- Background sync for data persistence
// Supported platforms:
- iOS Safari (iOS 11.3+)
- Android Chrome (Chrome 67+)
- Desktop browsers (Chrome, Firefox, Edge)
- Tablet devices (optimized layout)
// Platform-specific optimizations:
- iOS: Haptic feedback integration
- Android: Enhanced notification support
- Desktop: Keyboard navigation
- Tablet: Responsive grid layouts
Hearts features a modern, accessible interface optimized for touch interactions and mobile usage.
/* Color Palette */
:root {
--background-primary: #0F0F0F;
--background-secondary: #1A1A1A;
--text-primary: #FFFFFF;
--text-secondary: #A1A1AA;
--accent-blue: #60A5FA;
--accent-green: #34D399;
--accent-purple: #A78BFA;
}
/* Typography Scale */
.heading-xl { font-size: 2.25rem; font-weight: 700; }
.heading-lg { font-size: 1.875rem; font-weight: 600; }
.body-lg { font-size: 1.125rem; line-height: 1.6; }
.body-base { font-size: 1rem; line-height: 1.5; }
// Core UI components:
1. Dashboard Cards
- Rounded corners (12px border-radius)
- Consistent padding (20px)
- Shadow depth for hierarchy
- Responsive grid layout
2. Exercise Interface
- Modal overlay design
- Carousel for form instructions
- Progress indicators
- Touch-optimized controls
3. Input Components
- Rounded input fields (50px height)
- Haptic feedback on interaction
- Clear visual focus states
- Error state handling
4. Navigation
- Bottom tab bar (mobile)
- Breadcrumb navigation (desktop)
- Gesture-based interactions
- Smooth transitions
/* Breakpoint system */
@media (max-width: 768px) {
/* Mobile optimizations */
.dashboard-grid { grid-template-columns: 1fr; }
.exercise-modal { height: 100vh; }
.input-field { font-size: 16px; } /* Prevents zoom on iOS */
}
@media (min-width: 1024px) {
/* Desktop enhancements */
.dashboard-grid { grid-template-columns: repeat(2, 1fr); }
.exercise-modal { max-width: 600px; }
.sidebar-navigation { display: block; }
}
// WCAG 2.1 AA compliance:
- Semantic HTML structure
- ARIA labels for interactive elements
- High contrast color ratios (4.5:1 minimum)
- Keyboard navigation support
- Screen reader optimization
- Focus management for modals
- Alternative text for images
- Consistent navigation patterns
Hearts uses Firebase Firestore for cloud storage with local caching for offline functionality.
// Firestore collections structure:
/stats/{exerciseName}
- starting_weight: number
- current_weight: number
- last_updated: timestamp
/user_progress/{date}
- workouts_completed: number
- water_ml_consumed: number
- calorie_intake: number
- notes: string
/workout_logs/{workoutId}
- date: timestamp
- exercises: array
- duration_minutes: number
- completion_percentage: number
// localStorage usage:
localStorage.setItem('userPreferences', JSON.stringify({
hydrationGoal: 4000,
calorieGoal: 3800,
preferredWorkoutTime: 'morning',
notificationsEnabled: true
}));
// sessionStorage for temporary data:
sessionStorage.setItem('currentWorkout', JSON.stringify({
startTime: Date.now(),
exercisesCompleted: [],
currentExercise: 'dumbbell_curls'
}));
// Sync strategy:
1. Online: Direct Firebase operations
2. Offline: Queue operations in localStorage
3. Reconnection: Batch sync queued operations
4. Conflict resolution: Last-write-wins with timestamps
// Example sync function:
const syncPendingData = async () => {
const pendingOps = JSON.parse(localStorage.getItem('pendingOperations') || '[]');
for (const op of pendingOps) {
try {
await executeFirebaseOperation(op);
removePendingOperation(op.id);
} catch (error) {
console.log('Sync failed for operation:', op.id);
}
}
};
// Data loading strategies:
- Lazy loading for exercise images
- Pagination for workout history
- Compression for large datasets
- CDN usage for static assets
- Service worker caching
- Bundle size optimization
# Required tools:
- Git
- Modern web browser (Chrome/Firefox/Safari)
- Text editor (VS Code recommended)
- Firebase CLI (optional for advanced features)
- Local web server (for development)
# 1. Clone the repository
git clone https://github.com/username/hearts-fitness-app.git
cd hearts-fitness-app
# 2. Set up Firebase project
# - Create new Firebase project at console.firebase.google.com
# - Enable Firestore database
# - Copy configuration to index.html
# 3. Configure Firebase
# Replace firebaseConfig in index.html with your project config:
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "your-app-id"
};
# 4. Start local development server
# Option A: Python
python -m http.server 8000
# Option B: Node.js http-server
npx http-server -p 8000
# Option C: Live Server (VS Code extension)
# Right-click index.html -> "Open with Live Server"
# 5. Access application
# Navigate to http://localhost:8000
// 1. Firestore Security Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow read/write for all documents (single-user app)
match /{document=**} {
allow read, write: if true;
}
}
}
// 2. Storage Rules (for progress photos)
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
# 1. Feature development
git checkout -b feature/new-exercise-tracking
# Make changes
git add .
git commit -m "Add weight tracking for exercises"
# 2. Testing
# - Test offline functionality
# - Test PWA installation
# - Test across devices/browsers
# - Validate Firebase operations
# 3. Deployment
git push origin main
# GitHub Pages will automatically deploy
// Production optimizations:
1. Minify CSS and JavaScript
2. Optimize images (WebP format)
3. Enable compression (gzip/brotli)
4. Set up CDN for assets
5. Configure proper caching headers
6. Test PWA requirements
// Load today's workout
const loadTodaysWorkout = async () => {
const today = new Date().toISOString().split('T')[0];
const workoutDoc = await db.collection('workouts').doc(today).get();
return workoutDoc.exists ? workoutDoc.data() : getDefaultWorkout();
};
// Save workout progress
const saveWorkoutProgress = async (workoutData) => {
const today = new Date().toISOString().split('T')[0];
await db.collection('workouts').doc(today).set(workoutData, { merge: true });
};
// Calculate workout completion percentage
const calculateCompletionPercentage = (completedSets, totalSets) => {
return Math.round((completedSets / totalSets) * 100);
};
// Load exercise weight
const loadExerciseWeight = async (exerciseName) => {
try {
const doc = await db.collection('stats').doc(exerciseName).get();
if (doc.exists) {
return doc.data().current_weight || '';
}
return '';
} catch (error) {
console.error('Error loading exercise weight:', error);
return '';
}
};
// Save exercise weight
const saveExerciseWeight = async (exerciseName, weight) => {
try {
const doc = await db.collection('stats').doc(exerciseName).get();
const data = {
current_weight: parseFloat(weight),
last_updated: firebase.firestore.FieldValue.serverTimestamp()
};
if (!doc.exists) {
data.starting_weight = parseFloat(weight);
}
await db.collection('stats').doc(exerciseName).set(data, { merge: true });
} catch (error) {
console.error('Error saving exercise weight:', error);
}
};
// Update hydration progress
const updateHydrationProgress = async (amountMl) => {
const today = new Date().toISOString().split('T')[0];
try {
await db.collection('daily_progress').doc(today).update({
water_consumed_ml: firebase.firestore.FieldValue.increment(amountMl),
last_updated: firebase.firestore.FieldValue.serverTimestamp()
});
} catch (error) {
// Document doesn't exist, create it
await db.collection('daily_progress').doc(today).set({
water_consumed_ml: amountMl,
water_goal_ml: 4000,
created_at: firebase.firestore.FieldValue.serverTimestamp()
});
}
};
// Get daily progress summary
const getDailyProgress = async (date = null) => {
const targetDate = date || new Date().toISOString().split('T')[0];
const doc = await db.collection('daily_progress').doc(targetDate).get();
return doc.exists ? doc.data() : getDefaultProgress();
};
// Request notification permission
const requestNotificationPermission = async () => {
if (!('Notification' in window)) {
return 'not-supported';
}
if (Notification.permission === 'granted') {
return 'granted';
}
const permission = await Notification.requestPermission();
return permission;
};
// Schedule daily notification
const scheduleDailyNotification = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.active.postMessage({
type: 'SCHEDULE_NOTIFICATION',
data: { time: '23:59' }
});
});
}
};
// Load exercise data
const loadExerciseData = async (category, exerciseId) => {
try {
const response = await fetch(`${category}_exercises.json`);
const data = await response.json();
return data.exercises[exerciseId];
} catch (error) {
console.error('Error loading exercise data:', error);
return null;
}
};
// Get exercises by category
const getExercisesByCategory = async (category) => {
try {
const response = await fetch(`${category}_exercises.json`);
const data = await response.json();
return Object.keys(data.exercises).map(key => ({
id: key,
...data.exercises[key]
}));
} catch (error) {
console.error('Error loading exercises:', error);
return [];
}
};
// Format time duration
const formatDuration = (minutes) => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hours > 0) {
return `${hours}h ${mins}m`;
}
return `${mins}m`;
};
// Calculate progress percentage
const calculateProgress = (current, target) => {
return Math.min(100, Math.round((current / target) * 100));
};
// Get current week day
const getCurrentWeekDay = () => {
const date = new Date();
const day = date.getDay();
return day === 0 ? 7 : day; // Convert Sunday (0) to 7
};
// Haptic feedback (iOS)
const triggerHapticFeedback = (type = 'light') => {
if (window.navigator && window.navigator.vibrate) {
const patterns = {
light: [10],
medium: [50],
heavy: [100]
};
window.navigator.vibrate(patterns[type] || patterns.light);
}
};
// Issue: "Add to Home Screen" not appearing
// Solution: Ensure PWA requirements are met
const checkPWARequirements = () => {
const checks = {
manifest: document.querySelector('link[rel="manifest"]') !== null,
serviceWorker: 'serviceWorker' in navigator,
https: location.protocol === 'https:' || location.hostname === 'localhost',
icons: true // Check manifest.json has required icon sizes
};
console.log('PWA Requirements:', checks);
return Object.values(checks).every(check => check === true);
};
// Issue: Firebase operations failing
// Solution: Check configuration and network
const testFirebaseConnection = async () => {
try {
await db.collection('test').add({ timestamp: new Date() });
console.log('Firebase connection successful');
return true;
} catch (error) {
console.error('Firebase connection failed:', error);
// Check if offline, enable offline persistence
if (error.code === 'unavailable') {
enableOfflineMode();
}
return false;
}
};
const enableOfflineMode = () => {
db.enablePersistence()
.then(() => console.log('Offline persistence enabled'))
.catch(err => console.log('Offline persistence failed:', err));
};
// Issue: Notifications not working on iOS
// Solution: Proper feature detection and fallback
const checkNotificationSupport = () => {
const support = {
api: 'Notification' in window,
serviceWorker: 'serviceWorker' in navigator,
permission: Notification.permission
};
// iOS Safari limitations
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
if (isIOS) {
support.iosLimitations = 'iOS requires user gesture for notifications';
}
return support;
};
// Issue: App loading slowly
// Solution: Implement performance optimizations
const optimizePerformance = () => {
// Lazy load images
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
imageObserver.unobserve(entry.target);
}
});
});
images.forEach(img => imageObserver.observe(img));
// Preload critical resources
const preloadLinks = [
'arms_exercises.json',
'shoulders_exercises.json',
'chest_exercises.json'
];
preloadLinks.forEach(href => {
const link = document.createElement('link');
link.rel = 'preload';
link.href = href;
link.as = 'fetch';
link.crossOrigin = 'anonymous';
document.head.appendChild(link);
});
};
// Issue: Data not syncing between devices
// Solution: Implement proper sync mechanism
const handleDataSync = () => {
// Listen for online status
window.addEventListener('online', async () => {
console.log('Connection restored, syncing data...');
await syncPendingOperations();
});
window.addEventListener('offline', () => {
console.log('Connection lost, enabling offline mode...');
showOfflineNotification();
});
};
const syncPendingOperations = async () => {
const pending = JSON.parse(localStorage.getItem('pendingSync') || '[]');
for (const operation of pending) {
try {
await executeOperation(operation);
removePendingOperation(operation.id);
} catch (error) {
console.error('Sync failed for operation:', operation.id, error);
}
}
};
// Enable debug mode for troubleshooting
const enableDebugMode = () => {
window.DEBUG = true;
// Console logging for all Firebase operations
const originalFirestoreSet = db.collection('').constructor.prototype.set;
db.collection('').constructor.prototype.set = function(...args) {
if (window.DEBUG) console.log('Firestore SET:', this.path, args);
return originalFirestoreSet.apply(this, args);
};
// Performance monitoring
const performanceObserver = new PerformanceObserver((list) => {
if (window.DEBUG) {
list.getEntries().forEach(entry => {
console.log('Performance:', entry.name, entry.duration + 'ms');
});
}
});
performanceObserver.observe({ entryTypes: ['navigation', 'resource'] });
};
// Check browser compatibility
const checkBrowserSupport = () => {
const features = {
serviceWorker: 'serviceWorker' in navigator,
indexedDB: 'indexedDB' in window,
fetch: 'fetch' in window,
promise: 'Promise' in window,
asyncAwait: (async () => {})().constructor.name === 'AsyncFunction'
};
const unsupported = Object.entries(features)
.filter(([feature, supported]) => !supported)
.map(([feature]) => feature);
if (unsupported.length > 0) {
console.warn('Unsupported features:', unsupported);
showCompatibilityWarning(unsupported);
}
return unsupported.length === 0;
};
Hearts represents a comprehensive approach to teenage fitness tracking, combining research-based programming with modern web technologies. The app’s offline-first design, detailed exercise database, and progressive notification system create a reliable fitness companion that grows with the user’s fitness journey.
Hearts demonstrates how modern web technologies can create powerful, accessible fitness applications that rival native apps while maintaining broad compatibility and offline functionality.