The FAForever Patchnotes site is a Progressive Web App (PWA) built with vanilla JavaScript, CSS, and HTML. It follows a modular architecture with clean separation of concerns.
- HTML5 - Semantic markup
- CSS3 - Custom properties (CSS variables), Grid, Flexbox
- Vanilla JavaScript (ES6+) - No frameworks, class-based modules
- Font Awesome 6.4.0 - Icons (CDN with SRI)
- Google Fonts - Open Sans, Ubuntu (CDN)
- Netlify Identity - CMS authentication (optional)
- Service Worker - Offline support, caching strategy
- Web App Manifest - Installability, app metadata
- Cache API - Resource caching, version management
patchnotes/
├── index.html # Main entry point
├── manifest.json # PWA manifest
├── sw.js # Service worker
├── favicon.ico # Site favicon
├── CNAME # Custom domain config
├── LICENSE # GPL-3.0 License
├── README.md # Project overview
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
├── .gitignore # Git ignore rules
├── .editorconfig # Editor configuration
│
├── assets/
│ ├── data/
│ │ └── patches.json # Patch metadata (JSON)
│ └── images/
│ ├── backgrounds/ # Hero backgrounds
│ ├── Enhancements/ # Enhancement icons by faction
│ ├── faction/ # Faction logos
│ ├── icons/ # PWA icons
│ ├── orders/ # Order icons
│ ├── thumbnail/ # Social media thumbnails
│ └── units/ # Unit icons by faction/type
│
├── pages/
│ └── [year]/
│ └── [patch].html # Individual patch pages
│
├── scripts/
│ ├── logger.js # Centralized logging utility
│ ├── populatePatches.js # Main patch list rendering
│ ├── search.js # Search and filter logic
│ ├── coreUI.js # Theme, background management
│ ├── errorBoundary.js # Error handling and recovery
│ ├── performance.js # Performance monitoring
│ ├── analytics.js # Privacy-friendly analytics
│ ├── lazyLoader.js # Lazy loading and virtualization
│ ├── validatePatches.js # Data validation utility
│ ├── backToTop.js # Scroll-to-top functionality
│ └── pwa.js # PWA installation prompts
│
└── style/
├── root.css # CSS variables, base styles
├── index.css # Main stylesheet
├── critical.css # Above-the-fold styles
├── balance.css # Patch page styles
├── pwa.css # PWA-specific styles
└── components/ # Component-specific styles
├── button.css
├── enhanced-ui.css
├── images.css
├── patch_card.css
├── patch_footer.css
├── patch_intro.css
├── patch_layout.css
├── patch_responsive.css
├── patch_sidebar.css
├── patch_sidemenu.css
└── patch_typography.css
Purpose: Centralized logging with environment awareness
Features:
- Development vs production modes
- Log levels (debug, info, warn, error)
- Performance metrics
- Grouped logging
Usage:
const logger = new Logger('ComponentName');
logger.debug('Debug message', { data });
logger.info('Info message');
logger.error('Error message', error);Purpose: Fetch and render patch list from JSON data
Responsibilities:
- Fetch patches.json
- Render patch cards
- Handle loading/error states
- Initialize search functionality
Key Functions:
populate()- Main entry pointrenderPatchList()- Render patches to DOMshowLoadingState()- Display loading skeletonshowErrorState()- Display error with retry
Purpose: Real-time search and filtering of patches
Features:
- Debounced search (300ms)
- Year-based filtering
- Keyboard shortcuts (Ctrl+K, Escape)
- Search statistics
Key Methods:
performSearch(query)- Execute searchfilterByYear(year)- Filter by yearclearSearch()- Reset all filters
Purpose: Theme switching and background management
Features:
- Light/dark theme toggle
- System preference detection
- LocalStorage persistence
- Dynamic backgrounds
Components:
- Theme manager
- Background rotation
- Faction icon display
Purpose: Global error handling and recovery
Features:
- Catch unhandled errors
- Retry failed resources
- User-friendly error messages
- Error reporting
Capabilities:
- Automatic retry for critical resources
- Error logging for debugging
- Graceful degradation
Purpose: Track and report performance metrics
Metrics:
- Page load time
- Search performance
- Network timing
- Render performance
Usage:
const monitor = new PerformanceMonitor();
const metrics = monitor.getMetrics();Purpose: Privacy-friendly user behavior tracking
Tracked Events:
- Page views
- Search queries
- Patch interactions
- Theme changes
- Errors
Privacy:
- No third-party services
- Local data only
- No PII collection
Purpose: Optimize loading with intersection observer
Features:
- Image lazy loading
- Virtual scrolling (for large lists)
- Background image loading
- Fallback for older browsers
Usage:
window.lazyLoader.observe('[data-src]');Purpose: Validate patches.json structure
Validates:
- Required fields (patch, link, date)
- Date format (Month DD, YYYY)
- Duplicate detection
- Sorting order
Usage:
const result = await PatchValidator.validateFromFile();
console.log(PatchValidator.generateReport(result));1. index.html loads
↓
2. Critical CSS applied
↓
3. Logger initialized
↓
4. Service Worker registered
↓
5. DOM Content Loaded
↓
6. populatePatches.js fetches patches.json
↓
7. Patches rendered to DOM
↓
8. Search initialized with patch data
↓
9. Lazy loader observes images
User types in search
↓
300ms debounce
↓
PatchSearch.performSearch()
↓
Filter patches array
↓
Apply additional filters (year)
↓
renderPatchList()
↓
Update search statistics
User clicks theme button
↓
Toggle light-mode class on <html>
↓
Save to localStorage
↓
Update background
↓
Update button text/aria-label
-
Static Cache (STATIC_CACHE)
- Core HTML, JS, CSS files
- Faction images
- patches.json
- Never expires (version-based)
-
Dynamic Cache (DYNAMIC_CACHE)
- Individual patch pages
- User-requested resources
- LRU eviction
-
CSS Cache (CSS_CACHE)
- Stylesheet files
- Cache-busted with version query params
- Force refresh on deploy
// sw.js
const CACHE_VERSION = '2.2.0';
const STATIC_CACHE = `static-v${CACHE_VERSION}`;
// On activate, delete old caches
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(key => key !== STATIC_CACHE)
.map(key => caches.delete(key))
)
)
);
});- Search State: Managed by
PatchSearchclass - Theme State: Stored in
localStorage, class on<html> - Patch Data: Fetched once, stored in memory
- Filter State: Maintained in
PatchSearchinstance
We use modular classes instead of global state:
// Each module manages its own state
const patchSearch = new PatchSearch();
const logger = new Logger('App');Currently using standard DOM events. Could enhance with custom events:
// Example for future implementation
document.dispatchEvent(new CustomEvent('patchesLoaded', {
detail: { patches, count }
}));- Above-the-fold styles in
<head> - Non-critical CSS deferred
<link rel="preload" href="./style/index.css" as="style">
<link rel="preload" href="./assets/images/faction/UEF.svg" as="image">- 300ms delay prevents excessive filtering
const fragment = document.createDocumentFragment();
// Batch DOM operations
container.appendChild(fragment);- Images loaded on viewport intersection
- Reduces initial payload
- Offline-first architecture
- Instant repeat visits
- Search queries are escaped
- No
innerHTMLwith user content - No
eval()orFunction()constructor
- HTTPS only
- SRI hashes for CDN resources
- Trusted sources only
Content-Security-Policy:
default-src 'self';
script-src 'self' https://identity.netlify.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' https://raw.githubusercontent.com;
font-src 'self' https://fonts.gstatic.com;
- Semantic HTML5 elements
- ARIA labels and roles
- Keyboard navigation
- Focus management
- Screen reader support
- Color contrast compliance (WCAG AA)
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- iOS Safari 14+
- Chrome Android 90+
- Works without JavaScript (basic HTML)
- Works without Service Worker (no offline mode)
- Works without CSS Grid (fallback to flexbox)
Currently no build step (vanilla JS/CSS/HTML).
# Proposed for future
npm run build
├── Minify JS
├── Minify CSS
├── Optimize images
├── Generate SRI hashes
└── Update service worker version- Static site hosting (GitHub Pages, Netlify, etc.)
- HTTPS required for PWA features
- CDN recommended for global performance
- Page load performance
- Search performance
- Error rates
- User interactions
- Could add server-side analytics
- Error reporting service
- Performance monitoring
- TypeScript migration - Type safety
- Build pipeline - Optimization
- Testing framework - Jest, Cypress
- Component library - Reusable UI components
- API endpoints - Dynamic patch loading
- GraphQL - Flexible data queries
- State management - If complexity grows
When adding new features:
- Follow modular class-based approach
- Use Logger for debug output
- Add JSDoc comments
- Update this documentation
- Maintain backward compatibility
- Test across browsers
- Consider mobile performance
For questions about architecture decisions, open a GitHub issue or ask in Discord.
Last Updated: February 5, 2026