diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..7a550fe
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,9 @@
+# GEMINI_API_KEY: Required for Gemini AI API calls.
+# AI Studio automatically injects this at runtime from user secrets.
+# Users configure this via the Secrets panel in the AI Studio UI.
+GEMINI_API_KEY="MY_GEMINI_API_KEY"
+
+# APP_URL: The URL where this applet is hosted.
+# AI Studio automatically injects this at runtime with the Cloud Run service URL.
+# Used for self-referential links, OAuth callbacks, and API endpoints.
+APP_URL="MY_APP_URL"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5a86d2a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+build/
+dist/
+coverage/
+.DS_Store
+*.log
+.env*
+!.env.example
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..68a591f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,40 @@
+# Build Stage
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+# Copy dependency manifests
+COPY package*.json ./
+
+# Install all dependencies
+RUN npm ci
+
+# Copy full application codebase
+COPY . .
+
+# Compile frontend and bundle backend files
+RUN npm run build
+
+# Production alpine stage
+FROM node:20-alpine
+
+WORKDIR /app
+
+# Copy configuration files
+COPY package*.json ./
+
+# Install only production dependencies (Express, dotenv, @google/genai)
+RUN npm ci --only=production
+
+# Copy compiled build resources from builder stage
+COPY --from=builder /app/dist ./dist
+COPY --from=builder /app/data ./data
+
+# Expose port (Internal container routing to 3000)
+EXPOSE 3000
+
+ENV NODE_ENV=production
+ENV ADMIN_PASSWORD=autor2026
+
+# Execute standalone node production entrypoint
+CMD ["node", "dist/server.cjs"]
diff --git a/README.md b/README.md
index 2241000..b0186f4 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,92 @@
-
+# 📚 Dual Identity Autoren-Zentrale (Selbstgehostet)
-
+Willkommen in Ihrer modernen, reaktiven Autoren-Website für das 21. Jahrhundert! Dieses System ermöglicht es Ihnen, zwei völlig eigenständige Autorenprofile mit individuellen Themen, Biografien, Schreibprojekten und Bücherregalen über eine einzige, passwortgeschützte Administrationsoberfläche zu pflegen.
-
Built with AI Studio
+---
-
The fastest path from prompt to production with Gemini.
+## 🛠️ Warum kein klassischer LAMP-Stack? (Unser Vorschlag)
- Start building
+Bisher liefen klassische Portfolios oft auf einem standardmäßigen **LAMP-Stack** (Linux, Apache, MySQL, PHP). Für diese modernisierte Website schlagen wir einen **Node.JS + Docker-Stack** vor. Hier ist der Grund, warum das für Ihre Bedürfnisse die deutlich bessere Option ist:
-
+1. **Vollständige Kapselung (Kein Server-Wildwuchs):** Bei einem LAMP-Stack müssen Sie Apache/Nginx, PHP-Laufzeiten und eine MySQL-Datenbank auf dem Host installieren und managen. Unser Docker-Stack bündelt das gesamte Frontend, das Express-Backend und die Speicherrelevanz in einem **einzigen, autarken Container**.
+2. **Blitzschnelle, reaktive Benutzeroberfläche (SPA):** Das Frontend nutzt modernste Webtechnologie (React & Tailwind CSS). Seitenwechsel und Buchdetails laden augenblicklich im Browser des Nutzers, ohne das klassische, träge Neuladen von PHP-Seiten.
+3. **Einfachheit bei Datensicherungen (Backups):** Anstatt komplexe MySQL-Dumps durchzuführen, speichert unsere Anwendung alle Buchtitel, Projekte, Cover und Biografien in einer schlanken, robusten JSON-Datenbankdatei (`/data/database.json`). Ein Backup lässt sich durch einfaches Kopieren dieser Datei anfertigen!
+4. **KI-Unterstützung integriert:** Der integrierte **Gemini-Klappentext-Assistent** läuft nahtlos und sicher über das Node-Backend (ohne API-Schlüssel im Webbrowser zu exponieren).
+
+---
+
+## 🌌 Die Trennung der Profile
+
+Das System ist von Grund auf so strukturiert, dass Besucher der Website **keine Verbindung** zwischen Ihren beiden Identitäten herstellen können:
+- **Echte Identität (Daniel Hesse — Sci-Fi & Fantasy):** Erreichbar auf der Standard-Startseite (`/`). Das Design kombiniert dunkelblaue Weltraum-Energie, Monospace-Einflüsse und kosmische Magie-Akzente.
+- **Pseudonym (M. S. Velvet — Sinnliche Literatur):** Erreichbar **ausschließlich** über den Pfad `/sensual-moments`. Sie verwendet ein warmes, edles "Champagner-Gold & Samt-Bordeaux"-Thema mit klassischer und anspruchsvoller Serif-Asthetik. Es gibt keinerlei gegenseitige Verlinkungen!
+
+---
+
+## 🚀 Inbetriebnahme als Docker-Stack (Empfohlen)
+
+Es wird dringend empfohlen, die Website mithilfe von **Docker und Docker-Compose** zu betreiben. Dies garantiert, dass die Anwendung sofort und unabhängig von installierten Node.JS-Versionen auf Ihrem Server funktioniert.
+
+### 1. Dateien vorbereiten
+Stellen Sie sicher, dass sich folgende Dateien im gleichen Ordner auf Ihrem Server befinden:
+- `Dockerfile`
+- `docker-compose.yml`
+- Der gesamte Code-Ordner
+
+### 2. Konfiguration anpassen
+Öffnen Sie die `docker-compose.yml` auf Ihrem Server und passen Sie folgende Umgebungsvariablen an:
+```yaml
+environment:
+ - NODE_ENV=production
+ # Das Passwort für Ihren Administrationsbereich (/admin):
+ - ADMIN_PASSWORD=IhrSicheresLieblingsPasswort123
+ # (Optional) Für den 21. Jahrhundert Schreibassistenten (Gemini 3.5 Flash)
+ - GEMINI_API_KEY=Ihr_Gemini_API_Schluessel
+```
+
+### 3. Container starten
+Führen Sie im entsprechenden Verzeichnis folgenden Befehl aus:
+```bash
+docker compose up -d --build
+```
+Die Anwendung baut das Image und startet die Autoren-Zentrale im Hintergrund. Sie ist nun direkt auf Port **`3000`** Ihres Webservers erreichbar!
+
+#### Reverse Proxy Tipp:
+Sie können ganz hervorragend einen Reverse Proxy wie **Nginx Proxy Manager**, **Traefik** oder **Caddy** davorhängen, um SSL-Zertifikate (Let's Encrypt) zuzuweisen und Ihre Domain auf den Container-Port `3000` umzuleiten.
+
+---
+
+## 💾 Manuelle Installation ohne Docker (Alternativ)
+
+Sollten Sie die Software direkt auf Ihrem Server (ohne Docker) starten wollen:
+
+1. **Voraussetzung:** Installieren Sie **Node.js** (v18 oder neuer) auf Ihrem Linux- oder Windows-Server.
+2. **Abhängigkeiten installieren:**
+ ```bash
+ npm install
+ ```
+3. **Produktions-Build ausführen:**
+ ```bash
+ npm run build
+ ```
+4. **Umgebungsvariablen setzen:**
+ Erstellen Sie eine `.env`-Datei oder exportieren Sie diese im Terminal:
+ ```bash
+ export ADMIN_PASSWORD="IhrSicheresPasswort"
+ export GEMINI_API_KEY="Ihr_Gemini_API_Schlüssel"
+ ```
+5. **Starten:**
+ ```bash
+ npm start
+ ```
+
+---
+
+## 🧱 Datensicherung / Backups
+
+Alle Ihre wertvollen Inhalte (Biografien, Projektfortschritte und alle Bücherregal-Einträge) liegen verschlüsselt strukturiert in Ihrem Projektordner unter:
+`./data/database.json`
+
+Da wir in der `docker-compose.yml` ein **lokales Volume** gemountet haben (`./data:/app/data`), wird diese Datei direkt auf die Festplatte Ihres Servers geschrieben.
+- **Backup erstellen:** Sichern Sie einfach die Datei `./data/database.json`.
+- **Wiederherstellen:** Platzieren Sie eine gesicherte `database.json` in den `./data/`-Ordner vor dem Starten des Containers.
diff --git a/assets/.aistudio/.gitignore b/assets/.aistudio/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/assets/.aistudio/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..16119fa
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,19 @@
+version: "3.8"
+
+services:
+ author-suite:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: author_portfolio_suite
+ restart: unless-stopped
+ ports:
+ - "3000:3000"
+ volumes:
+ - ./data:/app/data
+ environment:
+ - NODE_ENV=production
+ # Ändern Sie das Passwort für Ihren Administrationsbereich:
+ - ADMIN_PASSWORD=IhrSicheresPasswort2026
+ # (Optional) Für den 21. Jahrhundert KI-Klappentextassistenten (Gemini 3.5 Flash)
+ - GEMINI_API_KEY=Ihr_Gemini_API_Schluessel
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..21dfe69
--- /dev/null
+++ b/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ My Google AI Studio App
+
+
+
+
+
+
+
diff --git a/metadata.json b/metadata.json
new file mode 100644
index 0000000..4ec7c95
--- /dev/null
+++ b/metadata.json
@@ -0,0 +1,6 @@
+{
+ "name": "Dual Identity Author Portfolio",
+ "description": "A high-performance modern portfolio for authors writing under multiple pen names. Completely separate visual themes, isolated routes, integrated Spotify player embeds, and an administrative panel. Docker self-hosting ready.",
+ "requestFramePermissions": [],
+ "majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"]
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8f06d77
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "react-example",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "tsx server.ts",
+ "build": "vite build && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs",
+ "start": "node dist/server.cjs",
+ "clean": "rm -rf dist",
+ "lint": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@google/genai": "^2.4.0",
+ "@tailwindcss/vite": "^4.1.14",
+ "@vitejs/plugin-react": "^5.0.4",
+ "lucide-react": "^0.546.0",
+ "react": "^19.0.1",
+ "react-dom": "^19.0.1",
+ "vite": "^6.2.3",
+ "express": "^4.21.2",
+ "dotenv": "^17.2.3",
+ "motion": "^12.23.24"
+ },
+ "devDependencies": {
+ "@types/node": "^22.14.0",
+ "autoprefixer": "^10.4.21",
+ "esbuild": "^0.25.0",
+ "tailwindcss": "^4.1.14",
+ "tsx": "^4.21.0",
+ "typescript": "~5.8.2",
+ "vite": "^6.2.3",
+ "@types/express": "^4.17.21"
+ }
+}
diff --git a/server.ts b/server.ts
new file mode 100644
index 0000000..118823e
--- /dev/null
+++ b/server.ts
@@ -0,0 +1,186 @@
+import express from "express";
+import path from "path";
+import fs from "fs/promises";
+import { fileURLToPath } from "url";
+import dotenv from "dotenv";
+import { GoogleGenAI } from "@google/genai";
+import { defaultAuthorData } from "./src/defaultData.js";
+import { AuthorData } from "./src/types.js";
+
+dotenv.config();
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const app = express();
+const PORT = 3000;
+
+// Path to durable local database file
+const DATA_DIR = path.join(process.cwd(), "data");
+const DATA_FILE = path.join(DATA_DIR, "database.json");
+
+// Establish initial database of authors
+async function initDatabase(): Promise {
+ try {
+ await fs.mkdir(DATA_DIR, { recursive: true });
+ try {
+ const existingData = await fs.readFile(DATA_FILE, "utf-8");
+ return JSON.parse(existingData) as AuthorData;
+ } catch {
+ // If file doesn't exist, seed it with default data
+ await fs.writeFile(DATA_FILE, JSON.stringify(defaultAuthorData, null, 2), "utf-8");
+ return defaultAuthorData;
+ }
+ } catch (err) {
+ console.error("Database storage initialization failed, using default data in-memory:", err);
+ return defaultAuthorData;
+ }
+}
+
+// In-memory runtime data cache, synced to active storage file
+let dbCache: AuthorData;
+
+// Initialize on boot
+initDatabase().then((data) => {
+ dbCache = data;
+});
+
+// Configure middleware
+app.use(express.json({ limit: "10mb" }));
+
+// Initialize Google GenAI if API key exists
+const getGeminiClient = () => {
+ const apiKey = process.env.GEMINI_API_KEY;
+ if (!apiKey) return null;
+ return new GoogleGenAI({
+ apiKey,
+ httpOptions: {
+ headers: {
+ "User-Agent": "aistudio-build",
+ },
+ },
+ });
+};
+
+// Admin authentication password helper
+// In production or self-hosted, they set ADMIN_PASSWORD in environment or docker-compose.
+// Default fallback is "autor2026"
+const getAdminPassword = () => {
+ return process.env.ADMIN_PASSWORD || "autor2026";
+};
+
+// Authorization verification middleware
+const verifyToken = (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ const authHeader = req.headers.authorization;
+ if (!authHeader) {
+ res.status(401).json({ error: "Kein Autorisierungs-Token bereitgestellt." });
+ return;
+ }
+ const token = authHeader.replace("Bearer ", "");
+ // To keep session simple, secure, and self-hosted, our auth token is just the password itself or adminPassword
+ if (token === getAdminPassword()) {
+ next();
+ } else {
+ res.status(403).json({ error: "Ungültiges Passwort oder Sitzungstoken." });
+ }
+};
+
+// --- API ROUTES ---
+
+// 1. Get entire public author setup
+app.get("/api/author-data", (req, res) => {
+ res.json(dbCache || defaultAuthorData);
+});
+
+// 2. Manage Admin Login
+app.post("/api/admin/login", (req, res) => {
+ const { password } = req.body;
+ if (!password) {
+ res.status(400).json({ error: "Passwort ist erforderlich." });
+ return;
+ }
+
+ if (password === getAdminPassword()) {
+ // Return the token which client stores in localStorage
+ res.json({ success: true, token: getAdminPassword() });
+ } else {
+ res.status(401).json({ error: "Ungültiges Passwort. Bitte versuchen Sie es erneut." });
+ }
+});
+
+// 3. Save modified profile configurations (About, Projects, Books)
+app.post("/api/admin/save-profile", verifyToken, async (req, res) => {
+ const { profileKey, profileData } = req.body;
+ if (profileKey !== "scifi" && profileKey !== "erotica") {
+ res.status(400).json({ error: "Ungültiger Profilschlüssel." });
+ return;
+ }
+
+ try {
+ dbCache[profileKey] = profileData;
+ await fs.writeFile(DATA_FILE, JSON.stringify(dbCache, null, 2), "utf-8");
+ res.json({ success: true, message: "Profil erfolgreich gespeichert." });
+ } catch (err: any) {
+ console.error("Failed to write to database.json:", err);
+ res.status(500).json({ error: "Fehler beim persistenten Speichern der Formulardaten." });
+ }
+});
+
+// 4. Creative AI Blurb Assistant for book blurb updates
+app.post("/api/admin/generate-blurb", verifyToken, async (req, res) => {
+ const { title, genre, ideas, tone } = req.body;
+
+ const ai = getGeminiClient();
+ if (!ai) {
+ res.status(503).json({
+ error: "Mit dem integrierten KI-Schreibassistenten konnte keine Verbindung hergestellt werden. Bitte stellen Sie sicher, dass GEMINI_API_KEY konfiguriert ist."
+ });
+ return;
+ }
+
+ try {
+ const prompt = `Du bist ein professioneller literarischer Marketing-Experte und Buch-Co-Autor.
+Schreibe eine fesselnde, hochkarätige Synopsis (Buchrückenblurb) auf Deutsch für folgendes Buch:
+- Buchtitel: "${title}"
+- Genre/Kategorie: "${genre}"
+- Eingebundene Ideen/Motive: "${ideas}"
+- Gewünschte Tonalität/Stilrichtung: "${tone}"
+
+Die Synopsis soll neugierig machen, stark die visuelle Stimmung rüberbringen und frei von Klischees sein. Teile den Text in zwei bis max. drei Absätze auf, evtl. eingeleitet durch einen kurzen, fetten Einzeiler, der Aufmerksamkeit catched (z.B. ein prägnanter Satz). Antworte NUR mit dem generierten Blurb-Text in Deutsch.`;
+
+ const response = await ai.models.generateContent({
+ model: "gemini-3.5-flash",
+ contents: prompt,
+ });
+
+ const generatedText = response.text || "Fehler beim Generieren der Synopsis.";
+ res.json({ success: true, text: generatedText });
+ } catch (error: any) {
+ console.error("Gemini-Fehler aufgetreten:", error);
+ res.status(500).json({ error: "KI-Generierungsfehler: " + error.message });
+ }
+});
+
+// Configure Vite middleware or static serve
+async function startServer() {
+ if (process.env.NODE_ENV !== "production") {
+ const { createServer: createViteServer } = await import("vite");
+ const vite = await createViteServer({
+ server: { middlewareMode: true },
+ appType: "spa",
+ });
+ app.use(vite.middlewares);
+ } else {
+ const distPath = path.join(process.cwd(), "dist");
+ app.use(express.static(distPath));
+ app._router.get("*", (req: express.Request, res: express.Response) => {
+ res.sendFile(path.join(distPath, "index.html"));
+ });
+ }
+
+ app.listen(PORT, "0.0.0.0", () => {
+ console.log(`Server running on http://0.0.0.0:${PORT}`);
+ });
+}
+
+startServer();
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..10fd809
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,114 @@
+import { useState, useEffect } from "react";
+import { AuthorData } from "./types";
+import SciFiPortfolio from "./components/SciFiPortfolio";
+import EroticaPortfolio from "./components/EroticaPortfolio";
+import AdminPanel from "./components/AdminPanel";
+import { Lock, Eye, Sparkles, RefreshCw } from "lucide-react";
+
+export default function App() {
+ const [authorData, setAuthorData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [currentPath, setCurrentPath] = useState(window.location.pathname);
+
+ // Monitor location changes for custom routing
+ useEffect(() => {
+ const handleLocationChange = () => {
+ setCurrentPath(window.location.pathname);
+ };
+
+ // Listen to popstate for navigation back/forward support
+ window.addEventListener("popstate", handleLocationChange);
+ return () => {
+ window.removeEventListener("popstate", handleLocationChange);
+ };
+ }, []);
+
+ // Fetch profiles configuration from server
+ useEffect(() => {
+ fetch("/api/author-data")
+ .then((res) => res.json())
+ .then((data) => {
+ setAuthorData(data);
+ setLoading(false);
+ })
+ .catch((err) => {
+ console.error("Fehler beim Abrufen der Autoren-Informationen:", err);
+ setLoading(false);
+ });
+ }, []);
+
+ const handleAdminLogout = () => {
+ // Relocate to landing site after administration work
+ window.history.pushState({}, "", "/");
+ setCurrentPath("/");
+ };
+
+ if (loading) {
+ return (
+
+
+
Kryptografisches Terminal bootet...
+
+ );
+ }
+
+ if (!authorData) {
+ return (
+
+
+
Verbindungsfehler
+
+ Die Verbindung zum lokalen Datenspeicher-Server konnte nicht hergestellt werden. Bitte vergewissern Sie sich, dass Ihr Express API-Server ordnungsgemäß läuft.
+
+
+
+ );
+ }
+
+ // Purely route-based render isolation structure
+ // 1. Administration panel
+ if (currentPath === "/admin" || currentPath.startsWith("/admin/")) {
+ return ;
+ }
+
+ // 2. Sensual/Erotica Portfolio pseudonym - accessed ONLY via /sensual-moments (Strict content separation)
+ if (currentPath === "/sensual-moments" || currentPath.startsWith("/sensual-moments/")) {
+ return ;
+ }
+
+ // 3. Fallback: SciFi & Fantasy portfolio - accessed via `/` (Daniel Hesse identity)
+ return (
+ <>
+
+
+ {/* Absolute floating administration bypass link hidden on the bottom right for convenience of testing/admin accessing */}
+
+
+ {/* 21st Century KI Integration - Gemini 3.5 Blurb generator tool panel */}
+
+
+
+
+ KI-Schreibwerkstatt: Klappentext-Generator
+
+ Powered by Gemini 3.5
+
+
+
+
+ Haben Sie eine Schreibblockade? Geben Sie ein paar Stichworte Ihrer Ideen ein. Unser KI-Assistent formuliert für Sie einen verkaufsfördernden und atmosphärischen Klappentext passend zur Identität!
+
+
+ {/* Integrated Spotify web player playlist if available */}
+ {selectedBook.spotifyPlaylistId && selectedBook.spotifyPlaylistId.trim() !== "" && (
+