A clean, beginner-friendly web app to log your workouts, track progress over time, and stay motivated β all without any backend or framework.
- Overview
- Live Demo
- Features
- Project Structure
- Getting Started
- How It Works
- Data Storage
- Responsive Design
- Technical Details
- Keyboard & Accessibility
- Browser Support
- Future Improvements
- Contributing
FitLog is a zero-dependency, client-side web application that helps gym-goers track their workout sessions. Log exercises with weights and reps, visualise progress over time, filter your history, and never lose your data β everything is stored locally in your browser using LocalStorage.
Built with only HTML, CSS, and Vanilla JavaScript β no React, no Node.js, no database, no internet connection required.
Since FitLog is a pure client-side app, you can run it directly by opening index.html in any modern browser. No server or build step needed.
π FitLog-Gym Progress Tracker/
βββ index.html β Just open this!
| Feature | Description |
|---|---|
| Add Workout | Log exercise name, weight (kg), reps, and date |
| Workout History | View all logged entries sorted newest-first by default |
| Delete Entry | Remove individual workout entries instantly |
| Filter by Exercise | Live search to filter history by exercise name |
| Sort Options | Sort by Newest, Oldest, Exercise Name, or Weight |
| Progress Indicator | See weight change (β² +5kg / βΌ -5kg / = Same) vs. previous session |
| Quick Stats | Total sessions, unique exercises, most logged, last workout date |
| Auto Date | Date field pre-filled with today, but fully editable |
| LocalStorage | All data persists across page reloads and browser restarts |
| Clear All | Delete entire workout history with a confirmation modal |
| Toast Notifications | Instant feedback on add, delete, and clear actions |
| Autocomplete | Exercise name input suggests previously used exercises |
- Dark gym theme with deep charcoal backgrounds and vibrant indigo/purple accents
- Animated background blobs for a premium visual feel
- Card-based layout with glassmorphism-inspired styling
- Hover micro-animations on workout cards (slide + glow)
- Fully responsive β all screen sizes from 360px β 1600px+
- Accessible β semantic HTML, ARIA labels, keyboard navigation
- Print styles β prints cleanly without UI chrome
- Reduced motion β respects
prefers-reduced-motionOS setting - Touch-optimised β bigger tap targets on mobile devices
FitLog-Gym Progress Tracker/
β
βββ index.html # App shell β HTML structure and semantic markup
βββ style.css # All styling β design tokens, components, responsive breakpoints
βββ script.js # All logic β CRUD, LocalStorage, filtering, sorting, UI updates
βββ README.md # This file
The project deliberately avoids build tools, bundlers, or package managers so it stays beginner-friendly and universally openable.
- Download or clone this repository
- Navigate to the folder
- Double-click
index.htmlβ it opens in your default browser
No npm install, no npm run dev, no terminal required.
Page Load
β
βΌ
loadWorkouts() β reads from LocalStorage
β
βΌ
render() β builds the workout list + stats
β
βββ No workouts? β show empty state
βββ Filter active? β show filtered results
βββ Show workout cards with progress badges
Add Workout (form submit)
β
βββ validateForm() β show inline errors if invalid
βββ Build workout object { id, exercise, weight, reps, date }
βββ workouts.unshift(entry) β add to top of array
βββ saveWorkouts() β JSON.stringify to LocalStorage
βββ render() + showToast()
Delete Workout (click trash icon)
β
βββ Remove entry from array by ID
βββ saveWorkouts() β update LocalStorage
βββ render() + showToast()
Filter / Sort
β
βββ filterInput event β update filterQuery, re-render
βββ sortSelect change β update sortMode, re-render
Clear All
β
βββ Click "Clear All" β open confirmation modal
βββ Confirm β workouts = [], saveWorkouts(), render()
βββ Cancel β closeModal()
For each workout entry displayed, FitLog looks through the entire workout history for any previous entry with the same exercise name that has an earlier date. It picks the most recent one and compares weights:
delta = current.weight - previous.weight
delta > 0 β β² +{delta}kg (green badge)
delta < 0 β βΌ {delta}kg (red badge)
delta = 0 β = Same (grey badge)
no prior β (no badge) (first entry)
FitLog uses the browser's LocalStorage API to persist data. No data is ever sent to a server.
fitlog_workouts_v1
Each workout is stored as a JSON object in an array:
[
{
"id": "lf3k2abc9",
"exercise": "Bench Press",
"weight": 85,
"reps": 10,
"date": "2026-04-09"
},
{
"id": "lf3k1xyz7",
"exercise": "Squat",
"weight": 100,
"reps": 8,
"date": "2026-04-08"
}
]| Field | Type | Description |
|---|---|---|
id |
string |
Unique ID generated from Date.now() + random suffix |
exercise |
string |
Exercise name as entered by the user |
weight |
number |
Weight in kilograms |
reps |
number |
Number of repetitions |
date |
string |
ISO date in YYYY-MM-DD format |
// Read
JSON.parse(localStorage.getItem('fitlog_workouts_v1')) || []
// Write
localStorage.setItem('fitlog_workouts_v1', JSON.stringify(workouts))
β οΈ Note: LocalStorage is browser and device specific. Data does not sync across devices. If you clear browser data/site data, your workout history will be erased.
FitLog is fully responsive across 9 breakpoints using a mobile-first approach:
| Breakpoint | Width | Layout |
|---|---|---|
| Large Desktop | β₯ 1280px | Wide 2-col, more padding, larger cards |
| Desktop | 1024pxβ1280px | Standard 2-col, narrower sidebar |
| Tablet Landscape | β€ 1024px | Sidebar shrinks to 300px |
| Tablet Portrait | β€ 900px | Stacked single column, stats as 4-col row |
| Large Mobile | β€ 640px | Full single column, full-width buttons |
| Small Mobile | β€ 480px | Tighter padding, dot separators hidden |
| Extra Small | β€ 360px | Minimal header, header stats hidden |
| Touch Devices | hover:none |
Bigger tap targets (42px delete btn) |
| Reduced Motion | OS setting | All animations disabled |
All user-entered content is sanitised before being inserted into the DOM using an escapeHtml() utility:
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}This prevents Cross-Site Scripting (XSS) attacks from malicious exercise names.
Each workout gets a unique ID that survives array mutation:
function generateId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
}
// Example output: "lf3k2abc9"Sorting is non-destructive β the original workouts array is never mutated for display:
function sortWorkouts(arr) {
const copy = [...arr]; // spread to avoid mutating source
switch (sortMode) {
case 'oldest': return copy.sort((a, b) => new Date(a.date) - new Date(b.date));
case 'exercise': return copy.sort((a, b) => a.exercise.localeCompare(b.exercise));
case 'weight': return copy.sort((a, b) => b.weight - a.weight);
default: return copy.sort((a, b) => new Date(b.date) - new Date(a.date));
}
}Styles follow a strict token-first architecture:
:root β Design tokens (colours, radii, shadows, transitions)
Reset & Base β Box-sizing, font, overflow
Background Blobs β Animated ambient gradients
Layout β App wrapper, header, main grid
Cards β Shared card + card-header + card-icon system
Form β Input grid, input wrappers, field errors
Buttons β btn base + modifiers (primary, danger, ghost)
Quick Stats β Stats grid and stat boxes
History β Header, filter controls, search wrapper
Workout List β Item cards, meta chips, progress badges
Empty States β Centred empty/no-results prompts
Modal β Backdrop + modal box
Toast β Slide-up notifications with dot indicators
Footer / Scrollbar β Minor finishing styles
Responsive (x9) β Full multi-breakpoint overrides
--bg-deep #0a0b10 /* Page background */
--bg-card #12141f /* Card surfaces */
--bg-input #1c1e2e /* Input backgrounds */
--accent #6366f1 /* Indigo β primary */
--purple #a855f7 /* Purple β accents */
--gold #f59e0b /* Gold β stats icon */
--green #22c55e /* Progress up */
--red #ef4444 /* Delete / progress down */
--text-primary #f1f5f9
--text-muted #64748b| Key / Action | Behaviour |
|---|---|
Tab |
Navigate between all interactive elements |
Enter on form |
Submits the Add Workout form |
Escape |
Closes the Clear All confirmation modal |
| Form validation | Inline errors shown below invalid fields |
| ARIA labels | All icon-only buttons have aria-label |
role="list" |
Workout history is semantically a list |
aria-live="polite" |
Toast container announced to screen readers |
role="dialog" + aria-modal |
Confirmation modal correctly identified |
aria-labelledby |
All sections labelled by their headings |
FitLog uses only stable, widely-supported Web APIs:
| API | Support |
|---|---|
| LocalStorage | All modern browsers (Chrome, Firefox, Safari, Edge) |
| CSS Grid / Flexbox | All modern browsers |
| CSS Custom Properties | All modern browsers |
datalist |
All modern browsers |
backdrop-filter |
Chrome 76+, Firefox 103+, Safari 9+ |
prefers-reduced-motion |
All modern browsers |
Internet Explorer is not supported (EOL since 2022).
Here are features that could be added in future versions:
- Charts & Graphs β Visualise weight progression per exercise using
<canvas>or SVG - Export to CSV / JSON β Let users download their workout history
- Import from JSON β Restore data from a backup file
- Sets support β Log multiple sets per exercise in one entry
- Rest timer β Built-in countdown timer between sets
- Custom categories β Tag exercises (Push / Pull / Legs / Cardio)
- Personal Records (PRs) β Highlight all-time best lifts with a π badge
- Dark / Light mode toggle β Switch between themes
- Weekly summary β "This week you did X sessions" dashboard
- PWA support β Make it installable on mobile as a Progressive Web App
- Body weight logging β Track body weight alongside workout data
Contributions are welcome! Since this is a frontend-only project, contributions can be as simple as suggesting a UX improvement or fixing a CSS layout bug.
- Fork the repository
- Create your feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add: my new feature' - Push to the branch:
git push origin feature/my-feature - Open a Pull Request
- Use
'use strict'in all JS - Keep functions small and single-purpose
- Use JSDoc comments for all functions
- Don't use
innerHTMLwith unsanitised user input β always useescapeHtml() - CSS: follow the existing token-first architecture, no magic numbers
Built with πͺ and Vanilla JS Β |Β No frameworks. No backend. Just pure web.