Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>{{ title | default('404') }}</title> | |
| <style> | |
| :root { | |
| --bg-color: {{ bgColor | default('#ffffff') }}; | |
| --text-color: {{ textColor | default('#1f2937') }}; | |
| --accent-color: {{ accentColor | default('#ef4444') }}; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 100vh; | |
| text-align: center; | |
| overflow-x: hidden; | |
| } | |
| .container { | |
| max-width: 600px; | |
| padding: 20px; | |
| } | |
| h1 { | |
| font-size: 5rem; | |
| margin: 0; | |
| line-height: 1; | |
| font-weight: 800; | |
| color: var(--accent-color); | |
| } | |
| p { | |
| font-size: 1.5rem; | |
| margin: 20px 0; | |
| opacity: 0.8; | |
| } | |
| .btn { | |
| display: inline-block; | |
| padding: 12px 24px; | |
| background-color: var(--accent-color); | |
| color: var(--bg-color); | |
| text-decoration: none; | |
| border-radius: 6px; | |
| font-weight: 600; | |
| transition: opacity 0.2s; | |
| margin-top: 20px; | |
| } | |
| .btn:hover { | |
| opacity: 0.9; | |
| } | |
| /* Retro Theme Overrides */ | |
| {% if theme == 'retro' %} | |
| body { | |
| font-family: 'Courier New', Courier, monospace; | |
| } | |
| h1 { | |
| text-shadow: 2px 2px 0px var(--text-color); | |
| } | |
| .btn { | |
| border-radius: 0; | |
| border: 2px solid var(--text-color); | |
| background: transparent; | |
| color: var(--text-color); | |
| } | |
| .btn:hover { | |
| background: var(--text-color); | |
| color: var(--bg-color); | |
| } | |
| {% endif %} | |
| /* Minimal Theme Overrides */ | |
| {% if theme == 'minimal' %} | |
| h1 { | |
| font-size: 3rem; | |
| font-weight: 300; | |
| } | |
| p { | |
| font-size: 1rem; | |
| } | |
| .btn { | |
| border-radius: 50px; | |
| } | |
| {% endif %} | |
| /* Illustrations */ | |
| .illustration { | |
| margin-bottom: 30px; | |
| height: 150px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .ghost { | |
| font-size: 100px; | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| @keyframes float { | |
| 0% { transform: translateY(0px); } | |
| 50% { transform: translateY(-10px); } | |
| 100% { transform: translateY(0px); } | |
| } | |
| /* Game Container */ | |
| #game-container { | |
| margin-top: 40px; | |
| display: none; | |
| border: 2px solid var(--text-color); | |
| padding: 10px; | |
| background: rgba(0,0,0,0.05); | |
| } | |
| canvas { | |
| background: var(--bg-color); | |
| display: block; | |
| } | |
| .game-toggle { | |
| margin-top: 20px; | |
| font-size: 0.9rem; | |
| cursor: pointer; | |
| text-decoration: underline; | |
| color: var(--accent-color); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="illustration"> | |
| {% if illustration == 'ghost' %} | |
| <div class="ghost">👻</div> | |
| {% elif illustration == 'robot' %} | |
| <div class="ghost">🤖</div> | |
| {% elif illustration == 'broken' %} | |
| <div class="ghost">💔</div> | |
| {% elif illustration == 'planet' %} | |
| <div class="ghost">🪐</div> | |
| {% else %} | |
| <!-- Default fallback if illustration matches nothing --> | |
| <div class="ghost">👻</div> | |
| {% endif %} | |
| </div> | |
| <h1>{{ title | default('404') }}</h1> | |
| <p>{{ message | default('Page not found') }}</p> | |
| <a href="{{ buttonLink | default('/') }}" class="btn">{{ buttonText | default('Go Home') }}</a> | |
| {% if showGame %} | |
| <div class="game-toggle" onclick="toggleGame()">Play Snake while you wait?</div> | |
| <div id="game-container"> | |
| <canvas id="gameCanvas" width="300" height="300"></canvas> | |
| <div style="font-size: 0.8rem; margin-top: 5px;">Use Arrow Keys to Move</div> | |
| </div> | |
| <script> | |
| let gameRunning = false; | |
| let canvas, ctx; | |
| let snake = [{x: 10, y: 10}]; | |
| let food = {x: 15, y: 15}; | |
| let dx = 0; | |
| let dy = 0; | |
| let score = 0; | |
| let gridSize = 15; | |
| let tileCount = 20; | |
| let gameInterval; | |
| function toggleGame() { | |
| const container = document.getElementById('game-container'); | |
| if (container.style.display === 'block') { | |
| container.style.display = 'none'; | |
| gameRunning = false; | |
| clearInterval(gameInterval); | |
| } else { | |
| container.style.display = 'block'; | |
| if (!canvas) initGame(); | |
| resetGame(); | |
| } | |
| } | |
| function initGame() { | |
| canvas = document.getElementById('gameCanvas'); | |
| ctx = canvas.getContext('2d'); | |
| document.addEventListener('keydown', keyDownEvent); | |
| } | |
| function resetGame() { | |
| snake = [{x: 10, y: 10}]; | |
| food = {x: 15, y: 15}; | |
| dx = 0; | |
| dy = 0; | |
| score = 0; | |
| gameRunning = true; | |
| if (gameInterval) clearInterval(gameInterval); | |
| gameInterval = setInterval(drawGame, 100); | |
| } | |
| function drawGame() { | |
| if (!gameRunning) return; | |
| // Move snake | |
| const head = {x: snake[0].x + dx, y: snake[0].y + dy}; | |
| // Wrap around | |
| if (head.x < 0) head.x = tileCount - 1; | |
| if (head.x >= tileCount) head.x = 0; | |
| if (head.y < 0) head.y = tileCount - 1; | |
| if (head.y >= tileCount) head.y = 0; | |
| // Check collision with self | |
| for (let i = 0; i < snake.length; i++) { | |
| if (head.x === snake[i].x && head.y === snake[i].y) { | |
| // Game Over logic (soft reset) | |
| snake = [{x: 10, y: 10}]; | |
| dx = 0; | |
| dy = 0; | |
| } | |
| } | |
| snake.unshift(head); | |
| // Check food | |
| if (head.x === food.x && head.y === food.y) { | |
| score++; | |
| food = { | |
| x: Math.floor(Math.random() * tileCount), | |
| y: Math.floor(Math.random() * tileCount) | |
| }; | |
| } else { | |
| snake.pop(); | |
| } | |
| // Draw | |
| ctx.fillStyle = '{{ bgColor | default("#ffffff") }}'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Snake | |
| ctx.fillStyle = '{{ accentColor | default("#ef4444") }}'; | |
| for (let i = 0; i < snake.length; i++) { | |
| ctx.fillRect(snake[i].x * gridSize, snake[i].y * gridSize, gridSize - 2, gridSize - 2); | |
| } | |
| // Food | |
| ctx.fillStyle = '{{ textColor | default("#1f2937") }}'; | |
| ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize - 2, gridSize - 2); | |
| } | |
| function keyDownEvent(e) { | |
| switch(e.keyCode) { | |
| case 37: if(dx !== 1) { dx = -1; dy = 0; } break; | |
| case 38: if(dy !== 1) { dx = 0; dy = -1; } break; | |
| case 39: if(dx !== -1) { dx = 1; dy = 0; } break; | |
| case 40: if(dy !== -1) { dx = 0; dy = 1; } break; | |
| } | |
| } | |
| </script> | |
| {% endif %} | |
| </div> | |
| </body> | |
| </html> | |