Skip to content

Commit dfb405c

Browse files
Andrew Bakerclaude
andcommitted
Add 404 Games page — moved from Crash Recovery plugin
Adds a new 404 Games tab to DevTools with 5 playable mini-games (Runner, Jetpack, Racer, Miner, Asteroids), global leaderboard via REST API, 12 colour schemes, and admin preview without requiring the feature to be enabled. Home button is electric blue, inline with the heading. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b786fe6 commit dfb405c

9 files changed

Lines changed: 2863 additions & 37 deletions

File tree

assets/cs-404-admin.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* CloudScale DevTools — 404 Games admin panel JS.
3+
*
4+
* Handles the enable toggle and colour scheme picker.
5+
* Depends on csDevtools404 (ajaxUrl, nonce, custom_404, scheme, previewUrl) localised by PHP.
6+
*/
7+
( function () {
8+
'use strict';
9+
10+
var ajaxUrl = csDevtools404.ajaxUrl;
11+
var nonce = csDevtools404.nonce;
12+
var previewUrl = csDevtools404.previewUrl;
13+
14+
// ── Enable toggle ────────────────────────────────────────────────────────
15+
16+
var chkEnabled = document.getElementById( 'cs-404-enabled' );
17+
var toggleMsg = document.getElementById( 'cs-404-toggle-msg' );
18+
19+
if ( chkEnabled ) {
20+
chkEnabled.checked = parseInt( csDevtools404.custom_404, 10 ) === 1;
21+
22+
chkEnabled.addEventListener( 'change', function () {
23+
var val = chkEnabled.checked ? 1 : 0;
24+
var fd = new FormData();
25+
fd.append( 'action', 'cs_devtools_save_404_settings' );
26+
fd.append( 'nonce', nonce );
27+
fd.append( 'custom_404', val );
28+
29+
fetch( ajaxUrl, { method: 'POST', body: fd } )
30+
.then( function ( r ) { return r.json(); } )
31+
.then( function ( resp ) {
32+
if ( toggleMsg ) {
33+
if ( resp.success ) {
34+
toggleMsg.innerHTML = '<span style="color:#2e7d32;">&#10003; Setting saved.</span>';
35+
} else {
36+
toggleMsg.innerHTML = '<span style="color:#c62828;">&#10007; Failed to save.</span>';
37+
}
38+
toggleMsg.style.display = '';
39+
setTimeout( function () { toggleMsg.style.display = 'none'; toggleMsg.innerHTML = ''; }, 2500 );
40+
}
41+
} )
42+
.catch( function () {
43+
if ( toggleMsg ) {
44+
toggleMsg.innerHTML = '<span style="color:#c62828;">&#10007; Request failed.</span>';
45+
toggleMsg.style.display = '';
46+
}
47+
} );
48+
} );
49+
}
50+
51+
// ── Colour scheme picker ─────────────────────────────────────────────────
52+
53+
var schemeGrid = document.getElementById( 'cs-404-scheme-grid' );
54+
var saveBtn = document.getElementById( 'cs-404-save-scheme' );
55+
var schemeMsg = document.getElementById( 'cs-404-scheme-msg' );
56+
var previewLink = document.getElementById( 'cs-404-preview-link' );
57+
58+
function getActiveScheme() {
59+
var active = schemeGrid ? schemeGrid.querySelector( '.cs-404-scheme-swatch.active' ) : null;
60+
return active ? active.dataset.scheme : 'ocean';
61+
}
62+
63+
function updatePreviewLink( scheme ) {
64+
if ( previewLink ) {
65+
previewLink.href = previewUrl + '?cs_devtools_preview_scheme=' + encodeURIComponent( scheme );
66+
}
67+
}
68+
69+
// Set initial preview link.
70+
updatePreviewLink( getActiveScheme() );
71+
72+
if ( schemeGrid ) {
73+
schemeGrid.addEventListener( 'click', function ( e ) {
74+
var btn = e.target.closest( '.cs-404-scheme-swatch' );
75+
if ( ! btn ) { return; }
76+
schemeGrid.querySelectorAll( '.cs-404-scheme-swatch' ).forEach( function ( el ) {
77+
el.style.borderColor = '#ddd';
78+
el.classList.remove( 'active' );
79+
} );
80+
btn.style.borderColor = '#f57c00';
81+
btn.classList.add( 'active' );
82+
updatePreviewLink( btn.dataset.scheme );
83+
} );
84+
}
85+
86+
if ( saveBtn ) {
87+
saveBtn.addEventListener( 'click', function () {
88+
var scheme = getActiveScheme();
89+
saveBtn.disabled = true;
90+
saveBtn.textContent = 'Saving…';
91+
92+
var fd = new FormData();
93+
fd.append( 'action', 'cs_devtools_save_404_settings' );
94+
fd.append( 'nonce', nonce );
95+
fd.append( 'scheme', scheme );
96+
fd.append( 'custom_404', chkEnabled && chkEnabled.checked ? 1 : 0 );
97+
98+
fetch( ajaxUrl, { method: 'POST', body: fd } )
99+
.then( function ( r ) { return r.json(); } )
100+
.then( function ( resp ) {
101+
saveBtn.disabled = false;
102+
saveBtn.textContent = '💾 Save Scheme';
103+
if ( schemeMsg ) {
104+
if ( resp.success ) {
105+
schemeMsg.innerHTML = '<span style="color:#2e7d32;">&#10003; Scheme saved.</span>';
106+
} else {
107+
schemeMsg.innerHTML = '<span style="color:#c62828;">&#10007; Failed to save.</span>';
108+
}
109+
schemeMsg.style.display = '';
110+
setTimeout( function () { schemeMsg.style.display = 'none'; schemeMsg.innerHTML = ''; }, 2500 );
111+
}
112+
} )
113+
.catch( function () {
114+
saveBtn.disabled = false;
115+
saveBtn.textContent = '💾 Save Scheme';
116+
if ( schemeMsg ) {
117+
schemeMsg.innerHTML = '<span style="color:#c62828;">&#10007; Request failed.</span>';
118+
schemeMsg.style.display = '';
119+
}
120+
} );
121+
} );
122+
}
123+
124+
} )();

