- Dynamische Modulerkennung via module.json in ModuleService.php - Kaskadierender Env-Loader fuer zentrale API-Keys (GEMINI_API_KEY) - Graceful Auth fuer alle Subprojekte (dinos, plants, storymachine, test) - Wiederverwendbares Starter-Template in _template/ - test-Modul auf philippurbschat.de Dark-Design modernisiert
152 lines
7.5 KiB
PHP
152 lines
7.5 KiB
PHP
<?php
|
||
require_once __DIR__ . '/bootstrap.php';
|
||
|
||
// Modul-Metadaten laden
|
||
$moduleDescriptor = file_exists(__DIR__ . '/module.json')
|
||
? json_decode(file_get_contents(__DIR__ . '/module.json'), true)
|
||
: [];
|
||
|
||
$moduleName = $moduleDescriptor['name'] ?? 'Neues Projekt';
|
||
$moduleBadge = $moduleDescriptor['badge'] ?? 'Lab';
|
||
$moduleDesc = $moduleDescriptor['description'] ?? 'Experimentelles Web-Modul';
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title><?= htmlspecialchars($moduleName) ?> – philippurbschat.de</title>
|
||
|
||
<!-- Zentrale Styles von philippurbschat.de -->
|
||
<link rel="stylesheet" href="/css/app.css">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
|
||
<!-- Standalone-Fallback: Falls außerhalb von philippurbschat.de gehostet -->
|
||
<script>
|
||
window.addEventListener('error', function(e) {
|
||
if (e.target && e.target.tagName === 'LINK' && e.target.href.includes('/css/app.css')) {
|
||
const cdn = document.createElement('script');
|
||
cdn.src = 'https://cdn.tailwindcss.com';
|
||
document.head.appendChild(cdn);
|
||
}
|
||
}, true);
|
||
</script>
|
||
</head>
|
||
<body class="bg-slate-950 text-slate-200 min-h-screen font-sans flex flex-col antialiased selection:bg-fuchsia-500 selection:text-white">
|
||
|
||
<!-- Top Navigation Bar -->
|
||
<header class="border-b border-slate-800/80 bg-slate-900/50 backdrop-blur-md sticky top-0 z-50">
|
||
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-3.5 flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<a href="/" class="text-xs font-mono text-slate-400 hover:text-cyan-400 transition-colors flex items-center gap-1.5 group">
|
||
<span class="group-hover:-translate-x-0.5 transition-transform">←</span> philippurbschat.de
|
||
</a>
|
||
<span class="text-slate-700">/</span>
|
||
<span class="text-xs font-mono text-slate-300 font-medium"><?= htmlspecialchars(basename(__DIR__)) ?></span>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-[11px] font-mono px-2 py-0.5 rounded-full border border-cyan-500/30 bg-cyan-500/10 text-cyan-400 uppercase tracking-wider">
|
||
<?= htmlspecialchars($moduleBadge) ?>
|
||
</span>
|
||
<span class="text-[11px] font-mono text-slate-500 hidden sm:inline-block">
|
||
<?= htmlspecialchars($currentUser) ?>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Content Container -->
|
||
<main class="grow max-w-5xl mx-auto px-4 sm:px-6 py-10 w-full">
|
||
<!-- Header Section -->
|
||
<div class="mb-10">
|
||
<div class="inline-flex items-center gap-2 mb-3">
|
||
<span class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
|
||
<span class="text-xs font-mono uppercase tracking-[0.2em] text-emerald-400">System Ready</span>
|
||
</div>
|
||
<h1 class="text-3xl sm:text-4xl font-bold font-mono tracking-tight text-white mb-2">
|
||
<?= htmlspecialchars($moduleName) ?>
|
||
</h1>
|
||
<p class="text-slate-400 text-sm max-w-2xl">
|
||
<?= htmlspecialchars($moduleDesc) ?>
|
||
</p>
|
||
</div>
|
||
|
||
<!-- App Sandbox Card -->
|
||
<div class="bg-slate-900/40 border border-slate-800 rounded-2xl p-6 sm:p-8 backdrop-blur shadow-2xl relative overflow-hidden">
|
||
<div class="absolute top-0 left-0 w-full h-[1px] bg-gradient-to-r from-transparent via-cyan-500/30 to-transparent"></div>
|
||
|
||
<div class="space-y-6">
|
||
<div>
|
||
<h2 class="text-xs font-mono uppercase tracking-widest text-slate-400 mb-2">Modul Workspace</h2>
|
||
<p class="text-sm text-slate-300 leading-relaxed">
|
||
Hier startet dein neues Mini-Projekt. Du hast vollen Zugriff auf Tailwind CSS, den zentralen Gemini API-Key und das Dark-Design der Hauptseite.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Interaktiver AI-Testbereich -->
|
||
<div class="pt-4 border-t border-slate-800/80">
|
||
<label for="promptInput" class="block text-xs font-mono uppercase text-slate-400 mb-2">
|
||
Schnelltest: Gemini API Relay
|
||
</label>
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<input type="text" id="promptInput" placeholder="Stelle eine kurze Frage..."
|
||
value="Erkläre Quantencomputer in einem prägnanten Satz."
|
||
class="grow bg-slate-950/80 border border-slate-700/80 rounded-xl px-4 py-3 text-sm text-slate-200 placeholder:text-slate-600 focus:outline-none focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500 transition-all font-mono">
|
||
<button id="sendBtn" class="bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/30 hover:border-cyan-500/60 font-mono text-xs uppercase tracking-wider px-6 py-3 rounded-xl transition-all duration-200 flex items-center justify-center gap-2 cursor-pointer active:scale-95">
|
||
<span>Ausführen</span> →
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Output Area -->
|
||
<div id="outputContainer" class="hidden mt-4 p-4 rounded-xl bg-slate-950/90 border border-slate-800/90 font-mono text-xs text-slate-300 whitespace-pre-wrap leading-relaxed"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Footer -->
|
||
<footer class="border-t border-slate-900 py-6 text-center text-xs font-mono text-slate-600">
|
||
<?= htmlspecialchars($moduleName) ?> • Philipp Urbschat Lab
|
||
</footer>
|
||
|
||
<script>
|
||
const sendBtn = document.getElementById('sendBtn');
|
||
const promptInput = document.getElementById('promptInput');
|
||
const outputContainer = document.getElementById('outputContainer');
|
||
|
||
sendBtn.addEventListener('click', async () => {
|
||
const prompt = promptInput.value.trim();
|
||
if (!prompt) return;
|
||
|
||
sendBtn.disabled = true;
|
||
sendBtn.innerHTML = 'Verarbeite...';
|
||
outputContainer.classList.remove('hidden');
|
||
outputContainer.textContent = 'Verbinde mit Gemini API...';
|
||
|
||
try {
|
||
const res = await fetch('api.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ prompt: prompt })
|
||
});
|
||
|
||
const data = await res.json();
|
||
if (data.error) {
|
||
outputContainer.textContent = 'Fehler: ' + data.error;
|
||
outputContainer.classList.add('text-rose-400');
|
||
} else {
|
||
outputContainer.classList.remove('text-rose-400');
|
||
const reply = data.candidates?.[0]?.content?.parts?.[0]?.text || JSON.stringify(data, null, 2);
|
||
outputContainer.textContent = reply;
|
||
}
|
||
} catch (err) {
|
||
outputContainer.textContent = 'Netzwerkfehler: ' + err.message;
|
||
outputContainer.classList.add('text-rose-400');
|
||
} finally {
|
||
sendBtn.disabled = false;
|
||
sendBtn.innerHTML = '<span>Ausführen</span> →';
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|