Release history
Changelog
Every new feature, fix and speed-up on s&box Explorer, newest first.
- Last 30 days
- 90
- Features shipped
- 233
- Fixes
- 218
- Days with changes
- 66
4 changes in May 2026 Clear filters
May 2026
1
-
Performance
4:36 AM
Improve UX, accessibility, and code quality for 4 pages (#15)
Details
- Improve UX, accessibility, and code quality for news, charts, and global-stats pages
- /news: Add reading progress bar, skeleton loading, grid/list view toggle, scroll-to-top button, keyboard navigation (J/K), article count display, better ARIA attributes, rel=noopener on external links, proper time elements
- /orgnews: Add server-side search, client-side sorting (newest/A-Z), relative time display, 'New' badge on recent articles, better ARIA labels, preserve search params in pagination links
- /ugc/charts/game: Add live player count summary with pulsing indicator, refresh button, section item counts, staggered card animations, IntersectionObserver for sections, keyboard navigation, focus-visible styles, reduced motion support, improved empty state with alternative link
- /global-stats: Extract ~250 lines of inline styles to dedicated CSS file (frontend/css/pages/global-stats.css), add ARIA roles/labels/live regions, aria-pressed on tag buttons, aria-checked on range buttons, hover transitions on metric cards, focus-visible on game cards, reduced motion support
- Improve accessibility and fix structural issues in frontend PHP pages
- changelog.php: Remove duplicate theme-manager.js script, remove extra </div></div> tags, move </main> before scripts/footer, add role/aria-label to main, add aria-current to active pagination and filter buttons, wrap pagination controls in nav with aria-label
- about.php: Add role/aria-label to main, add aria-hidden to decorative icons, add noreferrer to external links
- credits.php: Add role/aria-label to main, add aria-hidden to decorative icons, remove stray </div>, add noreferrer to external links
- donate.php: Add role/aria-label to main, add aria-hidden to decorative icons
- new-user-guide.php: Add role/aria-label to main, add aria-hidden to hero icon
- release-notes.php: Add role/aria-label to main, add aria-hidden to decorative icons, add rel=noopener noreferrer to sbox.game link, remove stray </div>
- Improve accessibility and UX across all remaining frontend pages
Pages improved:
- compare.php: Extract inline CSS to external file, add ARIA roles/labels, focus-visible styles, prefers-reduced-motion support
- search.php: FIX CRITICAL BUG - broken <style> tag that included footer inside it. Scripts moved to bottom, added role=search, aria-labels
- leaderboard.php: Add aria-pressed on filter buttons, role=list/listitem in JS rendering, aria-live=polite on container
- favorites.php: Change div to semantic main element, add role=list/listitem, aria-live=polite, aria-hidden on decorative icons
- followed.php: Same accessibility pattern as favorites
- notifications.php: Add role=main, aria-live on container, aria-labels on mark-all-read and remove-all buttons
- settings.php: Add role=main, aria-label on main and form elements
- settings-language.php: Add role=main, aria-current=page on active nav, role=radiogroup on language grid
- notifications-following.php: Add role=main, aria-hidden on decorative icons, role=list/listitem on grid items
- CSS overhaul: extract inline styles and modernize page stylesheets
- notifications-following.php: Extract ~250 lines of inline CSS to frontend/css/pages/notifications-following.css (following grid, cards, badges, empty state, unfollow buttons, responsive)
- settings-language.php: Extract ~150 lines of inline CSS to frontend/css/pages/settings-language.css (settings layout, sidebar, language cards, responsive)
- compare.css: Add card entrance animation (fadeInScale with stagger), stat value hover effect, button active click feedback
- global-stats.css: Add card entrance states for IntersectionObserver, focus-visible on hero, update reduced-motion block
- charts.css: Verified existing focus-visible, reduced-motion, and responsive styles are all present
- Replace ugly #04131c header color with #0c1222 (matches navy palette)
The old #04131c had a greenish undertone that clashed with the blue-navy palette (--bg-primary: #080f1f, --bg-secondary: #181f2f). Replaced with #0c1222 which sits naturally between the two and keeps the header visually distinct without looking out of place.
- Align CSS variables with sbox.game official theme
Synced root variables to match the sbox.game design system:
- --site-header-height: 56px -> 60px (matches official)
- --site-header-color: kept #0c1222 (improved over official #04131c)
- --text-secondary: #b8c0d9 -> #adb3cb (official --text-color)
- --text-muted: #8892a8 -> #b2bed4 (official)
- --link: now uses color-mix formula from sbox.game
- --link-hover: now var(--accent-bright) instead of #fff
- font-size: 14px -> 13px (matches official)
- body background: now uses var(--site-background-color) semantically
- Added --admin/#c4214f, --admin-alt, --text-on-strong
- Added --form-surface, --form-divider, --form-label-color
- Added --column-gap: 0.75rem
- Added sbox.game menu color variables (--menu-option-color, etc)
- CSS audit: remove dead vendor prefixes, migrate z-index to variables, tokenize colors
Dead code removal:
- Remove 10 deprecated -webkit-overflow-scrolling: touch declarations
- Remove 1 -ms-overflow-style: none (IE is dead)
Z-index migration (156 hardcoded -> variables):
- Expand z-index scale: add --z-below, --z-raised, --z-header, --z-popover, --z-loading, --z-command-palette
- Replace ALL 156 hardcoded z-index values with var(--z-*) tokens
- Only z-index: 0 (stacking context resets) left as literals
Color tokenization:
- Add --purple (#8b5cf6) and --purple-light (#a78bfa) to design system
- Replace 33 #4ade80 -> var(--success-light)
- Replace 19 #22c55e/#10b981 -> var(--success)
- Replace 29 #ef4444 -> var(--error)
- Replace 14 #f87171 -> var(--error-light)
- Replace 18 #f59e0b/#fbbf24 -> var(--warning)
- Replace 11 #3498db -> var(--info)
- Replace 11 #8b5cf6 -> var(--purple)
- Replace #a78bfa -> var(--purple-light)
- Fix toast.css to use design system variables for success/error/warning
- Update base.css
- Fix TypeError: navigator.clipboard.writeText on undefined in package-detail-sections.php
Added fallback using document.execCommand('copy') for browsers/contexts where navigator.clipboard is unavailable (e.g., non-HTTPS or older browsers).
- Security, performance, and code quality improvements
Security:
- Fix navigator.clipboard TypeError in mcp-knowledge.js (2), settings.js (1) with document.execCommand fallback for non-HTTPS/older browsers
- Restrict CORS on settings API from wildcard to same-origin only
- Add CSRF token validation to admin-tools-api.php POST requests with auto-injection via admin.js fetch wrapper and token rotation
- Add Content-Security-Policy and Permissions-Policy headers to SecurityMiddleware.php (whitelists required CDNs, blocks frame/object)
- Stop leaking exception messages in search-api.php error responses
Performance:
- Rewrite notification-bell.js: 30s polling (was 10s), 2min when hidden, exponential backoff on errors, beforeunload cleanup
- Add try/catch guards for localStorage in command-palette.js (prevents crashes in private browsing mode)
Code quality:
- Extract admin/index.php inline styles to admin-dashboard.css (BEM classes)
- Consolidate duplicate .text-gradient/.gradient-text in design-system.css
- Remove excessive error_log() calls from settings.php
- Strip all code comments from PHP, JS, and CSS files
Removed all inline (//) and block (/* */) comments across the entire codebase (578 files scanned, 261 modified):
- 175 PHP files: stripped via PHP tokenizer (safe with strings/heredocs)
- 63 JS files: stripped via terser with beautified output
- 70 CSS files: stripped via regex (/* */ only, no string ambiguity)
- 16 PHP files: additional pass for comments inside <script>/<style> blocks
Only functional code remains. String literals containing // or /* (URLs, regex patterns, programmatic comment builders) are preserved correctly.
- Collapse excessive blank lines left by comment removal
Reduces 2+ consecutive blank lines to a single blank line across all 426 source files. Removes trailing whitespace and leading blank lines after <?php tags.
- Lint all PHP files with php-cs-fixer (PSR-12 + PHP 8.2+)
Ran php-cs-fixer v3.95.2 across all 390 PHP files (321 modified). Applied rules include:
- PSR-12 formatting (indentation, braces, spacing)
- PHP 8.2 migration (modern syntax, typed properties)
- PHP 8.0 risky rules (arrow functions, named args, match)
- Strict comparisons and strict params
- Ordered imports (alpha, class/function/const groups)
- Trailing commas in multiline arrays/args/params
- Yoda-style disabled (natural reading order)
- Void return types added where applicable
- Ternary to null coalescing where possible
Also expanded .php-cs-fixer.dist.php finder to cover admin/, auth/, mcp/, tests/, and tools/ directories (previously only backend/, frontend/, includes/, config/).
- feat: implement Facepunch API features - hidden gems, browse filters, tag cloud, game modes, achievement rarity, review playtime, enhanced cards
- Add /hidden-gems page with sort:hiddengem discovery grid
- Add browse-api.php endpoint with full sort/filter support
- Add browse-sort-orders.js with all API sort orders
- Add browse-filters.php faceted sidebar (genre, style, stars, size, review tags)
- Add tag-cloud.php weighted tag cloud partial
- Add game-mode-badges.php with SinglePlayer/MultiPlayer/Coop/Versus badges
- Add achievement rarity tiers (Common/Uncommon/Rare/Epic/Legendary)
- Add review-playtime.php horizontal bar chart visualization
- Add package-card-enhanced.php with ThumbWide, FileSize, dates, WIP/player badges
- Add getGameModeBadges(), formatFileSize(), formatRelativeDate() helpers
- Fix 400 errors and integrate new features into UI
Fixes:
- Fix 400 Bad Request on /api.php/packages/search calls from homepage-categories.js loadSubFilters() — was missing required 'q' param. Now uses type-specific endpoint /packages/{type} instead.
Navigation:
- Add Hidden Gems link (diamond icon) to main site navigation
UI Integration:
- Hidden Gems page: add browse filters sidebar with sticky layout, enhanced cards showing ThumbWide, live player badge, FileSize, dates
- Search page: add tag cloud (fetches live tag counts from API) shown when no search query is active
- Package detail sidebar: add game mode badges (SinglePlayer, Multiplayer, Co-op, Versus) before tags section
- CSS: import browse-filters.css and hidden-gems.css in styles.css
- Fix category+tag filtering and hidden gems filters
Homepage categories:
- Fix PackageController to use skip/take params (Facepunch API format) instead of page/count (which Facepunch ignores entirely)
- Increase FETCH_TIMEOUT from 2s to 8s to avoid aborting requests that need a fresh API call (cache miss → 3-5s roundtrip to Facepunch)
- Tag sub-filters now work: clicking Maps → Rats fetches 'rats type:map' which returns relevant results via free-text search
Hidden Gems filters:
- Wire browse-filters.php to hidden-gems.php: genres, style, stars, size, and reviewtag params are now read from URL and passed to API query
- Map filter values to Facepunch API facet format: disksize:1-9, pos:bitmask, stars:1-5, genre:name, style:2d/3d
- Fix TotalCount key (was 'Total', API returns 'TotalCount')
- Cache key now includes filter state (was only page number)
Browse filters partial:
- Fix size filter values: use API disksize numbers (1-9) not text keys
- Fix reviewtag values: use API pos: bitmask values (4, 512, 256, etc.) mapped to human labels (Gameplay, Replayability, Addictive, etc.)
- Convert homepage category nav to stats-only links
The category nav on the homepage never worked for filtering — the JS tried to update a #packagesGrid element that only exists in the browse layout (else branch), not in the homepage layout (if branch). Every call to fetchAndRender hit 'if (!grid) return' immediately.
Changes:
- Convert category nav buttons to links pointing to /t/{type}/ browse pages (where server-side rendering actually works)
- Remove initCategories() JS import from homepage (no longer needed)
- Remove broken sub-filters panel (tags like 'rats' never rendered)
- Add text-decoration:none for link-based nav items
- Categories with 0 count are now hidden instead of disabled
- Fix /t/game/ showing homepage instead of filtered results, fix hidden gems filters
/t/{type}/ route:
- Set $filterType = $tagType in routing_logic.php so package-list.php renders the browse layout instead of the homepage layout. Previously $filterType was unset, so $isHomepage evaluated to true and showed the full homepage instead of filtered packages.
Hidden gems filters:
- Always show the filter sidebar (was inside if-packages-exist check, meaning filters disappeared when a filter produced 0 results)
- Handle comma-separated genres (only use first value since API only supports one genre: filter at a time)
- Fix empty state message to suggest adjusting filters
- Improve /t/{type}/ browse page: show active type, fix nav links
- Header now shows 'Browse Games', 'Browse Maps', etc. instead of generic 'Browse Packages' when viewing a specific type
- Type filter nav correctly highlights the active type (uses $filterType from routing instead of $_GET['type'] which is empty on /t/ routes)
- Filter links now use /t/{type}/ URLs (proper route) instead of /?type= query params (which triggered homepage rendering)
- Fix /t/{type}/ dumping all packages without pagination
The tag listing route was calling fetchPackagesByTypeAndTag() which fetches up to 2000 packages (10 pages × 200) in bulk with no pagination.
Now uses the same paginated approach as the main browse: calls fetchPackagesByType() with proper page/perPage params (25 per page), calculates pagination data, and renders page controls.
When a tag sub-filter is active (?tags=rats), uses fetchPackagesBySearch() with the tag as free-text combined with the type filter.
- Clarify Hidden Gems is powered by Facepunch API, not hand-picked
Add subtitle text making it clear the page uses Facepunch's built-in 'Hidden Gems' algorithm (sort:hiddengem) rather than being manually curated by the site owner.
- Fix hidden gems filters not responding to clicks
The browse-filters.php partial included an inline script that wrapped all event listeners in DOMContentLoaded. But since the partial is included mid-page (inside main), DOMContentLoaded has already fired by the time the browser parses the script so the callback never executed and no click handlers were ever attached.
Fix: Use readyState check pattern - if DOM is already loaded, run initFilters() immediately; otherwise attach to DOMContentLoaded. This ensures filters work regardless of when the partial is included in the document.
- feat: add org social links, type-specific filters, video preview, profile stats, browse filter sidebar, referenced packages count
- Task 1: Org social links (Twitter, Website, Discord) on package detail sidebar
- Task 2: Type-specific browse filters for game/map/library types
- Task 3: Video preview section on package detail page
- Task 4: TotalReviews and PositiveReviews stats on player profile
- Task 5: Browse filter sidebar integration for /t/game/ and /t/map/ pages
- Task 6: Referenced by X packages indicator in usage stats
- Use proper Material Symbols for social links and fix Twitter URL
- Twitter: alternate_email icon (@ symbol) + auto-convert @handle to https://x.com/handle URL
- Website: language icon (globe - unchanged)
- Discord: headset_mic icon (recognizable voice chat icon)
Previously used 'flutter' (not a real Material Symbol) for Twitter and 'forum' for Discord which were generic/incorrect.
- Use Font Awesome brand icons for social links (Twitter/X, Discord)
Google Material Symbols don't have Twitter/X or Discord icons. Switch to Font Awesome 6 Brands which is already loaded via CDN on profile pages — now also loaded on package detail pages.
- Twitter/X: fa-brands fa-x-twitter (official X logo)
- Discord: fa-brands fa-discord (official Discord logo)
- Website: keeps Material Symbols 'language' (globe icon)
Font Awesome CDN link now conditionally loaded on package detail pages (was previously only on user/org profile pages).
---------
e2ce930
3
-
Performance
1:33 AM
Remove Pace.js, async fonts, optimize image loading
Details
- Remove Pace.js (~30KB) — progress bar library loaded on every page with minimal user benefit. Removed from head.php, theme-manager.js vendor list, and disabled in bundleManager config.
- Make Material Symbols font non-render-blocking — was loaded as a synchronous stylesheet. Now uses preload/onload pattern (same as Poppins). Eliminates render-blocking external CSS request from the critical path.
- Remove unused cdn.jsdelivr.net dns-prefetch/preconnect (nothing loads from it)
- Add fetchpriority=high to package-detail hero image (LCP element)
- Add loading=lazy to skins-item large icon image
91bcbac -
Performance
1:25 AM
Frontend performance and accessibility improvements
Details
Performance:
- Fix render-blocking chart.min.js and chartjs-plugin-zoom.min.js (add defer)
- Remove duplicate Google Fonts @import from styles.css (already loaded via <link preload> in head.php — eliminates blocking CSS network request)
- Fix theme-manager.js double-loading 9 vendor scripts already present in head.php (popper, tippy, sweetalert2, toastify, pace, clipboard, chart.js, chartjs-plugin-zoom, smooth-scroll). Now detects existing <script> tags and skips duplicates.
- Restructure body-scripts.php from 30+ unconditional scripts to contextual loading: Core (8), Browse (6), Package detail (6), Profile (9), Auth (2), Interactive (3). Simple pages now load ~14 scripts instead of 30+.
Accessibility:
- Add skip-to-content link (visible on Tab focus, hidden otherwise)
- Replace div.container with main#main-content (proper ARIA landmark)
- Skip link styled with primary color, positioned absolutely, transitions into view on :focus
4430658 -
Performance
12:53 AM
Admin panel overhaul — speed, security, and bug fixes
Details
Performance:
- Cache dashboard metrics with 30s TTL (eliminates ~8 DB queries per load)
- Progressive analytics loading: core metrics first, then secondary panels
- Combine multiple COUNT queries into single SQL statements throughout
- active-users.php: lazy-load tabs, replace meta-refresh with JS interval
- moderation-logs.php: tail-based file reading (last 100KB vs entire file)
- player-history.php: combine 7 queries into 1 + add 60s cache
- Defer admin.js, async-load non-critical CSS across all admin pages
- broadcast-notification.php: batch INSERT (chunks of 100) vs N+1 loop
SQL Optimizations:
- getUserStats: 4 queries → 1 using COUNT(DISTINCT CASE WHEN)
- getUserEngagement: replace NOT IN subquery with LEFT JOIN
- getRetentionMetrics: compute in SQL instead of fetching all rows to PHP
- getUserSessionStats: 4 queries → 1 combined query
Bug Fixes:
- Fix TypeError in org-claims-full loadData() (wrong CSS selector)
- Fix CalHeatMap not defined (removed invalid defer from CDN scripts)
- Fix reputation avgScore using only top 50 (now queries all users)
- Fix donations double saveDonation() creating duplicate test entries
Security:
- Remove display_errors=1 from diagnose.php in production
5d2ab45
Generated from the project's commit history whenever the site is deployed. Times are server time.