assets/cs-custom-404.css

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/* CloudScale Crash Recovery — Custom 404 page styles v1.5.0 */
2+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3+
html, body { height: 100%; }
4+
body {
5+
background: linear-gradient(160deg, #cce9fb 0%, #a8d8f0 100%);
6+
color: #1a3a5c;
7+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
8+
display: flex;
9+
flex-direction: column;
10+
align-items: center;
11+
justify-content: flex-start;
12+
min-height: 100vh;
13+
padding: 28px 24px 36px;
14+
overflow-x: hidden;
15+
position: relative;
16+
}
17+
body::before {
18+
content: '';
19+
position: fixed;
20+
top: 50%; left: 50%;
21+
transform: translate(-50%, -50%);
22+
width: 640px; height: 640px;
23+
background: radial-gradient(circle, rgba(255,255,255,0.4) 0%, transparent 70%);
24+
pointer-events: none;
25+
}
26+
.cs404-wrap {
27+
position: relative;
28+
text-align: center;
29+
max-width: 760px;
30+
width: 100%;
31+
animation: cs404-in 0.55s cubic-bezier(0.22,1,0.36,1);
32+
}
33+
@keyframes cs404-in {
34+
from { opacity: 0; transform: translateY(20px); }
35+
to { opacity: 1; transform: translateY(0); }
36+
}
37+
.cs404-heading-row {
38+
display: flex;
39+
align-items: center;
40+
justify-content: center;
41+
gap: 16px;
42+
flex-wrap: wrap;
43+
width: 100%;
44+
max-width: 760px;
45+
margin: 0 0 10px;
46+
}
47+
.cs404-heading {
48+
font-size: clamp(28px, 6vw, 52px);
49+
font-weight: 900;
50+
line-height: 1.1;
51+
letter-spacing: -1px;
52+
background: linear-gradient(135deg, #f57c00 0%, #ffa726 55%, #ff9800 100%);
53+
-webkit-background-clip: text;
54+
-webkit-text-fill-color: transparent;
55+
background-clip: text;
56+
margin: 0;
57+
text-align: center;
58+
}
59+
.cs404-desc {
60+
font-size: 19px;
61+
font-weight: 700;
62+
color: #0d2a4a;
63+
line-height: 1.5;
64+
margin-bottom: 20px;
65+
}
66+
.cs404-btn {
67+
display: inline-flex;
68+
align-items: center;
69+
gap: 8px;
70+
padding: 13px 32px;
71+
background: linear-gradient(135deg, #f57c00, #e65100);
72+
color: #fff;
73+
text-decoration: none;
74+
border-radius: 50px;
75+
font-size: 15px;
76+
font-weight: 600;
77+
letter-spacing: 0.02em;
78+
box-shadow: 0 4px 24px rgba(245,124,0,0.32);
79+
transition: transform 0.2s ease, box-shadow 0.2s ease;
80+
}
81+
.cs404-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 28px rgba(245,124,0,0.44); }
82+
.cs404-btn:focus { outline: 2px solid #f57c00; outline-offset: 3px; }
83+
.cs404-btn svg { width: 16px; height: 16px; fill: currentColor; flex-shrink: 0; }
84+
/* Brand block */
85+
.cs404-brand {
86+
margin-top: 20px;
87+
display: flex;
88+
flex-direction: column;
89+
align-items: center;
90+
gap: 5px;
91+
}
92+
.cs404-logo a { display: inline-block; }
93+
.cs404-logo img { max-height: 48px; width: auto; display: block; }
94+
.cs404-site-name {
95+
font-size: 15px;
96+
font-weight: 700;
97+
color: #0d2a4a;
98+
letter-spacing: 0.04em;
99+
}
100+
.cs404-tagline {
101+
font-size: 13px;
102+
color: #3a6080;
103+
letter-spacing: 0.01em;
104+
}
105+
/* Game — sits above the main card */
106+
.cs404-game-wrap {
107+
margin-bottom: 24px;
108+
text-align: center;
109+
animation: cs404-in 0.7s cubic-bezier(0.22,1,0.36,1) both;
110+
animation-delay: 0.15s;
111+
}
112+
.cs404-game-label {
113+
font-size: 11px;
114+
font-weight: 600;
115+
letter-spacing: 0.12em;
116+
text-transform: uppercase;
117+
color: #3a6080;
118+
margin-bottom: 10px;
119+
}
120+
#cs404-game {
121+
display: block;
122+
margin: 0 auto;
123+
border-radius: 10px;
124+
background: rgba(255,255,255,0.45);
125+
border: 2px solid rgba(42,96,144,0.25);
126+
cursor: pointer;
127+
max-width: 100%;
128+
}
129+
.cs404-dots { position: fixed; inset: 0; pointer-events: none; overflow: hidden; }
130+
.cs404-dot { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.6); }
131+
.cs404-home-btn {
132+
display: inline-flex;
133+
align-items: center;
134+
gap: 4px;
135+
padding: 6px 18px;
136+
background: linear-gradient(135deg,#0080ff,#0050cc);
137+
border: 2px solid transparent;
138+
border-radius: 20px;
139+
color: #fff;
140+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
141+
font-size: 13px;
142+
font-weight: 600;
143+
text-decoration: none;
144+
cursor: pointer;
145+
box-shadow: 0 2px 12px rgba(0,128,255,0.35);
146+
transition: box-shadow 0.2s ease, transform 0.2s ease;
147+
}
148+
.cs404-home-btn:hover {
149+
box-shadow: 0 4px 16px rgba(0,128,255,0.55);
150+
transform: translateY(-1px);
151+
}
152+
/* Game tabs */
153+
.cs404-tabs {
154+
display: flex;
155+
gap: 8px;
156+
justify-content: center;
157+
margin-bottom: 10px;
158+
flex-wrap: wrap;
159+
}
160+
.cs404-tab {
161+
padding: 6px 18px;
162+
border: 2px solid rgba(42,96,144,0.3);
163+
border-radius: 20px;
164+
background: rgba(255,255,255,0.4);
165+
color: #0d2a4a;
166+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
167+
font-size: 13px;
168+
font-weight: 600;
169+
cursor: pointer;
170+
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
171+
}
172+
.cs404-tab:hover { background: rgba(255,255,255,0.7); border-color: rgba(42,96,144,0.5); }
173+
.cs404-tab.active {
174+
background: linear-gradient(135deg,#f57c00,#e65100);
175+
color: #fff;
176+
border-color: transparent;
177+
box-shadow: 0 2px 12px rgba(245,124,0,0.3);
178+
}
179+
/* Miner on-screen controls (touch) */
180+
.cs404-miner-ctrl {
181+
display: none;
182+
justify-content: center;
183+
gap: 16px;
184+
margin-top: 10px;
185+
}
186+
.cs404-miner-btn {
187+
padding: 10px 28px;
188+
background: rgba(255,255,255,0.4);
189+
border: 2px solid rgba(42,96,144,0.3);
190+
border-radius: 8px;
191+
font-size: 16px;
192+
font-weight: 700;
193+
color: #0d2a4a;
194+
cursor: pointer;
195+
user-select: none;
196+
-webkit-user-select: none;
197+
touch-action: none;
198+
}
199+
.cs404-miner-btn:active { background: rgba(255,255,255,0.7); }
200+
/* Leaderboard panel */
201+
#cs404-lb-panel {
202+
margin-top: 14px;
203+
width: 100%;
204+
max-width: 620px;
205+
background: rgba(13,42,74,0.07);
206+
border: 1.5px solid rgba(42,96,144,0.2);
207+
border-radius: 10px;
208+
overflow: hidden;
209+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
210+
}
211+
.cs404-lb-header {
212+
background: rgba(13,42,74,0.1);
213+
padding: 7px 14px;
214+
font-size: 13px;
215+
font-weight: 700;
216+
color: #0d2a4a;
217+
border-bottom: 1px solid rgba(42,96,144,0.15);
218+
}
219+
.cs404-lb-row {
220+
display: flex;
221+
align-items: center;
222+
gap: 10px;
223+
padding: 5px 14px;
224+
font-size: 12px;
225+
border-bottom: 1px solid rgba(42,96,144,0.07);
226+
}
227+
.cs404-lb-row:last-child { border-bottom: none; }
228+
.cs404-lb-row-gold { background: rgba(251,191,36,0.09); }
229+
.cs404-lb-rank { min-width: 26px; font-size: 13px; }
230+
.cs404-lb-name {
231+
flex: 1;
232+
font-weight: 600;
233+
color: #1a3a5c;
234+
overflow: hidden;
235+
text-overflow: ellipsis;
236+
white-space: nowrap;
237+
}
238+
.cs404-lb-score {
239+
font-weight: 700;
240+
color: #f57c00;
241+
font-variant-numeric: tabular-nums;
242+
font-family: monospace;
243+
letter-spacing: 0.04em;
244+
}
245+
.cs404-lb-empty {
246+
padding: 11px 14px;
247+
color: #3a6080;
248+
font-size: 12px;
249+
font-style: italic;
250+
}

0 commit comments

Comments
 (0)