https://github.com/mrthames/simple-pitch-counter
Pitch counter app for youth baseball and softball — tracks pitch counts, rest-day rules, catcher innings, and generates game summaries. iOS and Android.
https://github.com/mrthames/simple-pitch-counter
android baseball ios kotlin little-league pitch-counter playwright softball swift youth-sports
Last synced: 2 months ago
JSON representation
Pitch counter app for youth baseball and softball — tracks pitch counts, rest-day rules, catcher innings, and generates game summaries. iOS and Android.
- Host: GitHub
- URL: https://github.com/mrthames/simple-pitch-counter
- Owner: mrthames
- License: other
- Created: 2026-03-22T01:52:38.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-04T00:03:18.000Z (2 months ago)
- Last Synced: 2026-05-04T00:31:48.178Z (2 months ago)
- Topics: android, baseball, ios, kotlin, little-league, pitch-counter, playwright, softball, swift, youth-sports
- Language: HTML
- Homepage: https://simplepitchcounter.com/
- Size: 13.9 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 14
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Simple Pitch Counter
A pitch counter app for youth baseball and softball coaches, built for the dugout.
This is also a portfolio project by **Justin Thames** demonstrating AI-native product development — from concept and product requirements through design, development, testing, and App Store release, built end-to-end with AI as a core collaborator.
**App Store:** [Simple Pitch Counter on iOS](https://apps.apple.com/us/app/simple-pitch-counter/id6760922314)
**Website:** [simplepitchcounter.com](https://simplepitchcounter.com)
## What it does
Simple Pitch Counter tracks pitch counts and catcher innings during live games, enforces league rest-day rules, and generates post-game summary reports — available on iPhone and Android.
### Key features
- **Two tracking modes** — Simple mode (tap-to-count) for basic pitch counting, or Advanced mode with per-pitch type tracking (Ball, Called K, Swing K, Foul, Ball in Play)
- **Out tracking in both modes** — out dots display in the header next to the inning pill; Simple mode has a dedicated `+ Out` button; 3 outs triggers an end-of-half confirmation modal
- **Mercy rule auto-prompt** — when runs scored in a half inning reach the configured mercy limit, a modal prompts to end the half or continue playing; tracks runs from either team
- **Advanced pitch tracking** — tracks balls, strikes, and outs automatically; records pitch-by-pitch sequences per at-bat with visual chips; auto-advances count on walks, strikeouts, and balls in play
- **Ball in play outcomes** — when a ball is put in play, a bottom sheet prompts for Safe/Out with automatic out tracking and side-retired detection
- **Hit By Pitch button** — record HBP with one tap; tracked as a walk on the BB hero count, with HBP also broken out as its own pitcher stat
- **Game clock with confirmation** — toggleable elapsed-time clock; rolls over from MM:SS to H:MM:SS at the one-hour mark, requires confirmation before pausing to prevent accidental stops
- **Inning scoreboard** — live inning-by-inning scoreboard in the game view with team abbreviations and R column; also displayed in game history cards and on shared stats card exports
- **Editable inning cells** — tap past inning cells to correct scores; team totals recalculate automatically
- **9-inning auto-end** — prompts to end the game after 9 innings when the score is not tied; allows extra innings when tied
- **Pitcher stats list** — "Stats ›" link opens a pitcher list first, then drill into individual pitcher detail with back navigation
- **Shareable stats cards** — generate and share pitcher stats as image cards (PNG) with K/BB/BIP hero boxes, pitch breakdowns, inning scoreboard, and rest info; works from live game, game summary, and history views
- **Live pitcher stats** — dark-themed bottom sheet showing K, BB, BIP totals and a full pitch type breakdown with percentage bars
- **Configurable league rules** — set pitch limits, rest-day thresholds, and catcher inning rules per division (e.g., Minors 8, Minors 9/10, Majors)
- **Threshold awareness** — alerts when a pitcher approaches or crosses a rest-day threshold, with support for the "finish the batter" rule
- **Catcher/pitcher eligibility** — flags when a catcher has caught 4+ innings (can't pitch) or a pitcher has thrown 41+ pitches (can't catch)
- **Physical button support** — map volume buttons and Action Button to pitch counting, undo, next batter, or — in Advanced mode — Called Strike and Ball actions, mimicking a traditional umpire tracking dial. Configurable per-button mapping
- **In-game name editing** — edit a pitcher or catcher's name and number mid-game from the Change picker without interrupting the game
- **Game summaries** — generates a formatted summary with pitcher data, scores, homeruns, pitch type breakdowns, innings count, end time, and umpire feedback, ready to email
- **Dark scoreboard header** — persistent dark-themed header with live score, inning/half indicator, and out tracking dots
- **Score and inning tracking** — built-in scoreboard with inning management and inning-by-inning run tracking
- **Swipe-to-delete history** — swipe game cards left to reveal delete action; history cards show mode badge (Simple/Advanced), live game indicator, and full inning scoreboard
- **Game history** — review past games, pitcher rest requirements, and stats
- **Result flash animations** — full-screen animated overlays for strikeouts, walks, outs, safe calls, and side retired
- **Data export/import** — export all game data as JSON (via Share Sheet on iOS) and import backups from the history menu
- **Pitch dot visualization** — dots below the hero count alternate shading per batter to show at-bat boundaries at a glance
- **Swipe-to-dismiss stats** — swipe down on any stats bottom sheet to dismiss it
- **In-app review prompts** — automatically prompts users to rate the app after games 3, 10, and 25 using native StoreKit (iOS) and Google Play In-App Review (Android)
- **About screen** — accessible from the history menu; shows app version, Rate This App link, links to website, privacy policy, feedback form, and Buy Me a Coffee
- **Game clock** — live elapsed time clock starts when a game begins, visible during gameplay and saved to history cards; toggleable from the menu
- **Text acronym pitch labels** — pitch breakdown bars use clear text labels (B, CS, SS, F, BIP) across all views with a legend for advanced mode
- **Fully offline** — no account, no internet, no ads. All data stays on your device via localStorage
### Game summary output
The app generates summaries matching the EDHLL Game Summary form:
- Date, game time, field name
- Division
- Home/away team names and scores
- Each pitcher: name, actual pitch count (incl. last batter), pitches thrown to last batter
- Pitch type breakdowns per pitcher (Advanced mode): B, K̲, K, F, BIP counts with K/BB/BIP totals
- Number of innings played and end time
- Homeruns (over-the-fence)
- Umpire summary (plate/base umpire, feedback, comments)
## Project structure
```
simple-pitch-counter/
├── app/ # iOS app
│ ├── index.html # All app UI, CSS, and JavaScript (V2)
│ ├── AppDelegate.swift # Native iOS shell — window setup
│ ├── ViewController.swift # WKWebView config, haptics, and volume button capture
│ ├── V1/ # Archived V1 app files
│ └── Assets.xcassets/ # App icons and colors
├── android/ # Android app
│ └── app/src/main/
│ ├── java/.../MainActivity.kt # WebView config, haptics, volume buttons
│ ├── assets/index.html # Shared web app (tracked in git)
│ └── res/ # Adaptive icons and resources
├── tests/ # Playwright E2E tests (254 tests)
│ ├── helpers.ts # Shared test utilities (startGame, addPitches, etc.)
│ ├── core-game-flow.spec.ts # Game lifecycle, scoring, outs, undo
│ ├── advanced-mode.spec.ts # Pitch types, BSO count, BIP, auto-advance
│ ├── thresholds-alerts.spec.ts # Rest days, pitch limits, mercy rule
│ ├── pitcher-catcher.spec.ts # Roster management, mid-game switches
│ ├── summary-export.spec.ts # Game summary, report generation, export
│ ├── shareable-stats.spec.ts # Share features, swipe-to-dismiss, image cards
│ ├── history-config.spec.ts # History cards, config presets, setup flow
│ ├── about-screen.spec.ts # About screen, links, back navigation
│ ├── v2-features.spec.ts # V2 features: name editing, button mapping, scoreboard, game clock, acronym labels, batch fixes
│ └── version-sync.spec.ts # Version sync across Android, iOS, and app UI
├── .github/workflows/ # CI/CD
│ └── test.yml # Runs Playwright tests on push/PR to main
├── website/ # simplepitchcounter.com (12 pages)
│ ├── index.html # Landing page with interactive phone mockup
│ ├── privacy.html # Privacy policy
│ ├── contact.html # Contact form
│ ├── feedback.html # Feedback form
│ ├── android-beta.html # Android beta signup
│ ├── articles.html # Guides index
│ ├── pitch-count-rules.html # Pitch count rules reference
│ ├── parents-guide.html # Parent's guide to pitch counting
│ ├── catcher-innings-rules.html # Catcher innings rules
│ ├── travel-ball-pitch-counts.html # Travel ball pitch counts
│ ├── mercy-rule.html # Mercy rule explainer
│ ├── what-is-pitch-count.html # What is a pitch count
│ ├── favicon.png # Blue-S app icon favicon
│ ├── apple-touch-icon.png # iOS Add to Home Screen icon
│ └── *.php # PHP backends (not tracked — contain credentials)
├── marketing/ # App Store screenshot assets
│ ├── screenshots.html # Screenshot background templates (8 slides)
│ ├── capture.mjs # Playwright script to render slide PNGs
│ └── sample-data.js # Realistic sample data for simulator screenshots
├── docs/ # Product and portfolio documentation
├── playwright.config.ts # Playwright test configuration
├── CHANGELOG.md # App change history
└── README.md
```
## Testing
The project has **254 Playwright E2E tests** covering all app functionality:
| Spec file | Tests | Coverage |
|-----------|-------|----------|
| `core-game-flow` | 20 | Game lifecycle, scoring, outs (both modes), undo, persistence |
| `advanced-mode` | 21 | Pitch types, BSO count, BIP modal, auto-advance, non-pitch outs |
| `thresholds-alerts` | 18 | Rest days, pitch limits, mercy rule modal, at-bat warnings |
| `pitcher-catcher` | 11 | Roster management, mid-game switches, inning tracking |
| `summary-export` | 19 | Game summary, umpire data, report generation, export |
| `shareable-stats` | 18 | Share features, swipe-to-dismiss, image card exports, K/BB/BIP boxes |
| `history-config` | 19 | History cards, config presets, setup flow, umpire clearing |
| `about-screen` | 8 | About screen, app info, links, back navigation |
| `v2-features` | 118 | Name editing, button mapping, pitcher stats list, inning scoreboard, editable cells, 9-inning auto-end, share card scoreboard, history scoreboard, review prompts, batch bug fixes, game clock, acronym labels, Hit By Pitch button, hour rollover, pause confirmation modal, hamburger close, pitcher-list export |
| `version-sync` | 2 | Version string consistency across Android, iOS, and app UI |
```bash
npm test # Run all tests (headless)
npm run test:headed # Run with visible browser
npm run test:ui # Open Playwright UI
```
Tests run automatically on every push and pull request to `main` via GitHub Actions (`test.yml`). The CI pipeline uses Node 20 and Chromium, and uploads test reports as artifacts (14-day retention).
## Architecture
### iOS app (`app/`)
The app is a native iOS shell wrapping a single-page web application:
- **AppDelegate.swift** — creates the window and sets `ViewController` as root
- **ViewController.swift** — configures a `WKWebView` constrained to the safe area, with persistent localStorage, portrait lock, native JS alert/confirm/prompt handling, external link handling, haptic feedback bridge (JS → native via `WKScriptMessageHandler`), dynamic status bar styling (light on game screen, dark elsewhere), volume button capture for physical button mapping, and StoreKit in-app review prompts
- **index.html** — the entire app in one file: HTML structure, CSS styling, and JavaScript logic. Uses localStorage for all data persistence. Features a dark scoreboard header, two game modes (Simple/Advanced), pitch type tracking, out tracking, mercy rule enforcement, and iOS-native visual language
### Android app (`android/`)
The Android app mirrors the iOS architecture — a native Kotlin shell wrapping the same web application:
- **MainActivity.kt** — configures an Android `WebView` with persistent localStorage, portrait lock, native JS alert/confirm/prompt handling, external link handling, haptic feedback bridge (JS → native via `@JavascriptInterface`), dynamic status bar styling with pre-API-30 fallback, volume button capture, Android back button handling, image sharing via `FileProvider`, Google Play In-App Review API prompts, and native inset injection (`--top-inset`, `--bottom-inset`, `--header-pad`) for safe area support across notch and non-notch devices
- **Adaptive icons** — foreground/background layers supporting circle, squircle, and rounded square launcher masks
- **index.html** — the same shared web app as iOS, tracked in git at `android/app/src/main/assets/index.html` and kept in sync with `app/index.html`
### Website (`website/`)
A static marketing site (12 pages) hosted on a Synology NAS via Web Station:
- Landing page with interactive phone mockups showing the Advanced mode UI
- App Store and Google Play download links with feature highlights
- Android beta signup page with PHP form handler
- 6 SEO content pages: pitch count rules, parent's guide, catcher innings rules, travel ball pitch counts, mercy rule, and what is a pitch count
- Articles index page linking all guides
- Privacy policy (required by App Store and Google Play)
- Contact and feedback forms with PHP/SMTP backend
- Confirmation emails sent to submitters from all three forms
- Clean URLs via directory-based Nginx routing
- Open Graph meta tags with branded 1200×630 images for link previews
- PNG favicon (blue-S app icon mark) and Apple Touch icon across all pages
- Deployed at simplepitchcounter.com
## Deployment
### iOS app
Built and submitted via Xcode on macOS. The `app/index.html` is bundled into the iOS app and loaded by the WKWebView. Build numbers auto-increment from git commit count.
1. Push changes to `main`, confirm CI tests pass
2. On Mac: Xcode → Integrate → Pull
3. Xcode → Product → Archive → Distribute to App Store Connect
4. Build appears in TestFlight automatically
### Android app
Built and submitted via Android Studio. The `android/app/src/main/assets/index.html` is tracked in git alongside the iOS copy and loaded by the Android WebView. Version codes auto-increment from git commit count.
1. Push changes to `main`, confirm CI tests pass
2. Android Studio: pull latest (`git pull`), **Build → Clean Project**
3. **Build → Generate Signed Bundle** using `upload-keystore.jks`
4. Upload AAB to Google Play Console → Closed testing → Closed Beta
5. Currently in closed beta — requires 12 opted-in testers for 14 days before production access
### Website
Hosted on a self-hosted Synology NAS via Web Station (Nginx). Deployed via SCP over the local network after any content or mockup changes. Each page is deployed to two locations for clean URL support (e.g., `android-beta.html` and `android-beta/index.html`).
**Note:** PHP files (`contact.php`, `feedback.php`, `android-beta.php`) contain SMTP/API credentials and are excluded from git via `.gitignore`. Connection details (host, port, SSH key) are kept outside the repo.
## License
This project is licensed under the [PolyForm Noncommercial License 1.0.0](LICENSE). You are free to view, fork, and learn from the code, but **commercial use is not permitted**. The App Store deployment and distribution of Simple Pitch Counter is exclusively owned by Justin Thames.
## Future plans
- Google Play production release (pending 14-day closed beta tester requirement)