406 lines
14 KiB
HTML
406 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>503 Service Unavailable</title>
|
|
<style>
|
|
/* --- Clean, Modern Dark Theme (GitHub/VSCode style) --- */
|
|
:root {
|
|
--bg-color: #0d1117;
|
|
--card-bg: #161b22;
|
|
--border: #30363d;
|
|
--text-main: #c9d1d9;
|
|
--text-sub: #8b949e;
|
|
--accent: #58a6ff;
|
|
--error: #f85149;
|
|
--success: #3fb950;
|
|
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
--font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-main);
|
|
font-family: var(--font-ui);
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* --- 1. Boot Screen Overlay --- */
|
|
#boot-screen {
|
|
position: fixed;
|
|
top: 0; left: 0; width: 100%; height: 100%;
|
|
background-color: #000;
|
|
color: #ccc;
|
|
font-family: var(--font-mono);
|
|
font-size: 14px;
|
|
padding: 40px;
|
|
box-sizing: border-box;
|
|
z-index: 1000;
|
|
display: flex; /* Flex to keep log at bottom? No, top down is fine */
|
|
flex-direction: column;
|
|
}
|
|
|
|
.log-line {
|
|
margin-bottom: 4px;
|
|
white-space: pre-wrap;
|
|
}
|
|
.log-green { color: var(--success); }
|
|
.log-red { color: var(--error); }
|
|
|
|
.cursor-blink {
|
|
display: inline-block;
|
|
width: 8px;
|
|
height: 15px;
|
|
background: #ccc;
|
|
animation: blink 1s step-end infinite;
|
|
vertical-align: middle;
|
|
}
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
|
|
/* --- 2. Main UI (Hidden initially) --- */
|
|
#main-ui {
|
|
display: none; /* Hidden until boot finishes */
|
|
width: 100%;
|
|
max-width: 600px;
|
|
opacity: 0;
|
|
transition: opacity 1s ease;
|
|
}
|
|
|
|
.card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
padding: 32px;
|
|
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
|
|
text-align: center;
|
|
}
|
|
|
|
h1 { margin: 0 0 10px 0; font-size: 24px; color: var(--text-main); }
|
|
p { color: var(--text-sub); line-height: 1.5; margin-bottom: 24px; }
|
|
|
|
.error-code {
|
|
display: inline-block;
|
|
background: rgba(248, 81, 73, 0.15);
|
|
color: var(--error);
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-family: var(--font-mono);
|
|
font-size: 12px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid rgba(248, 81, 73, 0.4);
|
|
}
|
|
|
|
/* --- Game/Terminal Container --- */
|
|
.terminal-box {
|
|
background: #000; /* Deep black for contrast */
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
margin-top: 20px;
|
|
position: relative;
|
|
text-align: left;
|
|
}
|
|
|
|
.terminal-header {
|
|
background: #21262d;
|
|
padding: 6px 12px;
|
|
font-size: 11px;
|
|
color: var(--text-sub);
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-family: var(--font-mono);
|
|
}
|
|
|
|
canvas {
|
|
display: block;
|
|
width: 100%; /* Responsive */
|
|
background: #0d1117;
|
|
}
|
|
|
|
/* --- Vim Status Bar --- */
|
|
.vim-status {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
padding: 4px 10px;
|
|
display: none; /* Hidden by default for normal users */
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.hint-text {
|
|
font-size: 12px;
|
|
color: #444;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="boot-screen">
|
|
<div id="log-container"></div>
|
|
<div id="boot-prompt" style="margin-top: 20px; display:none;">
|
|
[root@server ~]# <span style="color:white">System Halted. Press <strong style="color: #fff; border-bottom: 1px solid #fff;">ENTER</strong> to view logs.</span><span class="cursor-blink"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="main-ui">
|
|
<div class="card">
|
|
<h1>Service Temporarily Unavailable</h1>
|
|
<div class="error-code">HTTP 503 - UPSTREAM_TIMEOUT</div>
|
|
<p>
|
|
The server is currently unable to handle the request due to a temporary overload or scheduled maintenance.
|
|
</p>
|
|
|
|
<div class="terminal-box">
|
|
<div class="terminal-header">
|
|
<span>root@server: ~/diagnostics</span>
|
|
<span>/bin/zsh</span>
|
|
</div>
|
|
|
|
<canvas id="gameCanvas" width="540" height="300"></canvas>
|
|
|
|
<div class="vim-status" id="vim-bar">
|
|
<span style="font-weight:bold;">-- INSERT --</span>
|
|
<span id="coord">10, 10</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hint-text">Waiting for connection...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
/* ------------------------------------------------
|
|
1. BOOT SEQUENCE LOGIC
|
|
------------------------------------------------ */
|
|
const logContainer = document.getElementById('log-container');
|
|
const bootPrompt = document.getElementById('boot-prompt');
|
|
const bootScreen = document.getElementById('boot-screen');
|
|
const mainUI = document.getElementById('main-ui');
|
|
|
|
const logs = [
|
|
"Initializing kernel...",
|
|
"[ <span class='log-green'>OK</span> ] Started System Logging Service.",
|
|
"[ <span class='log-green'>OK</span> ] Mounted /boot/efi.",
|
|
"[ <span class='log-green'>OK</span> ] Reached target System Initialization.",
|
|
"[ <span class='log-green'>OK</span> ] Listening on Port 80.",
|
|
"[ <span class='log-green'>OK</span> ] Listening on Port 443.",
|
|
"Starting nginx service...",
|
|
"[ <span class='log-red'>FAILED</span> ] Failed to start Application Server.",
|
|
"[ <span class='log-red'>ERROR</span> ] Connection refused on 127.0.0.1:8080",
|
|
"[ <span class='log-red'>CRITICAL</span> ] HTTP 503 Service Unavailable"
|
|
];
|
|
|
|
let lineIndex = 0;
|
|
|
|
function addLog() {
|
|
if (lineIndex < logs.length) {
|
|
const div = document.createElement('div');
|
|
div.className = 'log-line';
|
|
div.innerHTML = logs[lineIndex];
|
|
logContainer.appendChild(div);
|
|
lineIndex++;
|
|
// Random typing speed
|
|
setTimeout(addLog, Math.random() * 200 + 50);
|
|
} else {
|
|
// Done logging, show prompt
|
|
bootPrompt.style.display = 'block';
|
|
document.addEventListener('keydown', checkEnter);
|
|
}
|
|
}
|
|
|
|
function checkEnter(e) {
|
|
if (e.key === 'Enter') {
|
|
document.removeEventListener('keydown', checkEnter);
|
|
bootScreen.style.display = 'none';
|
|
mainUI.style.display = 'block';
|
|
|
|
// Trigger fade in
|
|
setTimeout(() => {
|
|
mainUI.style.opacity = '1';
|
|
startGame(); // Start game ONLY after visible
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
// Start Boot
|
|
setTimeout(addLog, 500);
|
|
|
|
|
|
/* ------------------------------------------------
|
|
2. SNAKE GAME LOGIC
|
|
------------------------------------------------ */
|
|
const canvas = document.getElementById('gameCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const vimBar = document.getElementById('vim-bar');
|
|
const coordSpan = document.getElementById('coord');
|
|
|
|
// Config
|
|
const gridSize = 20;
|
|
let tileCountX = Math.floor(canvas.width / gridSize);
|
|
let tileCountY = Math.floor(canvas.height / gridSize);
|
|
|
|
let snake = [{x: 10, y: 10}];
|
|
let food = {x: 15, y: 15};
|
|
let dx = 0;
|
|
let dy = 0;
|
|
let score = 0;
|
|
let gameRunning = false;
|
|
let gameSpeed = 100; // ms
|
|
let lastTime = 0;
|
|
|
|
// Vim Detection
|
|
let isVimUser = false;
|
|
|
|
function startGame() {
|
|
// Re-calculate grid in case of weird sizing
|
|
tileCountX = Math.floor(canvas.width / gridSize);
|
|
tileCountY = Math.floor(canvas.height / gridSize);
|
|
|
|
spawnFood();
|
|
requestAnimationFrame(gameLoop);
|
|
}
|
|
|
|
function gameLoop(currentTime) {
|
|
window.requestAnimationFrame(gameLoop);
|
|
|
|
const secondsSinceLastRender = (currentTime - lastTime) / 1000;
|
|
if (secondsSinceLastRender < (gameSpeed / 1000)) return;
|
|
|
|
lastTime = currentTime;
|
|
|
|
update();
|
|
draw();
|
|
}
|
|
|
|
function update() {
|
|
if (!gameRunning) return;
|
|
|
|
const head = { x: snake[0].x + dx, y: snake[0].y + dy };
|
|
|
|
// Wrap logic (Toroidal world)
|
|
if (head.x < 0) head.x = tileCountX - 1;
|
|
if (head.x >= tileCountX) head.x = 0;
|
|
if (head.y < 0) head.y = tileCountY - 1;
|
|
if (head.y >= tileCountY) head.y = 0;
|
|
|
|
// Self Collision
|
|
for (let i = 0; i < snake.length; i++) {
|
|
if (head.x === snake[i].x && head.y === snake[i].y) {
|
|
resetGame();
|
|
return;
|
|
}
|
|
}
|
|
|
|
snake.unshift(head);
|
|
|
|
// Eat Food
|
|
if (head.x === food.x && head.y === food.y) {
|
|
score++;
|
|
spawnFood();
|
|
// Speed up slightly
|
|
if(gameSpeed > 50) gameSpeed -= 2;
|
|
} else {
|
|
snake.pop();
|
|
}
|
|
|
|
// Update Vim Coords
|
|
if (isVimUser) {
|
|
coordSpan.innerText = `${head.y + 1}, ${head.x + 1}`;
|
|
}
|
|
}
|
|
|
|
function draw() {
|
|
// Clear
|
|
ctx.fillStyle = '#0d1117';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// Draw Snake
|
|
ctx.fillStyle = '#3fb950'; // GitHub Green
|
|
snake.forEach((part, index) => {
|
|
if (index === 0) ctx.fillStyle = '#7ee787'; // Lighter head
|
|
else ctx.fillStyle = '#3fb950';
|
|
|
|
ctx.fillRect(part.x * gridSize + 1, part.y * gridSize + 1, gridSize - 2, gridSize - 2);
|
|
});
|
|
|
|
// Draw Food
|
|
ctx.fillStyle = '#f85149'; // GitHub Red
|
|
ctx.fillRect(food.x * gridSize + 1, food.y * gridSize + 1, gridSize - 2, gridSize - 2);
|
|
|
|
// Draw "Paused" Text if not running
|
|
if (!gameRunning) {
|
|
ctx.fillStyle = '#8b949e';
|
|
ctx.font = '14px monospace';
|
|
ctx.fillText("Press Arrow Keys to Start", 20, 30);
|
|
}
|
|
}
|
|
|
|
function spawnFood() {
|
|
food.x = Math.floor(Math.random() * tileCountX);
|
|
food.y = Math.floor(Math.random() * tileCountY);
|
|
// Ensure food doesn't spawn on snake
|
|
snake.forEach(part => {
|
|
if(part.x === food.x && part.y === food.y) spawnFood();
|
|
});
|
|
}
|
|
|
|
function resetGame() {
|
|
snake = [{x: 10, y: 10}];
|
|
dx = 0; dy = 0;
|
|
gameRunning = false;
|
|
gameSpeed = 100;
|
|
}
|
|
|
|
/* ------------------------------------------------
|
|
3. CONTROLS
|
|
------------------------------------------------ */
|
|
document.addEventListener('keydown', (e) => {
|
|
// Only capture keys if Main UI is visible
|
|
if (mainUI.style.display === 'none') return;
|
|
|
|
const keys = ["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","h","j","k","l","w","a","s","d"];
|
|
if (keys.includes(e.key)) {
|
|
e.preventDefault(); // Stop scrolling
|
|
if (!gameRunning) gameRunning = true;
|
|
}
|
|
|
|
// Check for Vim usage
|
|
if (["h","j","k","l"].includes(e.key)) {
|
|
isVimUser = true;
|
|
vimBar.style.display = 'flex'; // REVEAL THE NERD UI
|
|
}
|
|
|
|
const goingUp = dy === -1;
|
|
const goingDown = dy === 1;
|
|
const goingRight = dx === 1;
|
|
const goingLeft = dx === -1;
|
|
|
|
switch(e.key) {
|
|
// Standard
|
|
case 'ArrowLeft': case 'a': if (!goingRight) { dx = -1; dy = 0; } break;
|
|
case 'ArrowUp': case 'w': if (!goingDown) { dx = 0; dy = -1; } break;
|
|
case 'ArrowRight': case 'd': if (!goingLeft) { dx = 1; dy = 0; } break;
|
|
case 'ArrowDown': case 's': if (!goingUp) { dx = 0; dy = 1; } break;
|
|
|
|
// Vim
|
|
case 'h': if (!goingRight) { dx = -1; dy = 0; } break;
|
|
case 'j': if (!goingUp) { dx = 0; dy = 1; } break; // Down
|
|
case 'k': if (!goingDown) { dx = 0; dy = -1; } break; // Up
|
|
case 'l': if (!goingLeft) { dx = 1; dy = 0; } break;
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|