managarten/games/mana-games/apps/web/src/layouts/Layout.astro
Till-JS 23f8950318 feat(games): add mana-games - AI-powered browser games platform
New project with 22+ browser games and AI game generation capabilities:
- Astro PWA web app with game catalog and player
- NestJS backend with AI game generator (Gemini, Claude, GPT-4o)
- Community game submission via GitHub API
- postMessage integration for score tracking
- PWA support with offline capabilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 20:13:14 +01:00

713 lines
No EOL
20 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import Button from '../components/Button.astro';
import InstallPrompt from '../components/InstallPrompt.astro';
import Footer from '../components/Footer.astro';
export interface Props {
title: string;
description?: string;
isGamePage?: boolean;
gameTitle?: string;
gameSlug?: string;
isPlayground?: boolean;
fullWidth?: boolean;
hideFooter?: boolean;
}
const { title, description = "Mana Games - Eine Sammlung von Web-basierten Spielen", isGamePage = false, gameTitle, gameSlug, isPlayground = false, fullWidth = false, hideFooter = false } = Astro.props;
---
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title} | Mana Games</title>
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Theme Color -->
<meta name="theme-color" content="#1a1a1a" />
<!-- iOS Meta Tags -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Mana Games" />
<!-- iOS Icons -->
<link rel="apple-touch-icon" href="/icons/icon-180x180.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/icons/icon-120x120.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="167x167" href="/icons/icon-167x167.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-180x180.png" />
<!-- iOS Splash Screens -->
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-640x1136.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-750x1334.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-828x1792.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)" href="/splash/splash-1125x2436.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)" href="/splash/splash-1242x2688.png" />
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-1536x2048.png" />
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-1668x2224.png" />
<link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)" href="/splash/splash-2048x2732.png" />
<!-- Microsoft Tiles -->
<meta name="msapplication-TileColor" content="#1a1a1a" />
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
<!-- Open Graph -->
<meta property="og:title" content={title + " | Mana Games"} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:image" content="/icons/icon-512x512.png" />
</head>
<body class={fullWidth ? 'full-width' : ''}>
<nav>
<div class="nav-container">
{isGamePage ? (
<div class="breadcrumb">
<a href="/" class="breadcrumb-logo">
<span class="logo-text">MANA</span>
<span class="logo-accent">GAMES</span>
</a>
<span class="breadcrumb-separator"></span>
<span class="breadcrumb-game">{gameTitle}</span>
</div>
) : (
<a href="/" class="logo">
<span class="logo-text">MANA</span>
<span class="logo-accent">GAMES</span>
</a>
)}
<div class="nav-links">
{isGamePage ? (
<div class="game-controls">
<Button
href="/"
variant="ghost"
size="icon"
title="Zurück zur Spieleübersicht"
class="back-btn"
>
<span class="icon">←</span>
</Button>
<div class="separator"></div>
<Button
id="menuBtn"
variant="ghost"
size="icon"
title="Menü öffnen"
>
<span class="icon">☰</span>
</Button>
<Button
id="refreshBtn"
variant="ghost"
size="icon"
title="Spiel neu laden"
>
<span class="icon">↻</span>
</Button>
<Button
id="fullscreenBtn"
variant="ghost"
size="icon"
title="Vollbild"
>
<span class="icon">⛶</span>
</Button>
<div class="separator"></div>
{isPlayground ? (
<Button
href={`/games/${gameSlug}`}
variant="accent"
size="icon"
title="Zum Spiel"
>
<span class="icon">🎮</span>
</Button>
) : (
<Button
href={`/games/${gameSlug}/playground`}
variant="accent"
size="icon"
title="Code bearbeiten"
>
<span class="icon">🔧</span>
</Button>
)}
</div>
) : (
<div class="nav-menu">
<Button href="/" variant="accent">Spiele</Button>
<Button href="/create" variant="accent">KI Generator</Button>
<Button href="/community" variant="accent">Community</Button>
<Button href="/submit" variant="ghost">Einreichen</Button>
<Button href="/stats" variant="ghost">Stats</Button>
<!-- More Dropdown -->
<div class="dropdown">
<button class="dropdown-toggle" id="moreDropdown" title="Mehr Optionen">
<span class="icon">⋮</span>
</button>
<div class="dropdown-menu" id="moreDropdownMenu">
<button class="dropdown-item" id="debugToggleDropdown">
<span class="icon">🐛</span>
<span>Debug Borders</span>
</button>
<div class="dropdown-divider"></div>
<a href="/datenschutz" class="dropdown-item">
<span class="icon">🔒</span>
<span>Datenschutz</span>
</a>
<a href="/impressum" class="dropdown-item">
<span class="icon">📋</span>
<span>Impressum</span>
</a>
<div class="dropdown-divider"></div>
<a href="/agb" class="dropdown-item">
<span class="icon">📜</span>
<span>AGB</span>
</a>
<a href="/jugendschutz" class="dropdown-item">
<span class="icon">🛡️</span>
<span>Jugendschutz</span>
</a>
<a href="/copyright" class="dropdown-item">
<span class="icon">©️</span>
<span>Copyright</span>
</a>
</div>
</div>
</div>
)}
</div>
</div>
</nav>
<main>
<slot />
</main>
{!hideFooter && <Footer />}
<InstallPrompt />
<script>
// Service Worker Registration
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registriert:', registration);
// Update gefunden
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// Neuer Service Worker verfügbar
if (confirm('Neue Version verfügbar! Jetzt aktualisieren?')) {
newWorker.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
}
}
});
});
} catch (error) {
console.error('Service Worker Registrierung fehlgeschlagen:', error);
}
});
}
// iOS PWA Detection
if (window.navigator.standalone === true) {
document.documentElement.classList.add('ios-pwa');
}
// Debug Borders Toggle
const debugToggleDropdown = document.getElementById('debugToggleDropdown');
const debugState = localStorage.getItem('debugBorders') === 'true';
// Apply initial state
if (debugState) {
document.body.classList.add('debug-borders');
}
// Function to toggle debug borders
function toggleDebugBorders() {
const isEnabled = document.body.classList.toggle('debug-borders');
localStorage.setItem('debugBorders', isEnabled.toString());
}
// Add click handler for dropdown button
debugToggleDropdown?.addEventListener('click', () => {
toggleDebugBorders();
// Close dropdown after clicking
document.getElementById('moreDropdownMenu')?.classList.remove('show');
});
// Dropdown Menu Toggle
const moreDropdown = document.getElementById('moreDropdown');
const moreDropdownMenu = document.getElementById('moreDropdownMenu');
moreDropdown?.addEventListener('click', (e) => {
e.stopPropagation();
moreDropdownMenu?.classList.toggle('show');
});
// Close dropdown when clicking outside
document.addEventListener('click', () => {
moreDropdownMenu?.classList.remove('show');
});
// Prevent dropdown from closing when clicking inside
moreDropdownMenu?.addEventListener('click', (e) => {
e.stopPropagation();
});
// Install Prompt für Android
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
// Zeige Install-Button wenn gewünscht
const installButton = document.getElementById('install-button');
if (installButton) {
installButton.style.display = 'block';
installButton.addEventListener('click', async () => {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`User ${outcome} the install prompt`);
deferredPrompt = null;
});
}
});
</script>
</body>
</html>
<style is:global>
:root {
--color-bg: #0a0a0a;
--color-bg-secondary: #1a1a1a;
--color-text: #ffffff;
--color-text-secondary: #b0b0b0;
--color-accent: #00ff88;
--color-accent-secondary: #00cc6a;
--color-border: #2a2a2a;
--max-width: 1200px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background-color: var(--color-bg);
color: var(--color-text);
line-height: 1.6;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}
body.no-scroll {
overflow: hidden;
height: 100vh;
}
main {
flex: 1;
width: 100%;
max-width: var(--max-width);
margin: 0 auto;
padding: 2rem;
}
body.full-width main {
max-width: none;
padding: 0;
}
/* Override container widths for full-width pages */
body.full-width .hero,
body.full-width .games-section,
body.full-width .stats-section,
body.full-width section {
max-width: none !important;
width: 100% !important;
}
body.full-width .games-grid {
max-width: none !important;
padding: 0 2rem;
}
body.full-width .section-content,
body.full-width .stats-container {
max-width: none !important;
}
body.no-scroll main {
padding: 0;
height: 100%;
overflow: hidden;
}
nav {
background-color: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 100;
}
/* Nav Left Container */
.nav-left {
display: flex;
align-items: center;
gap: 1rem;
}
/* Debug Toggle Button */
.debug-toggle {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text-secondary);
font-size: 1rem;
padding: 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
opacity: 0.7;
display: flex;
align-items: center;
justify-content: center;
}
/* Dropdown Styles */
.dropdown {
position: relative;
}
.dropdown-toggle {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text);
padding: 0.5rem 0.75rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 1.2rem;
display: flex;
align-items: center;
justify-content: center;
}
.dropdown-toggle:hover {
background: rgba(255, 255, 255, 0.05);
border-color: var(--color-accent);
}
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
margin-top: 0.5rem;
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: 8px;
min-width: 200px;
padding: 0.5rem;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.2s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.dropdown-menu.show {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-item {
display: flex;
align-items: center;
gap: 0.75rem;
width: 100%;
padding: 0.75rem 1rem;
background: transparent;
border: none;
color: var(--color-text);
text-decoration: none;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
font-family: inherit;
}
.dropdown-item:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--color-accent);
}
.dropdown-item .icon {
font-size: 1.1rem;
width: 1.5rem;
text-align: center;
}
.dropdown-divider {
height: 1px;
background: var(--color-border);
margin: 0.5rem 0;
}
.debug-toggle:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.05);
border-color: var(--color-border);
}
.debug-toggle.clicked {
animation: pulse 0.3s ease;
}
body.debug-borders .debug-toggle {
opacity: 1;
color: var(--color-accent);
border-color: var(--color-accent);
}
@keyframes pulse {
0% { transform: translateY(-50%) scale(1); }
50% { transform: translateY(-50%) scale(1.2); }
100% { transform: translateY(-50%) scale(1); }
}
.nav-container {
max-width: var(--max-width);
margin: 0 auto;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
text-decoration: none;
font-size: 1.5rem;
font-weight: 900;
letter-spacing: -0.05em;
}
.logo-text {
color: var(--color-text);
}
.logo-accent {
color: var(--color-accent);
margin-left: 0.25rem;
}
.nav-links {
display: flex;
gap: 2rem;
}
.nav-menu {
display: flex;
gap: 1rem;
align-items: center;
}
.game-controls {
display: flex;
gap: 0.75rem;
align-items: center;
}
.separator {
width: 1px;
height: 24px;
background-color: var(--color-border);
margin: 0 0.5rem;
}
/* Special styling for back button */
.back-btn:hover {
background-color: var(--color-text) !important;
color: var(--color-bg) !important;
border-color: var(--color-text) !important;
}
/* Breadcrumb Navigation */
.breadcrumb {
display: flex;
align-items: center;
gap: 0.75rem;
}
.breadcrumb-logo {
text-decoration: none;
font-size: 1.5rem;
font-weight: 900;
letter-spacing: -0.05em;
opacity: 0.5;
transition: opacity 0.2s ease;
}
.breadcrumb-logo:hover {
opacity: 0.8;
}
.breadcrumb-logo .logo-text {
color: var(--color-text);
}
.breadcrumb-logo .logo-accent {
color: var(--color-accent);
margin-left: 0.25rem;
}
.breadcrumb-separator {
color: var(--color-text-secondary);
opacity: 0.3;
font-size: 1.5rem;
margin: 0 0.25rem;
}
.breadcrumb-game {
color: var(--color-text);
font-size: 1.5rem;
font-weight: 900;
letter-spacing: -0.05em;
}
h1, h2, h3, h4, h5, h6 {
color: var(--color-text);
margin-bottom: 1rem;
}
p {
margin-bottom: 1rem;
}
@media (max-width: 768px) {
main {
padding: 1rem;
}
.nav-container {
padding: 1rem;
}
.nav-links {
gap: 1rem;
}
.logo {
font-size: 1.25rem;
}
.breadcrumb-logo {
font-size: 1.25rem;
}
.breadcrumb-game {
font-size: 1.25rem;
}
.breadcrumb-separator {
font-size: 1.25rem;
}
.debug-toggle {
font-size: 0.9rem;
padding: 0.4rem;
}
.dropdown-toggle {
padding: 0.4rem 0.6rem;
font-size: 1rem;
}
.dropdown-menu {
min-width: 180px;
}
}
/* Debug Borders Styles */
body.debug-borders * {
outline: 1px solid rgba(255, 0, 0, 0.25);
}
body.debug-borders div {
outline-color: rgba(0, 255, 0, 0.3);
}
body.debug-borders button,
body.debug-borders a {
outline-color: rgba(0, 255, 255, 0.4);
}
body.debug-borders section,
body.debug-borders article,
body.debug-borders main,
body.debug-borders nav,
body.debug-borders header,
body.debug-borders footer {
outline-color: rgba(255, 255, 0, 0.4);
outline-width: 2px;
}
body.debug-borders form,
body.debug-borders input,
body.debug-borders textarea,
body.debug-borders select {
outline-color: rgba(255, 0, 255, 0.4);
}
body.debug-borders img,
body.debug-borders video,
body.debug-borders iframe,
body.debug-borders canvas {
outline-color: rgba(255, 128, 0, 0.5);
outline-width: 2px;
}
body.debug-borders .container,
body.debug-borders .wrapper,
body.debug-borders .panel,
body.debug-borders .split-container,
body.debug-borders .left-panel,
body.debug-borders .right-panel {
outline-color: rgba(128, 128, 255, 0.5);
outline-width: 2px;
outline-style: dashed;
}
/* Hover effect for debug mode */
body.debug-borders *:hover {
outline-width: 2px;
outline-style: solid;
}
/* Exclude debug button from debug borders */
body.debug-borders .debug-toggle {
outline: none !important;
}
</style>