Merge pull request #9 from Dada1981/dev

Modularization done
This commit is contained in:
Dada1981 2026-07-26 21:36:20 +02:00 committed by GitHub
commit e7d0a43c1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 1601 additions and 297 deletions

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
# Ignoriere lokale Daten und Abhängigkeiten beim Docker-Build
node_modules
dist
data
.git
.env
.env.*
*.log

2
.gitignore vendored
View file

@ -6,5 +6,5 @@ coverage/
*.log
.env*
!.env.example
data/database.json
data/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

View file

@ -5,7 +5,7 @@ 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";
import { AuthorData, AuthorProfile } from "./src/types.js";
dotenv.config();
@ -32,36 +32,43 @@ async function initDatabase(): Promise<AuthorData> {
try {
const existingData = await fs.readFile(DATA_FILE, "utf-8");
const parsed = JSON.parse(existingData) as AuthorData;
// Auto-heal database: make sure all profiles from defaultAuthorData exist
let modified = false;
if (!parsed.scifi) {
parsed.scifi = defaultAuthorData.scifi;
// Ensure all dynamic static text and design properties exist for all profiles
const profileKeys: ("scifi" | "erotica" | "clara" | "renee")[] = ["scifi", "erotica", "clara", "renee"];
for (const pKey of profileKeys) {
if (!parsed[pKey]) {
parsed[pKey] = defaultAuthorData[pKey];
modified = true;
} else {
const defaults = defaultAuthorData[pKey];
const keysToEnsure: (keyof AuthorProfile)[] = [
"customSectionTitle", "customSectionContent",
"customDomain", "customPath",
"bioTag1Label", "bioTag1Value",
"bioTag2Label", "bioTag2Value",
"bioTag3Label", "bioTag3Value",
"badgeText", "aboutTitle", "projectsTitle", "projectsSubtitle",
"booksTitle", "booksSubtitle", "footerText",
"accentColor", "secondaryColor", "backgroundColor", "cardBgColor",
"fontFamily", "heroBannerUrl"
];
for (const k of keysToEnsure) {
if (parsed[pKey][k] === undefined) {
(parsed[pKey] as any)[k] = defaults[k];
modified = true;
}
if (!parsed.erotica) {
parsed.erotica = defaultAuthorData.erotica;
modified = true;
}
if (!parsed.clara) {
parsed.clara = defaultAuthorData.clara;
modified = true;
}
}
// Ensure customSection fields exist for all profiles
if (parsed.scifi && parsed.scifi.customSectionTitle === undefined) {
parsed.scifi.customSectionTitle = defaultAuthorData.scifi.customSectionTitle;
parsed.scifi.customSectionContent = defaultAuthorData.scifi.customSectionContent;
modified = true;
// Migration check: update erotica default name from M. S. Velvet / Marc Velvet to Annie Slone if unchanged
if (parsed.erotica && (parsed.erotica.name === "M. S. Velvet" || parsed.erotica.name === "Marc Velvet")) {
parsed.erotica.name = "Annie Slone";
parsed.erotica.heroTitle = "ANNIE SLONE";
if (parsed.erotica.bio && parsed.erotica.bio.includes("M. S. Velvet")) {
parsed.erotica.bio = parsed.erotica.bio.replace(/M\. S\. Velvet/g, "Annie Slone");
}
if (parsed.erotica && parsed.erotica.customSectionTitle === undefined) {
parsed.erotica.customSectionTitle = defaultAuthorData.erotica.customSectionTitle;
parsed.erotica.customSectionContent = defaultAuthorData.erotica.customSectionContent;
modified = true;
}
if (parsed.clara && parsed.clara.customSectionTitle === undefined) {
parsed.clara.customSectionTitle = defaultAuthorData.clara.customSectionTitle;
parsed.clara.customSectionContent = defaultAuthorData.clara.customSectionContent;
modified = true;
}
@ -154,7 +161,7 @@ app.post("/api/admin/login", (req, res) => {
// 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" && profileKey !== "clara") {
if (profileKey !== "scifi" && profileKey !== "erotica" && profileKey !== "clara" && profileKey !== "renee") {
res.status(400).json({ error: "Ungültiger Profilschlüssel." });
return;
}

View file

@ -3,6 +3,7 @@ import { AuthorData } from "./types";
import SciFiPortfolio from "./components/SciFiPortfolio";
import EroticaPortfolio from "./components/EroticaPortfolio";
import ClaraPortfolio from "./components/ClaraPortfolio";
import ReneePortfolio from "./components/ReneePortfolio";
import AdminPanel from "./components/AdminPanel";
import { Lock, Eye, Sparkles, RefreshCw, Leaf } from "lucide-react";
@ -39,29 +40,53 @@ export default function App() {
}, []);
// Ermittelt die aktive Suite basierend auf Pfad-Überschreibungen oder dem Domainnamen (Hostname)
const getActiveSuite = (): "erotica" | "clara" | "scifi" => {
const getActiveSuite = (): "erotica" | "clara" | "scifi" | "renee" => {
const hostname = window.location.hostname.toLowerCase();
const profileKeys: ("erotica" | "clara" | "renee" | "scifi")[] = ["erotica", "clara", "renee", "scifi"];
// 1. Pfad-basierte Routen (haben Vorrang, z.B. für die Vorschau oder Admin-Interaktionen)
if (currentPath === "/sensual-moments" || currentPath.startsWith("/sensual-moments/")) {
return "erotica";
// 1. Pfad-basierte Routen (haben Vorrang, z.B. für Vorschau & Direktlinks)
for (const key of profileKeys) {
const p = authorData?.[key];
const customPaths: string[] = p?.customPath
? p.customPath.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
// Statische Standard-Fallbacks
if (key === "erotica") customPaths.push("/sensual-moments", "/annie-slone", "/marc-velvet");
if (key === "clara") customPaths.push("/clara-finch", "/clara");
if (key === "renee") customPaths.push("/renee-heart", "/renee");
if (key === "scifi") customPaths.push("/sci-fi", "/daniel-hesse");
for (const prefix of customPaths) {
if (prefix) {
const formattedPrefix = prefix.startsWith("/") ? prefix : "/" + prefix;
if (currentPath === formattedPrefix || currentPath.startsWith(formattedPrefix + "/")) {
return key;
}
}
if (currentPath === "/clara-finch" || currentPath.startsWith("/clara-finch/")) {
return "clara";
}
if (currentPath === "/sci-fi" || currentPath.startsWith("/sci-fi/")) {
return "scifi";
}
// 2. Domain-basierte Routen (für den Live-Betrieb hinter einem Reverse Proxy)
if (hostname.includes("annieslone.de")) {
return "erotica";
// 2. Dynamische Domain-basierte Routen (für Live-Domains)
for (const key of profileKeys) {
const p = authorData?.[key];
const customDomains: string[] = p?.customDomain
? p.customDomain.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
: [];
// Statische Standard-Fallbacks
if (key === "erotica") customDomains.push("annieslone.de", "marcvelvet.de");
if (key === "clara") customDomains.push("clarafinch.de", "clara-finch.de");
if (key === "renee") customDomains.push("reneeheart.de", "renee-heart.de");
if (key === "scifi") customDomains.push("hesse-sf.de", "danielhesse.de");
for (const domain of customDomains) {
// Entferne evtl. eingegebene Protokolle (http://, https://) oder führendes "www."
const cleanDomain = domain.replace(/^https?:\/\//, "").replace(/^www\./, "").trim();
if (cleanDomain && (hostname === cleanDomain || hostname.includes(cleanDomain))) {
return key;
}
if (hostname.includes("clarafinch.de")) {
return "clara";
}
if (hostname.includes("hesse-sf.de")) {
return "scifi";
}
// Standard-Fallback (Daniel Hesse SciFi)
@ -81,9 +106,11 @@ export default function App() {
const activeSuite = getActiveSuite();
if (activeSuite === "erotica") {
document.title = authorData.erotica.name || "Marc Velvet";
document.title = authorData.erotica.name || "Annie Slone";
} else if (activeSuite === "clara") {
document.title = authorData.clara.name || "Clara Finch";
} else if (activeSuite === "renee") {
document.title = authorData.renee.name || "Renee Heart";
} else {
document.title = authorData.scifi.name || "Daniel Hesse";
}
@ -123,10 +150,7 @@ export default function App() {
return <AdminPanel onLogout={handleAdminLogout} />;
}
// 2. Sensual/Erotica Portfolio pseudonym
// 3. Poetic Clara Finch Portfolio pseudonym
// 4. SciFi Daniel Hesse Portfolio
// Render active portfolio
const renderActivePortfolio = () => {
const activeSuite = getActiveSuite();
if (activeSuite === "erotica") {
@ -135,6 +159,9 @@ export default function App() {
if (activeSuite === "clara") {
return <ClaraPortfolio data={authorData.clara} />;
}
if (activeSuite === "renee") {
return <ReneePortfolio data={authorData.renee} />;
}
return <SciFiPortfolio data={authorData.scifi} />;
};

View file

@ -2,7 +2,8 @@ import React, { useState, useEffect, FormEvent } from "react";
import { AuthorData, AuthorProfile, Book, Project } from "../types";
import {
Lock, Save, Plus, Trash2, Edit2, Check, RefreshCw, Sparkles,
BookOpen, Compass, Clipboard, Eye, Music, LogOut, ArrowLeftRight
BookOpen, Compass, Clipboard, Eye, Music, LogOut, ArrowLeftRight,
Palette, Type, Image as ImageIcon, Globe, Tag
} from "lucide-react";
import ImagePicker from "./ImagePicker";
@ -20,11 +21,11 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
// Author Data State
const [authorData, setAuthorData] = useState<AuthorData | null>(null);
// Selected Profile state ("scifi" vs "erotica" vs "clara")
const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara">("scifi");
// Selected Profile state ("scifi" vs "erotica" vs "clara" vs "renee")
const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara" | "renee">("scifi");
// Sub-tab selection state ("profile" | "books" | "projects")
const [activeTab, setActiveTab] = useState<"profile" | "books" | "projects">("profile");
// Sub-tab selection state ("profile" | "texts" | "design" | "books" | "projects")
const [activeTab, setActiveTab] = useState<"profile" | "texts" | "design" | "books" | "projects">("profile");
// Editing forms state
const [editingBookId, setEditingBookId] = useState<string | null>(null);
@ -98,7 +99,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
};
// Profile save handler
const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara") => {
const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara" | "renee") => {
if (!authorData) return;
setError("");
setLoading(true);
@ -411,24 +412,30 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
<div className="flex items-center gap-3">
{/* Swapping genre profile profile */}
<div className="bg-slate-950 p-0.5 rounded-xl border border-slate-800 flex">
<div className="bg-slate-950 p-0.5 rounded-xl border border-slate-800 flex flex-wrap gap-0.5">
<button
onClick={() => { setActiveProfile("scifi"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "scifi" ? "bg-cyan-500 text-slate-950" : "text-slate-400 hover:text-white"}`}
>
Sci-Fi & Fantasy (Realname)
Sci-Fi (Daniel Hesse)
</button>
<button
onClick={() => { setActiveProfile("erotica"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "erotica" ? "bg-rose-800 text-white" : "text-slate-400 hover:text-white"}`}
>
Erotik (Marc Velvet)
Erotik ({authorData?.erotica?.name || "Annie Slone"})
</button>
<button
onClick={() => { setActiveProfile("clara"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "clara" ? "bg-emerald-700 text-white" : "text-slate-400 hover:text-white"}`}
>
Naturlyrik (Clara Finch)
Naturlyrik ({authorData?.clara?.name || "Clara Finch"})
</button>
<button
onClick={() => { setActiveProfile("renee"); setActiveTab("profile"); }}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold cursor-pointer transition-all ${activeProfile === "renee" ? "bg-yellow-500 text-slate-950" : "text-slate-400 hover:text-white"}`}
>
Romantik ({authorData?.renee?.name || "Renee Heart"})
</button>
</div>
@ -472,6 +479,20 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
<Compass className="w-4 h-4" />
<span>Profil & Biografie info</span>
</button>
<button
onClick={() => setActiveTab("texts")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "texts" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Type className="w-4 h-4" />
<span>Texte & Überschriften</span>
</button>
<button
onClick={() => setActiveTab("design")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "design" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
>
<Palette className="w-4 h-4" />
<span>Design & Farben</span>
</button>
<button
onClick={() => setActiveTab("projects")}
className={`px-3 py-2.5 rounded-xl text-left text-xs font-bold transition-all flex items-center gap-2 cursor-pointer ${activeTab === "projects" ? "bg-slate-800 border-l-4 border-indigo-500 text-white" : "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
@ -573,6 +594,137 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
/>
</div>
{/* Dynamic Schlagworte / Highlights */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Tag className="w-4 h-4 text-amber-400" />
<span>Schlagworte & Fokus (3 Infoboxen unter "Über mich")</span>
</h4>
<p className="text-xs text-slate-500">Diese drei Kacheln erscheinen unter der Biografie. Sie können die Kategoriename (z.B. GENRE, TONALITÄT, FOKUS) und den zugehörigen Inhalt frei anpassen.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{/* Schlagwort 1 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 1</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag1Label || ""}
onChange={(e) => updateProfileField("bioTag1Label", e.target.value)}
placeholder="z.B. GENRE"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag1Value || ""}
onChange={(e) => updateProfileField("bioTag1Value", e.target.value)}
placeholder="z.B. Emotionale Romance"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
{/* Schlagwort 2 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 2</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag2Label || ""}
onChange={(e) => updateProfileField("bioTag2Label", e.target.value)}
placeholder="z.B. TONALITÄT"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag2Value || ""}
onChange={(e) => updateProfileField("bioTag2Value", e.target.value)}
placeholder="z.B. Herzlich & Hoffnungsvoll"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
{/* Schlagwort 3 */}
<div className="p-3 bg-slate-950 border border-slate-800 rounded-xl space-y-2">
<div className="text-xs font-semibold text-amber-400 uppercase flex items-center gap-1">
<span>Schlagwort 3</span>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Kategorie-Titel</label>
<input
type="text"
value={profile.bioTag3Label || ""}
onChange={(e) => updateProfileField("bioTag3Label", e.target.value)}
placeholder="z.B. FOKUS"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs"
/>
</div>
<div>
<label className="text-[10px] font-mono text-slate-400 uppercase">Inhalt / Wert</label>
<input
type="text"
value={profile.bioTag3Value || ""}
onChange={(e) => updateProfileField("bioTag3Value", e.target.value)}
placeholder="z.B. Gefühl & Tiefe"
className="w-full px-2.5 py-1.5 bg-slate-900 border border-slate-800 rounded-lg text-white text-xs font-medium"
/>
</div>
</div>
</div>
</div>
{/* Custom Domain & Routing Section */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
<h4 className="text-sm font-bold text-white flex items-center gap-2">
<Globe className="w-4 h-4 text-indigo-400" />
<span>Domain- & Routing-Einstellungen</span>
</h4>
<p className="text-xs text-slate-500">Legen Sie fest, unter welchen Domains und Pfaden dieses Profil geladen werden soll. Sobald Besucher eine dieser Domains aufrufen, zeigt die Applikation automatisch diese Autoren-Suite an.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Eigene Domains (Domain-Routing)</label>
<input
type="text"
value={profile.customDomain || ""}
onChange={(e) => updateProfileField("customDomain", e.target.value)}
placeholder="z.B. reneeheart.de, renee-heart.de"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
<p className="text-[11px] text-slate-500">Kommagetrennte Domainnamen. Bsp: <code className="text-slate-400">reneeheart.de, renee-heart.de</code></p>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Eigener Pfad-Prefix (Pfad-Routing)</label>
<input
type="text"
value={profile.customPath || ""}
onChange={(e) => updateProfileField("customPath", e.target.value)}
placeholder="z.B. /renee-heart, /renee"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
<p className="text-[11px] text-slate-500">Kommagetrennte Pfade für Direktlinks & Vorschau. Bsp: <code className="text-slate-400">/renee-heart, /renee</code></p>
</div>
</div>
</div>
{/* Custom Highlight Section */}
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
<div>
@ -595,7 +747,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Abschnitts-Inhalt (Text)</label>
<label className="text-xs font-mono text-slate-400 uppercase">Abschnitts-Inhalt (Markdown & Links unterstützt)</label>
<textarea
rows={4}
value={profile.customSectionContent || ""}
@ -610,6 +762,317 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
</div>
)}
{/* TAB 2: Static Texts & Headings */}
{activeTab === "texts" && (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white flex items-center gap-2">
<Type className="w-5 h-5 text-indigo-400" />
<span>Statische Texte & Sektionsüberschriften</span>
</h2>
<p className="text-xs text-slate-400">Passen Sie alle Beschriftungen, Sektions-Header und Fußzeilen für {profile.name} an.</p>
</div>
<button
onClick={() => handleSaveProfileInfo(activeProfile)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Texte speichern</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Header Badge-Text (kleiner Tag oben)</label>
<input
type="text"
value={profile.badgeText || ""}
onChange={(e) => updateProfileField("badgeText", e.target.value)}
placeholder="z.B. Cyber-Mystisches Archive"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
<p className="text-[10px] text-slate-500">Wird über dem Haupttitel mit feinem Glanz-Effekt angezeigt.</p>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase font-bold">"Über mich" Sektionsüberschrift</label>
<input
type="text"
value={profile.aboutTitle || ""}
onChange={(e) => updateProfileField("aboutTitle", e.target.value)}
placeholder="z.B. Über den Autor"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekte Sektionsüberschrift</label>
<input
type="text"
value={profile.projectsTitle || ""}
onChange={(e) => updateProfileField("projectsTitle", e.target.value)}
placeholder="z.B. Aktuelle Projekte"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Projekte Untertitel / Beschreibung</label>
<input
type="text"
value={profile.projectsSubtitle || ""}
onChange={(e) => updateProfileField("projectsSubtitle", e.target.value)}
placeholder="z.B. Ein exklusiver Blick auf mein Schreib-Amboss..."
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Bücherregal Sektionsüberschrift</label>
<input
type="text"
value={profile.booksTitle || ""}
onChange={(e) => updateProfileField("booksTitle", e.target.value)}
placeholder="z.B. Bücherregal"
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1">
<label className="text-xs font-mono text-slate-400 uppercase">Bücherregal Untertitel / Beschreibung</label>
<input
type="text"
value={profile.booksSubtitle || ""}
onChange={(e) => updateProfileField("booksSubtitle", e.target.value)}
placeholder="z.B. Entdecken Sie meine verlegten Werke..."
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Footer-Text (Fußzeile ganz unten)</label>
<input
type="text"
value={profile.footerText || ""}
onChange={(e) => updateProfileField("footerText", e.target.value)}
placeholder="z.B. Alle Rechte vorbehalten..."
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
/>
</div>
</div>
</div>
)}
{/* TAB 3: Design & Dynamic Theme Controls */}
{activeTab === "design" && (
<div className="p-6 bg-slate-900 border border-slate-800 rounded-2xl space-y-6">
<div className="flex items-center justify-between border-b border-slate-800 pb-4">
<div>
<h2 className="text-lg font-bold text-white flex items-center gap-2">
<Palette className="w-5 h-5 text-indigo-400" />
<span>Design, Farben & Themes</span>
</h2>
<p className="text-xs text-slate-400">Gestalten Sie das Erscheinungsbild für {profile.name} nach Ihren Wünschen.</p>
</div>
<button
onClick={() => handleSaveProfileInfo(activeProfile)}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 cursor-pointer"
>
<Save className="w-3.5 h-3.5" />
<span>Design speichern</span>
</button>
</div>
{/* Preset Theme Selection */}
<div className="space-y-3">
<label className="text-xs font-mono text-cyan-400 uppercase tracking-widest font-bold">Schnell-Paletten (Presets)</label>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#06b6d4");
updateProfileField("secondaryColor", "#a855f7");
updateProfileField("backgroundColor", "#020617");
updateProfileField("cardBgColor", "#0f172a");
updateProfileField("fontFamily", "sans");
}}
className="p-3 bg-slate-950 border border-cyan-500/40 hover:border-cyan-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#06b6d4]" />
<div className="w-4 h-4 rounded-full bg-[#a855f7]" />
<div className="w-4 h-4 rounded-full bg-[#020617] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Neon Sci-Fi</div>
<div className="text-[10px] text-slate-400">Cyan & Purple Slate</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#f97316");
updateProfileField("secondaryColor", "#f59e0b");
updateProfileField("backgroundColor", "#0a0a0a");
updateProfileField("cardBgColor", "#171717");
updateProfileField("fontFamily", "serif");
}}
className="p-3 bg-slate-950 border border-orange-500/40 hover:border-orange-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#f97316]" />
<div className="w-4 h-4 rounded-full bg-[#f59e0b]" />
<div className="w-4 h-4 rounded-full bg-[#0a0a0a] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Samt & Peach</div>
<div className="text-[10px] text-slate-400">Orange & Amber Dark</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#10b981");
updateProfileField("secondaryColor", "#84cc16");
updateProfileField("backgroundColor", "#064e3b");
updateProfileField("cardBgColor", "#022c22");
updateProfileField("fontFamily", "serif");
}}
className="p-3 bg-slate-950 border border-emerald-500/40 hover:border-emerald-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#10b981]" />
<div className="w-4 h-4 rounded-full bg-[#84cc16]" />
<div className="w-4 h-4 rounded-full bg-[#064e3b] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Wald & Salbei</div>
<div className="text-[10px] text-slate-400">Emerald & Lime Forest</div>
</button>
<button
type="button"
onClick={() => {
updateProfileField("accentColor", "#ec4899");
updateProfileField("secondaryColor", "#8b5cf6");
updateProfileField("backgroundColor", "#09090b");
updateProfileField("cardBgColor", "#18181b");
updateProfileField("fontFamily", "sans");
}}
className="p-3 bg-slate-950 border border-pink-500/40 hover:border-pink-400 rounded-xl text-left space-y-2 cursor-pointer transition-all"
>
<div className="flex gap-1.5">
<div className="w-4 h-4 rounded-full bg-[#ec4899]" />
<div className="w-4 h-4 rounded-full bg-[#8b5cf6]" />
<div className="w-4 h-4 rounded-full bg-[#09090b] border border-slate-700" />
</div>
<div className="text-xs font-bold text-white">Obsidian Magenta</div>
<div className="text-[10px] text-slate-400">Pink & Violet Dark</div>
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-2">
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Haupt-Akzentfarbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.accentColor || "#06b6d4"}
onChange={(e) => updateProfileField("accentColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.accentColor || "#06b6d4"}
onChange={(e) => updateProfileField("accentColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Sekundär-Farbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.secondaryColor || "#a855f7"}
onChange={(e) => updateProfileField("secondaryColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.secondaryColor || "#a855f7"}
onChange={(e) => updateProfileField("secondaryColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Hintergrundfarbe (Seiten-Canvas)</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.backgroundColor || "#020617"}
onChange={(e) => updateProfileField("backgroundColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.backgroundColor || "#020617"}
onChange={(e) => updateProfileField("backgroundColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Karten-Hintergrundfarbe</label>
<div className="flex gap-2 items-center">
<input
type="color"
value={profile.cardBgColor || "#0f172a"}
onChange={(e) => updateProfileField("cardBgColor", e.target.value)}
className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1"
/>
<input
type="text"
value={profile.cardBgColor || "#0f172a"}
onChange={(e) => updateProfileField("cardBgColor", e.target.value)}
className="flex-1 px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm font-mono"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-mono text-slate-400 uppercase">Schriftart / Typografie</label>
<select
value={profile.fontFamily || "sans"}
onChange={(e) => updateProfileField("fontFamily", e.target.value as any)}
className="w-full px-3 py-2 bg-slate-950 border border-slate-800 rounded-xl text-white outline-none focus:border-indigo-500 text-sm"
>
<option value="sans">Moderne Sans-Serif (Standard)</option>
<option value="serif">Klassische Serif (Elegante Buchschrift)</option>
<option value="mono">Futuristische Monospace (Code/Tech)</option>
</select>
</div>
<div className="space-y-2 md:col-span-2 pt-2">
<label className="text-xs font-mono text-slate-400 uppercase font-bold text-white flex items-center gap-2">
<ImageIcon className="w-4 h-4 text-cyan-400" />
<span>Hero-Hintergrundbild (optional)</span>
</label>
<ImagePicker
value={profile.heroBannerUrl || ""}
onChange={(url) => updateProfileField("heroBannerUrl", url)}
token={token}
label="Hero Banner-Bild"
/>
<p className="text-[10px] text-slate-500">Wenn angegeben, wird dieses Bild als stimmungsvoller Hintergrund im oberen Bereich der Webseite hinterlegt.</p>
</div>
</div>
</div>
)}
{/* TAB 2: Projects manager */}
{activeTab === "projects" && (
<div className="space-y-6">
@ -720,7 +1183,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
/>
</div>
<div className="space-y-1 md:col-span-2">
<label className="text-xs font-mono text-slate-400 uppercase">Kurzbeschreibung / Vorschau</label>
<label className="text-xs font-mono text-slate-400 uppercase">Kurzbeschreibung / Vorschau (Markdown & Links unterstützt)</label>
<textarea
rows={3}
value={projectForm.description || ""}

View file

@ -11,48 +11,69 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const accentColor = data.accentColor || "#8FA797";
const secondaryColor = data.secondaryColor || "#B59EB1";
const bgColor = data.backgroundColor || "#0c0a09";
const cardBgColor = data.cardBgColor || "#1c1917";
const fontClass = data.fontFamily === "sans" ? "font-sans" : data.fontFamily === "mono" ? "font-mono" : "font-serif";
return (
<div className="min-h-screen bg-stone-950 text-stone-200 font-sans selection:bg-[#B39EAF]/30 selection:text-white overflow-x-hidden relative">
{/* Delicate organic gradients - Sage (Salbei) and Mauve (Malve) */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-[#4A5D50]/15 via-stone-950 to-stone-950 -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-[#8FA797]/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute bottom-1/3 right-1/3 w-96 h-96 bg-[#B59EB1]/5 rounded-full blur-3xl pointer-events-none" />
<div
className={`min-h-screen text-stone-200 ${fontClass} selection:bg-[#B39EAF]/30 selection:text-white overflow-x-hidden relative`}
style={{ backgroundColor: bgColor }}
>
{/* Hero Banner Background if present */}
{data.heroBannerUrl && (
<div className="absolute top-0 inset-x-0 h-96 overflow-hidden pointer-events-none -z-5 opacity-20">
<img src={data.heroBannerUrl} alt="" className="w-full h-full object-cover blur-sm" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-stone-950" style={{ backgroundImage: `linear-gradient(to bottom, transparent, ${bgColor})` }} />
</div>
)}
{/* Delicate organic gradients */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-stone-900/10 via-transparent to-transparent -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-15" style={{ backgroundColor: accentColor }} />
<div className="absolute bottom-1/3 right-1/3 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-15" style={{ backgroundColor: secondaryColor }} />
{/* Header / Hero */}
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-[#4A5D50]/20">
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-stone-800/80">
<div className="text-center max-w-4xl mx-auto space-y-6">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-[#4A5D50]/10 border border-[#8FA797]/20 rounded-full text-[#8FA797] text-xs tracking-wider mb-2">
<Leaf className="w-3.5 h-3.5 text-[#8FA797] animate-pulse" />
<span className="font-serif italic text-stone-300">Lyrik & Naturphilosophie</span>
<div
className="inline-flex items-center gap-2 px-4 py-1.5 bg-stone-900/80 border rounded-full text-xs tracking-wider mb-2 shadow-sm"
style={{ borderColor: `${accentColor}40`, color: accentColor }}
>
<Leaf className="w-3.5 h-3.5 animate-pulse" style={{ color: accentColor }} />
<span className="font-serif italic text-stone-300">{data.badgeText || "Lyrik & Naturphilosophie"}</span>
</div>
<h1 className="text-4xl md:text-6xl font-serif tracking-widest text-stone-100 uppercase font-light drop-shadow-[0_2px_10px_rgba(143,167,151,0.1)]">
{data.heroTitle || "CLARA FINCH"}
<h1 className="text-4xl md:text-6xl font-serif tracking-widest text-stone-100 uppercase font-light drop-shadow-sm">
{data.heroTitle || data.name || "CLARA FINCH"}
</h1>
<p className="text-[#B59EB1] font-sans text-xs md:text-sm tracking-[0.4em] uppercase">
<p className="font-sans text-xs md:text-sm tracking-[0.4em] uppercase" style={{ color: secondaryColor }}>
{data.heroSubtitle || "LYRIK, ESSAYS & NATURPHILOSOPHIE"}
</p>
<div className="h-[1px] w-36 bg-gradient-to-r from-transparent via-[#8FA797]/55 via-[#B59EB1]/50 to-transparent mx-auto mt-6" />
<div className="h-[1px] w-36 mx-auto mt-6" style={{ background: `linear-gradient(to right, transparent, ${accentColor}, ${secondaryColor}, transparent)` }} />
{/* Quick Section Navigation (Sage & Mauve Styled) */}
{/* Quick Section Navigation */}
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-sans">
<a
href="#ueber-mich"
className="px-4 py-2 rounded-full border border-[#4A5D50]/30 bg-[#4A5D50]/10 hover:border-[#8FA797] hover:text-[#8FA797] transition-all text-stone-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-stone-800 bg-stone-900/60 hover:border-stone-500 transition-all text-stone-300 uppercase tracking-wider"
>
🌿 Über mich
🌿 {data.aboutTitle || "Über mich"}
</a>
<a
href="#projekte"
className="px-4 py-2 rounded-full border border-[#4A5D50]/30 bg-[#4A5D50]/10 hover:border-[#8FA797] hover:text-[#8FA797] transition-all text-stone-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-stone-800 bg-stone-900/60 hover:border-stone-500 transition-all text-stone-300 uppercase tracking-wider"
>
✍️ Notizbücher
✍️ {data.projectsTitle || "Notizbücher"}
</a>
<a
href="#buecherregal"
className="px-4 py-2 rounded-full border border-[#4A5D50]/30 bg-[#4A5D50]/10 hover:border-[#8FA797] hover:text-[#8FA797] transition-all text-stone-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-stone-800 bg-stone-900/60 hover:border-stone-500 transition-all text-stone-300 uppercase tracking-wider"
>
📖 Gedruckte Werke
📖 {data.booksTitle || "Gedruckte Werke"}
</a>
</nav>
</div>
@ -66,8 +87,8 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
<div className="md:col-span-4 flex justify-center">
<div className="relative group">
{/* Outer soft natural glow */}
<div className="absolute -inset-1.5 bg-gradient-to-r from-[#8FA797] to-[#B59EB1] rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-1000 group-hover:duration-200"></div>
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-[#4A5D50]/30 bg-stone-900">
<div className="absolute -inset-1.5 rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-1000 group-hover:duration-200" style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }} />
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-stone-800" style={{ backgroundColor: cardBgColor }}>
<img
src={data.avatarUrl || "https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=400"}
alt={data.name}
@ -79,48 +100,54 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
</div>
<div className="md:col-span-8 space-y-6">
<h2 className="text-2xl md:text-3xl font-serif text-stone-100 flex items-center gap-3">
<Feather className="w-6 h-6 text-[#8FA797]" />
<span>Normal ist das, was heilt.</span>
<Feather className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.aboutTitle || "Das Atmen zwischen den Zeilen"}</span>
</h2>
<div className="prose prose-invert prose-stone max-w-none text-stone-350 text-stone-300 leading-relaxed font-sans space-y-4">
<div className="prose prose-invert prose-stone max-w-none text-stone-300 leading-relaxed font-sans space-y-4">
<p className="whitespace-pre-wrap italic">{data.bio}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 text-xs">
<div className="p-3 bg-stone-900/50 border border-stone-900 rounded-xl flex items-center gap-3">
<Leaf className="w-5 h-5 text-[#8FA797]" />
{(data.bioTag1Label || data.bioTag1Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Leaf className="w-5 h-5 shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Genre</div>
<div className="text-stone-200 font-semibold">Deep Romance & Contemporary</div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag1Label || "Genre"}</div>
<div className="text-stone-200 font-semibold">{data.bioTag1Value || "Lyrik & Essays"}</div>
</div>
</div>
<div className="p-3 bg-stone-900/50 border border-stone-900 rounded-xl flex items-center gap-3">
<Sparkles className="w-5 h-5 text-[#B59EB1]" />
)}
{(data.bioTag2Label || data.bioTag2Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Sparkles className="w-5 h-5 shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Ästhetik</div>
<div className="text-stone-200 font-semibold">Cozy, analog & geerdet</div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag2Label || "Ästhetik"}</div>
<div className="text-stone-200 font-semibold">{data.bioTag2Value || "Natur & Poesie"}</div>
</div>
</div>
<div className="p-3 bg-stone-900/50 border border-stone-900 rounded-xl flex items-center gap-3 col-span-2 md:col-span-1">
<Feather className="w-5 h-5 text-[#8FA797]" />
)}
{(data.bioTag3Label || data.bioTag3Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3 col-span-2 md:col-span-1" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Feather className="w-5 h-5 shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Fokus</div>
<div className="text-stone-200 font-semibold">Heilung, Akzeptanz & Safe Spaces</div>
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag3Label || "Fokus"}</div>
<div className="text-stone-200 font-semibold">{data.bioTag3Value || "Naturkontemplation"}</div>
</div>
</div>
)}
</div>
</div>
</section>
{/* Dynamic ongoing work writing progress */}
<section id="projekte" className="space-y-8">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-[#4A5D50]/20 pb-4">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-stone-800 pb-4">
<div>
<h2 className="text-2xl md:text-3xl font-serif text-stone-100 flex items-center gap-3">
<Feather className="w-6 h-6 text-[#8FA797]" />
<span>Aus den Notizbüchern</span>
<Feather className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.projectsTitle || "Aus den Notizbüchern"}</span>
</h2>
<p className="text-stone-400 text-sm mt-1">Gedanken im Entwurf, Fragmente und Sammlungen, die bald das Licht der Welt erblicken.</p>
<p className="text-stone-400 text-sm mt-1">{data.projectsSubtitle || "Gedanken im Entwurf, Fragmente und Sammlungen, die bald das Licht der Welt erblicken."}</p>
</div>
</div>
@ -129,33 +156,47 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
<button
key={project.id}
onClick={() => setSelectedProject(project)}
className="p-6 bg-[#4A5D50]/5 border border-[#4A5D50]/20 hover:border-[#8FA797]/40 rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:shadow-[0_0_20px_rgba(143,167,151,0.15)] focus:outline-none"
className="p-6 border rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:scale-[1.01] focus:outline-none shadow-md"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}
>
<div className="space-y-4 w-full">
<div className="flex items-center justify-between">
<span className="text-[10px] uppercase font-sans text-[#8FA797] bg-[#4A5D50]/20 px-2.5 py-1 rounded border border-[#8FA797]/20 tracking-wider">
<span className="text-[10px] uppercase font-sans px-2.5 py-1 rounded border tracking-wider" style={{ color: accentColor, backgroundColor: `${accentColor}15`, borderColor: `${accentColor}30` }}>
{project.status || "In Arbeit"}
</span>
<span className="text-xs text-stone-400 italic">
Reifegrad: {project.progress}%
</span>
</div>
<h3 className="text-lg font-serif text-stone-100 group-hover:text-[#B59EB1] transition-colors">
<h3 className="text-lg font-serif text-stone-100 group-hover:text-stone-300 transition-colors">
{project.title}
</h3>
<p className="text-stone-300 text-sm italic line-clamp-3">
<div className="text-stone-300 text-sm italic line-clamp-3 markdown-body [&_p]:inline [&_p]:m-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-[#8FA797] hover:text-stone-200 font-medium"
onClick={(e) => e.stopPropagation()}
{...props}
/>
)
}}
>
{project.description}
</p>
<p className="text-[#8FA797] font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic">
</Markdown>
</div>
<p className="font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic" style={{ color: accentColor }}>
Details einsehen <ArrowRight className="w-3 h-3" />
</p>
</div>
{/* Sage & Mauve loading progress meter */}
<div className="mt-6 space-y-1.5 w-full">
<div className="h-1 w-full bg-stone-900 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-[#4A5D50] via-[#8FA797] to-[#B59EB1] rounded-full transition-all duration-1000 ease-out shadow-[0_0_8px_rgba(143,167,151,0.3)]"
style={{ width: `${project.progress}%` }}
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
</div>
</div>
@ -166,12 +207,12 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
{/* Bookshelf display */}
<section id="buecherregal" className="space-y-8">
<div className="border-b border-[#4A5D50]/20 pb-4">
<div className="border-b border-stone-800 pb-4">
<h2 className="text-2xl md:text-3xl font-serif text-stone-100 flex items-center gap-3">
<BookOpen className="w-6 h-6 text-[#8FA797]" />
<span>Gedruckte Werke</span>
<BookOpen className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.booksTitle || "Gedruckte Werke"}</span>
</h2>
<p className="text-stone-400 text-sm mt-1">Erschlossene Wortlandschaften. Wählen Sie einen Band, um tiefer hineinzuhören.</p>
<p className="text-stone-400 text-sm mt-1">{data.booksSubtitle || "Erschlossene Wortlandschaften. Wählen Sie einen Band, um tiefer hineinzuhören."}</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
@ -179,9 +220,10 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
<button
key={book.id}
onClick={() => setSelectedBook(book)}
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-lg overflow-hidden border border-stone-900 bg-stone-900 hover:border-[#8FA797]/50 transition-all duration-300 hover:shadow-[0_0_20px_rgba(143,167,151,0.15)] cursor-pointer"
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-xl overflow-hidden border transition-all duration-300 hover:shadow-xl cursor-pointer"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}
>
<div className="absolute inset-0 bg-[#4A5D50]/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
<div className="absolute inset-0 bg-stone-900/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
<img
src={book.coverUrl || "https://images.unsplash.com/photo-1501504905252-473c47e087f8?auto=format&fit=crop&q=80&w=300"}
alt={book.title}
@ -189,29 +231,42 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
referrerPolicy="no-referrer"
/>
{/* Readable, beautiful overlay on top of cover, slightly translucent, fully visible on hover */}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-stone-950 via-stone-950/90 to-transparent p-4 opacity-80 group-hover:opacity-100 transition-all duration-350 z-20">
<p className="text-[#E6E0E5] text-xs font-serif truncate">{book.title}</p>
<p className="text-[#8FA797] text-[10px] font-sans mt-0.5 italic">Verweilen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
<p className="text-stone-100 text-xs font-serif truncate">{book.title}</p>
<p className="text-[10px] font-sans mt-0.5 italic" style={{ color: accentColor }}>Verweilen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
</div>
</button>
))}
</div>
</section>
{/* Customizable Custom Section */}
{data.customSectionContent && data.customSectionContent.trim() !== "" && (
<section id="garten-notiz" className="space-y-6 pt-8 border-t border-[#4A5D50]/20">
<div className="bg-gradient-to-br from-[#4A5D50]/10 to-stone-900/40 p-8 rounded-2xl border border-[#4A5D50]/30 max-w-3xl mx-auto text-center space-y-4">
{/* Custom Highlight Section if present */}
{(data.customSectionTitle || data.customSectionContent) && (
<section id="garten-notiz" className="p-8 rounded-2xl border relative overflow-hidden max-w-4xl mx-auto shadow-xl" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
{data.customSectionTitle && (
<h3 className="text-xl md:text-2xl font-serif text-stone-100">
{data.customSectionTitle}
</h3>
<h2 className="text-2xl font-serif text-stone-100 mb-4 flex items-center gap-2">
<Sparkles className="w-5 h-5" style={{ color: accentColor }} />
<span>{data.customSectionTitle}</span>
</h2>
)}
<p className="text-stone-300 text-sm md:text-base leading-relaxed italic whitespace-pre-wrap">
{data.customSectionContent && (
<div className="text-stone-300 text-sm md:text-base leading-relaxed italic markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-[#8FA797] hover:text-stone-200 font-medium"
{...props}
/>
)
}}
>
{data.customSectionContent}
</p>
</Markdown>
</div>
)}
</section>
)}
@ -385,10 +440,10 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
)}
{/* Footer */}
<footer className="bg-stone-950 border-t border-[#4A5D50]/20 py-12 px-4 text-center text-xs text-stone-500 italic">
<footer className="border-t border-stone-800 py-12 px-4 text-center text-xs text-stone-500 italic" style={{ backgroundColor: bgColor }}>
<div className="max-w-7xl mx-auto space-y-2">
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
<p className="text-stone-600">Betrachtungen, verwoben mit dem Rhythmus der wachsenden Natur.</p>
<p className="text-stone-500">{data.footerText || "Betrachtungen, verwoben mit dem Rhythmus der wachsenden Natur."}</p>
</div>
</footer>
</div>

View file

@ -11,48 +11,69 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const accentColor = data.accentColor || "#f97316";
const secondaryColor = data.secondaryColor || "#f59e0b";
const bgColor = data.backgroundColor || "#0a0a0a";
const cardBgColor = data.cardBgColor || "#171717";
const fontClass = data.fontFamily === "sans" ? "font-sans" : data.fontFamily === "mono" ? "font-mono" : "font-serif";
return (
<div className="min-h-screen bg-neutral-950 text-neutral-200 font-sans selection:bg-orange-450 selection:bg-orange-500 selection:text-black overflow-x-hidden relative">
<div
className={`min-h-screen text-neutral-200 ${fontClass} selection:bg-orange-500 selection:text-black overflow-x-hidden relative`}
style={{ backgroundColor: bgColor }}
>
{/* Hero Banner Background if present */}
{data.heroBannerUrl && (
<div className="absolute top-0 inset-x-0 h-96 overflow-hidden pointer-events-none -z-5 opacity-20">
<img src={data.heroBannerUrl} alt="" className="w-full h-full object-cover blur-sm" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-neutral-950" style={{ backgroundImage: `linear-gradient(to bottom, transparent, ${bgColor})` }} />
</div>
)}
{/* Decorative Warm background gradient and peach glow vibes */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-amber-950/20 via-neutral-950 to-neutral-950 -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/3 w-96 h-96 bg-orange-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute bottom-1/3 right-1/4 w-96 h-96 bg-amber-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-amber-950/20 via-transparent to-transparent -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/3 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20" style={{ backgroundColor: accentColor }} />
<div className="absolute bottom-1/3 right-1/4 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20" style={{ backgroundColor: secondaryColor }} />
{/* Header / Hero */}
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-orange-950/40">
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-neutral-800/80">
<div className="text-center max-w-4xl mx-auto space-y-6">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-orange-950/30 border border-orange-900/40 rounded-full text-orange-300 text-xs tracking-wider mb-2">
<Heart className="w-3.5 h-3.5 text-orange-400 fill-orange-400/20 animate-pulse" />
<span className="font-sans italic">Stilvolle Sinnlichkeit & Romantik</span>
<div
className="inline-flex items-center gap-2 px-4 py-1.5 bg-neutral-900/80 border rounded-full text-xs tracking-wider mb-2 shadow-sm"
style={{ borderColor: `${accentColor}40`, color: accentColor }}
>
<Heart className="w-3.5 h-3.5 fill-current animate-pulse" style={{ color: accentColor }} />
<span>{data.badgeText || "Stilvolle Sinnlichkeit & Romantik"}</span>
</div>
<h1 className="text-4xl md:text-6xl font-serif tracking-wide text-amber-100 uppercase font-light drop-shadow-[0_2px_10px_rgba(249,115,22,0.1)]">
{data.heroTitle || "M. S. VELVET"}
<h1 className="text-4xl md:text-6xl tracking-wide text-amber-100 uppercase font-light drop-shadow-sm font-serif">
{data.heroTitle || data.name || "M. S. VELVET"}
</h1>
<p className="text-orange-400 font-sans text-xs md:text-sm tracking-[0.3em] uppercase">
<p className="font-sans text-xs md:text-sm tracking-[0.3em] uppercase" style={{ color: accentColor }}>
{data.heroSubtitle || "ELEGANT-SINNLICHE LITERATUR"}
</p>
<div className="h-[1px] w-36 bg-gradient-to-r from-transparent via-orange-450/60 via-orange-400 to-transparent mx-auto mt-6" />
<div className="h-[1px] w-36 mx-auto mt-6" style={{ background: `linear-gradient(to right, transparent, ${accentColor}, transparent)` }} />
{/* Quick Section Navigation (Peach Styled) */}
{/* Quick Section Navigation */}
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-sans">
<a
href="#ueber-mich"
className="px-4 py-2 rounded-full border border-orange-950/60 bg-orange-950/20 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-neutral-800 bg-neutral-900/60 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
>
🍑 Über mich
🍑 {data.aboutTitle || "Über mich"}
</a>
<a
href="#projekte"
className="px-4 py-2 rounded-full border border-orange-950/60 bg-orange-950/20 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-neutral-800 bg-neutral-900/60 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
>
✍️ In der Entstehung
✍️ {data.projectsTitle || "In der Entstehung"}
</a>
<a
href="#buecherregal"
className="px-4 py-2 rounded-full border border-orange-950/60 bg-orange-950/20 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-neutral-800 bg-neutral-900/60 hover:border-orange-400 hover:text-orange-300 transition-all text-neutral-300 uppercase tracking-wider"
>
📖 Das Samtregal
📖 {data.booksTitle || "Das Samtregal"}
</a>
</nav>
</div>
@ -66,8 +87,8 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
<div className="md:col-span-4 flex justify-center">
<div className="relative group">
{/* Outer warm glow */}
<div className="absolute -inset-1.5 bg-gradient-to-r from-orange-500 to-amber-600 rounded-2xl blur opacity-25 group-hover:opacity-60 transition duration-1000 group-hover:duration-200"></div>
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-orange-950 bg-neutral-900">
<div className="absolute -inset-1.5 rounded-2xl blur opacity-25 group-hover:opacity-60 transition duration-1000 group-hover:duration-200" style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }} />
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-neutral-800" style={{ backgroundColor: cardBgColor }}>
<img
src={data.avatarUrl || "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400"}
alt={data.name}
@ -79,48 +100,54 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
</div>
<div className="md:col-span-8 space-y-6">
<h2 className="text-2xl md:text-3xl font-serif text-amber-100 flex items-center gap-3">
<Sparkles className="w-6 h-6 text-orange-400" />
<span>Zwischen Spielzeugautos und der Stille der Nacht...</span>
<Sparkles className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.aboutTitle || "Das Geheimnis hinter der Feder"}</span>
</h2>
<div className="prose prose-invert prose-stone max-w-none text-neutral-300 leading-relaxed font-sans space-y-4">
<p className="whitespace-pre-wrap italic">{data.bio}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 text-xs">
<div className="p-3 bg-neutral-900/50 border border-neutral-900 rounded-xl flex items-center gap-3">
<Heart className="w-5 h-5 text-orange-450 text-orange-400 fill-orange-400/10" />
{(data.bioTag1Label || data.bioTag1Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Heart className="w-5 h-5 fill-current shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Genre</div>
<div className="text-neutral-200 font-semibold">Spicy Romance & Slice of Life</div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag1Label || "Genre"}</div>
<div className="text-neutral-200 font-semibold">{data.bioTag1Value || "Sinnliche Romance"}</div>
</div>
</div>
<div className="p-3 bg-neutral-900/50 border border-neutral-900 rounded-xl flex items-center gap-3">
<Sparkles className="w-5 h-5 text-amber-400" />
)}
{(data.bioTag2Label || data.bioTag2Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Sparkles className="w-5 h-5 shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Ästhetik</div>
<div className="text-neutral-200 font-semibold">Hautnah, cozy & ungefiltert</div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag2Label || "Tonalität"}</div>
<div className="text-neutral-200 font-semibold">{data.bioTag2Value || "Elegant & Knisternd"}</div>
</div>
</div>
<div className="p-3 bg-neutral-900/50 border border-neutral-900 rounded-xl flex items-center gap-3 col-span-2 md:col-span-1">
<Key className="w-5 h-5 text-orange-300" />
)}
{(data.bioTag3Label || data.bioTag3Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3 col-span-2 md:col-span-1" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Key className="w-5 h-5 shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Fokus</div>
<div className="text-neutral-200 font-semibold">Bedingungsloses Vertrauen & Intime Facetten</div>
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag3Label || "Identität"}</div>
<div className="text-neutral-200 font-semibold">{data.bioTag3Value || "Diskret / Pseudonym"}</div>
</div>
</div>
)}
</div>
</div>
</section>
{/* Dynamic ongoing work writing progress */}
<section id="projekte" className="space-y-8">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-orange-950/20 pb-4">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-neutral-800 pb-4">
<div>
<h2 className="text-2xl md:text-3xl font-serif text-amber-100 flex items-center gap-3">
<Heart className="w-6 h-6 text-orange-400" />
<span>In der Entstehung...</span>
<Heart className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.projectsTitle || "In der Entstehung..."}</span>
</h2>
<p className="text-neutral-400 text-sm mt-1">Ein diskreter Blick hinter die Samtvorhänge meiner aktuellen Manuskripte.</p>
<p className="text-neutral-400 text-sm mt-1">{data.projectsSubtitle || "Ein diskreter Blick hinter die Samtvorhänge meiner aktuellen Manuskripte."}</p>
</div>
</div>
@ -129,11 +156,12 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
<button
key={project.id}
onClick={() => setSelectedProject(project)}
className="p-6 bg-orange-950/10 border border-orange-950/20 hover:border-amber-500/40 rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:shadow-[0_0_20px_rgba(249,115,22,0.15)] focus:outline-none"
className="p-6 border rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:scale-[1.01] focus:outline-none shadow-lg"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}25` }}
>
<div className="space-y-4 w-full">
<div className="flex items-center justify-between">
<span className="text-[10px] uppercase font-sans text-amber-400 bg-amber-950/40 px-2.5 py-1 rounded border border-amber-900/30 tracking-wider">
<span className="text-[10px] uppercase font-sans px-2.5 py-1 rounded border tracking-wider" style={{ color: accentColor, backgroundColor: `${accentColor}15`, borderColor: `${accentColor}30` }}>
{project.status || "In Arbeit"}
</span>
<span className="text-xs text-neutral-400 italic">
@ -143,19 +171,32 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
<h3 className="text-lg font-serif text-amber-100 group-hover:text-orange-400 transition-colors">
{project.title}
</h3>
<p className="text-neutral-300 text-sm italic line-clamp-3">
<div className="text-neutral-300 text-sm italic line-clamp-3 markdown-body [&_p]:inline [&_p]:m-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-orange-400 hover:text-amber-300 font-medium"
onClick={(e) => e.stopPropagation()}
{...props}
/>
)
}}
>
{project.description}
</p>
<p className="text-orange-400 font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic">
</Markdown>
</div>
<p className="font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic" style={{ color: accentColor }}>
Details einsehen <ArrowRight className="w-3 h-3" />
</p>
</div>
{/* Luxurious peach-golden loading progress meter */}
<div className="mt-6 space-y-1.5 w-full">
<div className="h-1 w-full bg-neutral-900 rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-orange-500 via-amber-400 to-yellow-300 rounded-full transition-all duration-1000 ease-out shadow-[0_0_8px_rgba(249,115,22,0.3)]"
style={{ width: `${project.progress}%` }}
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
</div>
</div>
@ -164,14 +205,14 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
</div>
</section>
{/* Bookshelf display, strictly elegant and minimalist */}
{/* Bookshelf display */}
<section id="buecherregal" className="space-y-8">
<div className="border-b border-orange-950/20 pb-4">
<div className="border-b border-neutral-800 pb-4">
<h2 className="text-2xl md:text-3xl font-serif text-amber-100 flex items-center gap-3">
<BookOpen className="w-6 h-6 text-orange-400" />
<span>Das Samtregal</span>
<BookOpen className="w-6 h-6" style={{ color: secondaryColor }} />
<span>{data.booksTitle || "Das Samtregal"}</span>
</h2>
<p className="text-neutral-400 text-sm mt-1">Wählen Sie ein Buch aus, um tiefer in die Versuchung einzutauchen.</p>
<p className="text-neutral-400 text-sm mt-1">{data.booksSubtitle || "Wählen Sie ein Buch aus, um tiefer in die Versuchung einzutauchen."}</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
@ -179,7 +220,8 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
<button
key={book.id}
onClick={() => setSelectedBook(book)}
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-lg overflow-hidden border border-neutral-900 bg-neutral-900 hover:border-orange-400/50 transition-all duration-300 hover:shadow-[0_0_20px_rgba(249,115,22,0.15)] cursor-pointer"
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-xl overflow-hidden border transition-all duration-300 hover:shadow-xl cursor-pointer"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}
>
<div className="absolute inset-0 bg-orange-950/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
<img
@ -189,29 +231,42 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
referrerPolicy="no-referrer"
/>
{/* Sensual overlay - elegant translucent readable overlay, fully visible on hover */}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-neutral-950 via-neutral-950/90 to-transparent p-4 opacity-80 group-hover:opacity-100 transition-all duration-350 z-20">
<p className="text-amber-100 text-xs font-serif truncate">{book.title}</p>
<p className="text-orange-400 text-[10px] font-sans mt-0.5 italic">Eintauchen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
<p className="text-[10px] font-sans mt-0.5 italic" style={{ color: accentColor }}>Eintauchen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
</div>
</button>
))}
</div>
</section>
{/* Customizable Custom Section */}
{data.customSectionContent && data.customSectionContent.trim() !== "" && (
<section id="exklusiv-notiz" className="space-y-6 pt-8 border-t border-orange-950/20">
<div className="bg-gradient-to-br from-orange-950/20 to-neutral-900/40 p-8 rounded-2xl border border-orange-950/40 max-w-3xl mx-auto text-center space-y-4">
{/* Custom Highlight Section if present */}
{(data.customSectionTitle || data.customSectionContent) && (
<section id="exklusiv-notiz" className="p-8 rounded-2xl border relative overflow-hidden max-w-4xl mx-auto shadow-xl" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
{data.customSectionTitle && (
<h3 className="text-xl md:text-2xl font-serif text-amber-100">
{data.customSectionTitle}
</h3>
<h2 className="text-2xl font-serif text-amber-100 mb-4 flex items-center gap-2">
<Sparkles className="w-5 h-5" style={{ color: accentColor }} />
<span>{data.customSectionTitle}</span>
</h2>
)}
<p className="text-neutral-300 text-sm md:text-base leading-relaxed italic whitespace-pre-wrap">
{data.customSectionContent && (
<div className="text-neutral-300 text-sm md:text-base leading-relaxed italic markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-orange-400 hover:text-amber-300 font-medium"
{...props}
/>
)
}}
>
{data.customSectionContent}
</p>
</Markdown>
</div>
)}
</section>
)}
@ -385,10 +440,10 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
)}
{/* Footer */}
<footer className="bg-neutral-950 border-t border-orange-950/20 py-12 px-4 text-center text-xs text-neutral-500 italic">
<footer className="border-t border-neutral-800 py-12 px-4 text-center text-xs text-neutral-500 italic" style={{ backgroundColor: bgColor }}>
<div className="max-w-7xl mx-auto space-y-2">
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
<p className="text-neutral-600">Geschlossenes Refugium für feine, hochästhetische Phantasiereisen.</p>
<p className="text-neutral-500">{data.footerText || "Geschlossenes Refugium für feine, hochästhetische Phantasiereisen."}</p>
</div>
</footer>
</div>

View file

@ -0,0 +1,439 @@
import React, { useState } from "react";
import Markdown from "react-markdown";
import { AuthorProfile, Book, Project } from "../types";
import {
Heart, Sparkles, BookOpen, ExternalLink, X, ArrowRight, Sun, Music
} from "lucide-react";
interface ReneePortfolioProps {
data: AuthorProfile;
}
export default function ReneePortfolio({ data }: ReneePortfolioProps) {
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const accentColor = data.accentColor || "#eab308";
const secondaryColor = data.secondaryColor || "#ec4899";
const bgColor = data.backgroundColor || "#0f172a";
const cardBgColor = data.cardBgColor || "#1e293b";
const fontClass = data.fontFamily === "serif" ? "font-serif" : data.fontFamily === "mono" ? "font-mono" : "font-sans";
return (
<div
className={`min-h-screen text-slate-100 ${fontClass} selection:bg-pink-500 selection:text-white overflow-x-hidden relative`}
style={{ backgroundColor: bgColor }}
>
{/* Hero Banner Background if present */}
{data.heroBannerUrl && (
<div className="absolute top-0 inset-x-0 h-96 overflow-hidden pointer-events-none -z-5 opacity-25">
<img src={data.heroBannerUrl} alt="" className="w-full h-full object-cover blur-sm" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-slate-900" style={{ backgroundImage: `linear-gradient(to bottom, transparent, ${bgColor})` }} />
</div>
)}
{/* Decorative Yellow & Pink Warm Glows */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-yellow-500/10 via-transparent to-transparent -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20" style={{ backgroundColor: accentColor }} />
<div className="absolute bottom-1/3 right-1/3 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20" style={{ backgroundColor: secondaryColor }} />
{/* Header / Hero */}
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-slate-800">
<div className="text-center max-w-4xl mx-auto space-y-6">
<div
className="inline-flex items-center gap-2 px-4 py-1.5 bg-slate-800/80 border rounded-full text-xs tracking-wider mb-2 shadow-sm"
style={{ borderColor: `${accentColor}40`, color: accentColor }}
>
<Heart className="w-3.5 h-3.5 fill-current animate-pulse" style={{ color: secondaryColor }} />
<span>{data.badgeText || "Herzklopfen & Gefühlsstark"}</span>
</div>
<h1 className="text-4xl md:text-6xl tracking-wide text-white uppercase font-bold drop-shadow-sm">
{data.heroTitle || data.name || "RENEE HEART"}
</h1>
<p className="font-sans text-xs md:text-sm tracking-[0.3em] uppercase font-semibold" style={{ color: secondaryColor }}>
{data.heroSubtitle || "HERZENSGESCHICHTEN & EMOTIONALE ROMANE"}
</p>
<div className="h-[2px] w-36 mx-auto mt-6 rounded-full" style={{ background: `linear-gradient(to right, transparent, ${accentColor}, ${secondaryColor}, transparent)` }} />
{/* Quick Section Navigation */}
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-sans">
<a
href="#ueber-mich"
className="px-4 py-2 rounded-full border border-slate-700 bg-slate-800/60 hover:border-yellow-400 hover:text-yellow-300 transition-all text-slate-200 uppercase tracking-wider font-medium"
>
💖 {data.aboutTitle || "Über Renee Heart"}
</a>
<a
href="#projekte"
className="px-4 py-2 rounded-full border border-slate-700 bg-slate-800/60 hover:border-yellow-400 hover:text-yellow-300 transition-all text-slate-200 uppercase tracking-wider font-medium"
>
✍️ {data.projectsTitle || "Aus der Schreibstube"}
</a>
<a
href="#buecherregal"
className="px-4 py-2 rounded-full border border-slate-700 bg-slate-800/60 hover:border-yellow-400 hover:text-yellow-300 transition-all text-slate-200 uppercase tracking-wider font-medium"
>
📖 {data.booksTitle || "Gefühlvolle Romane"}
</a>
</nav>
</div>
</header>
{/* Main Content Area */}
<main className="max-w-6xl mx-auto px-4 py-16 space-y-24">
{/* Author Bio Section */}
<section id="ueber-mich" className="grid grid-cols-1 md:grid-cols-12 gap-12 items-center">
<div className="md:col-span-4 flex justify-center">
<div className="relative group">
{/* Outer yellow/pink glow */}
<div className="absolute -inset-1.5 rounded-2xl blur opacity-30 group-hover:opacity-70 transition duration-1000 group-hover:duration-200" style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }} />
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-slate-700 shadow-xl" style={{ backgroundColor: cardBgColor }}>
<img
src={data.avatarUrl || "https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=400"}
alt={data.name}
className="w-full h-full object-cover group-hover:scale-105 transition duration-500"
/>
</div>
</div>
</div>
<div className="md:col-span-8 space-y-6">
<h2 className="text-2xl md:text-3xl font-bold text-white flex items-center gap-3">
<Sparkles className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.aboutTitle || "Über Renee Heart"}</span>
</h2>
<div className="prose prose-invert max-w-none text-slate-300 leading-relaxed space-y-4">
<p className="whitespace-pre-wrap">{data.bio}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 text-xs">
{(data.bioTag1Label || data.bioTag1Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3 shadow-sm" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
<Heart className="w-5 h-5 fill-current shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-slate-400 uppercase tracking-wider text-[10px]">{data.bioTag1Label || "Genre"}</div>
<div className="text-slate-200 font-semibold">{data.bioTag1Value || "Emotionale Romance"}</div>
</div>
</div>
)}
{(data.bioTag2Label || data.bioTag2Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3 shadow-sm" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
<Sun className="w-5 h-5 shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-slate-400 uppercase tracking-wider text-[10px]">{data.bioTag2Label || "Tonalität"}</div>
<div className="text-slate-200 font-semibold">{data.bioTag2Value || "Herzlich & Hoffnungsvoll"}</div>
</div>
</div>
)}
{(data.bioTag3Label || data.bioTag3Value) && (
<div className="p-3 border rounded-xl flex items-center gap-3 col-span-2 md:col-span-1 shadow-sm" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
<Sparkles className="w-5 h-5 shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-slate-400 uppercase tracking-wider text-[10px]">{data.bioTag3Label || "Fokus"}</div>
<div className="text-slate-200 font-semibold">{data.bioTag3Value || "Gefühl & Tiefe"}</div>
</div>
</div>
)}
</div>
</div>
</section>
{/* Dynamic ongoing work writing progress */}
<section id="projekte" className="space-y-8">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-slate-800 pb-4">
<div>
<h2 className="text-2xl md:text-3xl font-bold text-white flex items-center gap-3">
<Heart className="w-6 h-6" style={{ color: secondaryColor }} />
<span>{data.projectsTitle || "Aus der Schreibstube"}</span>
</h2>
<p className="text-slate-400 text-sm mt-1">{data.projectsSubtitle || "Ein Blick auf meine aktuellen Geschichten in Entstehung – voller Hoffnung und Herzklopfen."}</p>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{data.projects.map((project) => (
<button
key={project.id}
onClick={() => setSelectedProject(project)}
className="p-6 border rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:scale-[1.01] focus:outline-none shadow-lg"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}25` }}
>
<div className="space-y-4 w-full">
<div className="flex items-center justify-between">
<span className="text-[10px] uppercase font-sans font-bold px-2.5 py-1 rounded border tracking-wider" style={{ color: accentColor, backgroundColor: `${accentColor}15`, borderColor: `${accentColor}30` }}>
{project.status || "In Arbeit"}
</span>
<span className="text-xs text-slate-400 italic">
{project.progress}% fertiggestellt
</span>
</div>
<h3 className="text-lg font-bold text-white group-hover:text-yellow-300 transition-colors">
{project.title}
</h3>
<div className="text-slate-300 text-sm line-clamp-3 markdown-body [&_p]:inline [&_p]:m-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-yellow-400 hover:text-pink-400 font-medium"
onClick={(e) => e.stopPropagation()}
{...props}
/>
)
}}
>
{project.description}
</Markdown>
</div>
<p className="font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 font-semibold" style={{ color: secondaryColor }}>
Details einsehen <ArrowRight className="w-3 h-3" />
</p>
</div>
<div className="mt-6 space-y-1.5 w-full">
<div className="h-1.5 w-full bg-slate-900 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
</div>
</div>
</button>
))}
</div>
</section>
{/* Bookshelf display */}
<section id="buecherregal" className="space-y-8">
<div className="border-b border-slate-800 pb-4">
<h2 className="text-2xl md:text-3xl font-bold text-white flex items-center gap-3">
<BookOpen className="w-6 h-6" style={{ color: accentColor }} />
<span>{data.booksTitle || "Gefühlvolle Romane"}</span>
</h2>
<p className="text-slate-400 text-sm mt-1">{data.booksSubtitle || "Geschichten zum Mitfühlen, Träumen und Genießen."}</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
{data.books.map((book) => (
<button
key={book.id}
onClick={() => setSelectedBook(book)}
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-xl overflow-hidden border transition-all duration-300 hover:shadow-2xl cursor-pointer"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}
>
<div className="absolute inset-0 bg-yellow-500/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
<img
src={book.coverUrl || "https://images.unsplash.com/photo-1512820790803-83ca734da794?auto=format&fit=crop&q=80&w=300"}
alt={book.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
referrerPolicy="no-referrer"
/>
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-slate-950 via-slate-950/90 to-transparent p-4 opacity-90 group-hover:opacity-100 transition-all duration-300 z-20">
<p className="text-white text-xs font-bold truncate">{book.title}</p>
<p className="text-[10px] font-sans mt-0.5 font-semibold flex items-center gap-1" style={{ color: secondaryColor }}>
Entdecken <ArrowRight className="inline-block w-2.5 h-2.5" />
</p>
</div>
</button>
))}
</div>
</section>
{/* Custom Highlight Section if present */}
{(data.customSectionTitle || data.customSectionContent) && (
<section id="herzenspost" className="p-8 rounded-2xl border relative overflow-hidden max-w-4xl mx-auto shadow-xl" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
{data.customSectionTitle && (
<h2 className="text-2xl font-bold text-white mb-4 flex items-center gap-2">
<Heart className="w-5 h-5" style={{ color: secondaryColor }} />
<span>{data.customSectionTitle}</span>
</h2>
)}
{data.customSectionContent && (
<div className="text-slate-300 text-sm md:text-base leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-yellow-400 hover:text-pink-400 font-medium"
{...props}
/>
)
}}
>
{data.customSectionContent}
</Markdown>
</div>
)}
</section>
)}
</main>
{/* Book Details Modal */}
{selectedBook && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md animate-fade-in">
<div
className="relative w-full max-w-2xl border rounded-2xl p-6 md:p-8 space-y-6 max-h-[90vh] overflow-y-auto shadow-2xl"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}40` }}
>
<button
onClick={() => setSelectedBook(null)}
className="absolute top-4 right-4 p-2 text-slate-400 hover:text-white bg-slate-800/80 rounded-full transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
<div className="grid grid-cols-1 md:grid-cols-12 gap-6 items-start">
<div className="md:col-span-5 aspect-[2/3] rounded-xl overflow-hidden border border-slate-700 shadow-md">
<img
src={selectedBook.coverUrl}
alt={selectedBook.title}
className="w-full h-full object-cover"
referrerPolicy="no-referrer"
/>
</div>
<div className="md:col-span-7 space-y-4">
<div>
<span className="text-xs uppercase tracking-wider font-bold" style={{ color: accentColor }}>Erscheinungsjahr {selectedBook.publishedYear}</span>
<h3 className="text-2xl font-bold text-white">{selectedBook.title}</h3>
{selectedBook.subtitle && (
<p className="text-sm text-slate-400 italic">{selectedBook.subtitle}</p>
)}
</div>
{selectedBook.genres && selectedBook.genres.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{selectedBook.genres.map((g, idx) => (
<span key={idx} className="px-2.5 py-0.5 rounded-full bg-slate-800 text-[10px] text-slate-300 border border-slate-700">
{g}
</span>
))}
</div>
)}
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-yellow-400 hover:text-pink-400 font-medium"
{...props}
/>
)
}}
>
{selectedBook.description}
</Markdown>
</div>
{/* Spotify Playlist player if configured */}
{selectedBook.spotifyPlaylistId && (
<div className="pt-2">
<div className="flex items-center gap-1.5 text-xs text-slate-400 mb-2">
<Music className="w-3.5 h-3.5 text-pink-400" />
<span>Klangwelt zum Buch auf Spotify</span>
</div>
<iframe
style={{ borderRadius: "12px" }}
src={`https://open.spotify.com/embed/playlist/${selectedBook.spotifyPlaylistId}?utm_source=generator&theme=0`}
width="100%"
height="152"
frameBorder="0"
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy"
/>
</div>
)}
{selectedBook.buyLink && (
<div className="pt-2">
<a
href={selectedBook.buyLink}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl font-bold text-slate-950 transition-all text-xs cursor-pointer shadow-md"
style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
>
<span>Buch kaufen / Lesen</span>
<ExternalLink className="w-4 h-4" />
</a>
</div>
)}
</div>
</div>
</div>
</div>
)}
{/* Project Details Modal */}
{selectedProject && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-md animate-fade-in">
<div
className="relative w-full max-w-lg border rounded-2xl p-6 md:p-8 space-y-6 shadow-2xl"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}40` }}
>
<button
onClick={() => setSelectedProject(null)}
className="absolute top-4 right-4 p-2 text-slate-400 hover:text-white bg-slate-800/80 rounded-full transition-colors cursor-pointer"
>
<X className="w-5 h-5" />
</button>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-xs uppercase font-bold px-2.5 py-1 rounded border tracking-wider" style={{ color: accentColor, backgroundColor: `${accentColor}15`, borderColor: `${accentColor}30` }}>
Status: {selectedProject.status}
</span>
<span className="text-xs text-slate-400 italic">
Fortschritt: {selectedProject.progress}%
</span>
</div>
<h3 className="text-2xl font-bold text-white">{selectedProject.title}</h3>
<div className="h-2 w-full bg-slate-900 rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-500"
style={{ width: `${selectedProject.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
</div>
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-yellow-400 hover:text-pink-400 font-medium"
{...props}
/>
)
}}
>
{selectedProject.description || "Für dieses Schreibprojekt wurden noch keine weiteren Details abgelegt."}
</Markdown>
</div>
</div>
</div>
</div>
)}
{/* Footer */}
<footer className="border-t border-slate-800 py-12 px-4 text-center text-xs text-slate-500 font-sans" style={{ backgroundColor: bgColor }}>
<div className="max-w-7xl mx-auto space-y-2">
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
<p className="text-slate-500">{data.footerText || "Geschrieben mit viel Gefühl und Herzblut."}</p>
</div>
</footer>
</div>
);
}

View file

@ -11,49 +11,79 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const accentColor = data.accentColor || "#06b6d4";
const secondaryColor = data.secondaryColor || "#a855f7";
const bgColor = data.backgroundColor || "#020617";
const cardBgColor = data.cardBgColor || "#0f172a";
const fontClass = data.fontFamily === "serif" ? "font-serif" : data.fontFamily === "mono" ? "font-mono" : "font-sans";
return (
<div className="min-h-screen bg-slate-950 text-slate-100 font-sans selection:bg-purple-500 selection:text-white overflow-x-hidden relative">
{/* Decorative starry background with magical purple aura */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-indigo-950/40 via-slate-950 to-slate-950 -z-10 pointer-events-none" />
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-cyan-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute top-2/3 right-1/4 w-96 h-96 bg-purple-500/5 rounded-full blur-3xl pointer-events-none" />
<div
className={`min-h-screen text-slate-100 ${fontClass} selection:bg-cyan-500 selection:text-white overflow-x-hidden relative`}
style={{ backgroundColor: bgColor }}
>
{/* Hero Banner Background if present */}
{data.heroBannerUrl && (
<div className="absolute top-0 inset-x-0 h-96 overflow-hidden pointer-events-none -z-5 opacity-25">
<img src={data.heroBannerUrl} alt="" className="w-full h-full object-cover blur-sm" />
<div className="absolute inset-0 bg-gradient-to-b from-transparent to-slate-950" style={{ backgroundImage: `linear-gradient(to bottom, transparent, ${bgColor})` }} />
</div>
)}
{/* Decorative starry background with magical aura */}
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-indigo-950/40 via-transparent to-transparent -z-10 pointer-events-none" />
<div
className="absolute top-1/4 left-1/4 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20"
style={{ backgroundColor: accentColor }}
/>
<div
className="absolute top-2/3 right-1/4 w-96 h-96 rounded-full blur-3xl pointer-events-none opacity-20"
style={{ backgroundColor: secondaryColor }}
/>
{/* Hero Section */}
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-indigo-900/40">
<header className="relative py-24 px-4 max-w-7xl mx-auto border-b border-slate-800/80">
<div className="text-center max-w-4xl mx-auto">
{/* Subtle magical/futuristic accent */}
<div className="inline-flex items-center gap-2 px-3 py-1 bg-indigo-950/60 border border-indigo-700/50 rounded-full text-indigo-400 text-xs font-mono mb-6 tracking-widest uppercase">
<Star className="w-3 h-3 text-cyan-400 animate-pulse" />
<span>Cyber-Mystisches Archive</span>
<div
className="inline-flex items-center gap-2 px-3.5 py-1 bg-slate-900/80 border rounded-full text-xs font-mono mb-6 tracking-widest uppercase shadow-sm"
style={{ borderColor: `${accentColor}40`, color: accentColor }}
>
<Star className="w-3.5 h-3.5 animate-pulse" style={{ color: accentColor }} />
<span>{data.badgeText || "Cyber-Mystisches Archive"}</span>
</div>
<h1 className="text-5xl md:text-7xl font-sans tracking-tight text-white mb-4 uppercase drop-shadow-[0_2px_10px_rgba(6,182,212,0.15)] font-bold">
{data.heroTitle || "DANIEL HESSE"}
<h1 className="text-5xl md:text-7xl tracking-tight text-white mb-4 uppercase drop-shadow-md font-bold">
{data.heroTitle || data.name || "DANIEL HESSE"}
</h1>
<p className="text-cyan-400 font-mono text-xs md:text-sm tracking-widest uppercase mb-8">
<p className="font-mono text-xs md:text-sm tracking-widest uppercase mb-8" style={{ color: accentColor }}>
{data.heroSubtitle || "WELTENWANDLER & STERNENZEICHNER"}
</p>
<div className="h-0.5 w-24 bg-gradient-to-r from-cyan-400 via-indigo-500 to-purple-500 mx-auto rounded-full" />
<div
className="h-0.5 w-24 mx-auto rounded-full"
style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
{/* Quick Section Navigation */}
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-mono">
<a
href="#ueber-mich"
className="px-4 py-2 rounded-full border border-indigo-900/40 bg-indigo-950/20 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-slate-800 bg-slate-900/60 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
>
🌌 Über mich
🌌 {data.aboutTitle || "Über mich"}
</a>
<a
href="#projekte"
className="px-4 py-2 rounded-full border border-indigo-900/40 bg-indigo-950/20 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-slate-800 bg-slate-900/60 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
>
🛰️ Aktuelle Projekte
🛰️ {data.projectsTitle || "Aktuelle Projekte"}
</a>
<a
href="#buecherregal"
className="px-4 py-2 rounded-full border border-indigo-900/40 bg-indigo-950/20 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
className="px-4 py-2 rounded-full border border-slate-800 bg-slate-900/60 hover:border-cyan-400 hover:text-cyan-400 transition-all text-slate-300 uppercase tracking-wider"
>
🪐 Bücherregal
🪐 {data.booksTitle || "Bücherregal"}
</a>
</nav>
</div>
@ -67,8 +97,11 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
<div className="md:col-span-4 flex justify-center">
<div className="relative group">
{/* Outer neon ring */}
<div className="absolute -inset-1.5 bg-gradient-to-r from-cyan-500 to-purple-600 rounded-2xl blur opacity-30 group-hover:opacity-70 transition duration-1000 group-hover:duration-200"></div>
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-indigo-500/30 bg-slate-900">
<div
className="absolute -inset-1.5 rounded-2xl blur opacity-30 group-hover:opacity-70 transition duration-1000 group-hover:duration-200"
style={{ background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
<div className="relative w-64 h-64 md:w-80 md:h-80 rounded-2xl overflow-hidden border border-slate-800" style={{ backgroundColor: cardBgColor }}>
<img
src={data.avatarUrl || "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&q=80&w=400"}
alt={data.name}
@ -80,50 +113,56 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
</div>
<div className="md:col-span-8 space-y-6">
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
<Compass className="w-7 h-7 text-cyan-400" />
<span>Über den Autor</span>
<Compass className="w-7 h-7" style={{ color: accentColor }} />
<span>{data.aboutTitle || "Über den Autor"}</span>
</h2>
<div className="prose prose-invert prose-cyan max-w-none text-slate-300 leading-relaxed space-y-4">
<div className="prose prose-invert max-w-none text-slate-300 leading-relaxed space-y-4">
<p className="whitespace-pre-wrap">{data.bio}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 font-mono text-xs">
<div className="p-3 bg-slate-900/60 border border-slate-800 rounded-lg flex items-center gap-3">
<BookOpen className="w-5 h-5 text-indigo-400" />
{(data.bioTag1Label || data.bioTag1Value) && (
<div className="p-3 border rounded-lg flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<BookOpen className="w-5 h-5 shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-slate-500 uppercase tracking-widest">Fokus</div>
<div className="text-white font-semibold">Sci-Fi & Fantasy</div>
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag1Label || "Fokus"}</div>
<div className="text-white font-semibold">{data.bioTag1Value || "Sci-Fi & Fantasy"}</div>
</div>
</div>
<div className="p-3 bg-slate-900/60 border border-slate-800 rounded-lg flex items-center gap-3">
<Star className="w-5 h-5 text-cyan-400" />
)}
{(data.bioTag2Label || data.bioTag2Value) && (
<div className="p-3 border rounded-lg flex items-center gap-3" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Star className="w-5 h-5 shrink-0" style={{ color: accentColor }} />
<div>
<div className="text-slate-500 uppercase tracking-widest">Welten</div>
<div className="text-white font-semibold">Techno-Magie</div>
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag2Label || "Welten"}</div>
<div className="text-white font-semibold">{data.bioTag2Value || "Techno-Magie"}</div>
</div>
</div>
<div className="p-3 bg-slate-900/60 border border-slate-800 rounded-lg flex items-center gap-3 col-span-2 md:col-span-1">
<Award className="w-5 h-5 text-purple-400" />
)}
{(data.bioTag3Label || data.bioTag3Value) && (
<div className="p-3 border rounded-lg flex items-center gap-3 col-span-2 md:col-span-1" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}20` }}>
<Award className="w-5 h-5 shrink-0" style={{ color: secondaryColor }} />
<div>
<div className="text-slate-500 uppercase tracking-widest">Stil</div>
<div className="text-white font-semibold">Episch & Atmosphärisch</div>
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag3Label || "Stil"}</div>
<div className="text-white font-semibold">{data.bioTag3Value || "Episch & Atmosphärisch"}</div>
</div>
</div>
)}
</div>
</div>
</section>
{/* Section 2: Aktuelle Projekte */}
<section id="projekte" className="space-y-8">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-indigo-900/30 pb-4">
<div className="flex flex-col md:flex-row md:items-end justify-between gap-4 border-b border-slate-800 pb-4">
<div>
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
<Star className="w-7 h-7 text-indigo-400" />
<span>Aktuelle Projekte</span>
<Star className="w-7 h-7" style={{ color: accentColor }} />
<span>{data.projectsTitle || "Aktuelle Projekte"}</span>
</h2>
<p className="text-slate-400 text-sm mt-1">Ein exklusiver Blick auf das, was sich gerade auf meinem Schreib-Amboss befindet.</p>
<p className="text-slate-400 text-sm mt-1">{data.projectsSubtitle || "Ein exklusiver Blick auf das, was sich gerade auf meinem Schreib-Amboss befindet."}</p>
</div>
<div className="text-xs font-mono text-cyan-400 bg-cyan-950/40 px-3 py-1 border border-cyan-800/40 rounded">
<div className="text-xs font-mono px-3 py-1 rounded border" style={{ color: accentColor, backgroundColor: `${accentColor}10`, borderColor: `${accentColor}30` }}>
Laufendes Telemetrie-Tracking
</div>
</div>
@ -133,33 +172,48 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
<button
key={project.id}
onClick={() => setSelectedProject(project)}
className="p-6 bg-slate-900/40 backdrop-blur-sm border border-indigo-950 hover:border-cyan-500/50 rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:shadow-[0_0_20px_rgba(6,182,212,0.15)] focus:outline-none"
className="p-6 border rounded-xl transition-all duration-300 flex flex-col text-left w-full justify-between group cursor-pointer hover:scale-[1.01] focus:outline-none shadow-lg"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}25` }}
>
<div className="space-y-4 w-full">
<div className="flex items-center justify-between">
<span className="text-xs font-mono text-cyan-400 bg-cyan-950/60 px-2.5 py-1 rounded border border-cyan-900/50 uppercase tracking-wider">
<span className="text-xs font-mono px-2.5 py-1 rounded border uppercase tracking-wider" style={{ color: accentColor, backgroundColor: `${accentColor}15`, borderColor: `${accentColor}30` }}>
{project.status || "In Arbeit"}
</span>
<span className="text-xs text-slate-500 font-mono">
<span className="text-xs text-slate-400 font-mono">
Progression: {project.progress}%
</span>
</div>
<h3 className="text-xl font-bold text-white group-hover:text-cyan-300 transition-colors">
{project.title}
</h3>
<p className="text-slate-400 text-sm leading-relaxed line-clamp-3">
<div className="text-slate-300 text-sm leading-relaxed line-clamp-3 markdown-body [&_p]:inline [&_p]:m-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-cyan-400 hover:text-cyan-300 font-medium"
onClick={(e) => e.stopPropagation()}
{...props}
/>
)
}}
>
{project.description}
</p>
<p className="text-cyan-400 font-mono text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1">
</Markdown>
</div>
<p className="font-mono text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1" style={{ color: accentColor }}>
Details anzeigen <ArrowRight className="w-3 h-3" />
</p>
</div>
{/* Custom glowing progress bar */}
<div className="mt-6 space-y-1.5 w-full">
<div className="h-1.5 w-full bg-slate-800 rounded-full overflow-hidden">
<div className="h-1.5 w-full bg-slate-950/80 rounded-full overflow-hidden border border-slate-800">
<div
className="h-full bg-gradient-to-r from-cyan-400 via-indigo-500 to-purple-600 rounded-full transition-all duration-1000 ease-out shadow-[0_0_8px_rgba(34,211,238,0.4)]"
style={{ width: `${project.progress}%` }}
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
/>
</div>
</div>
@ -170,12 +224,12 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
{/* Section 3: Bücherregal */}
<section id="buecherregal" className="space-y-8">
<div className="border-b border-indigo-900/30 pb-4">
<div className="border-b border-slate-800 pb-4">
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
<BookOpen className="w-7 h-7 text-purple-400" />
<span>Das Bücherregal</span>
<BookOpen className="w-7 h-7" style={{ color: secondaryColor }} />
<span>{data.booksTitle || "Das Bücherregal"}</span>
</h2>
<p className="text-slate-400 text-sm mt-1">Veröffentlichte Meilensteine. Klicken oder Tippen Sie auf ein Cover, um Geheimnisse zu enthüllen.</p>
<p className="text-slate-400 text-sm mt-1">{data.booksSubtitle || "Veröffentlichte Meilensteine. Klicken oder Tippen Sie auf ein Cover, um Geheimnisse zu enthüllen."}</p>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
@ -183,9 +237,9 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
<button
key={book.id}
onClick={() => setSelectedBook(book)}
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-lg overflow-hidden border border-slate-800 bg-slate-900 hover:border-cyan-400 transition-all duration-300 hover:shadow-[0_0_20px_rgba(6,182,212,0.2)] cursor-pointer"
className="group relative focus:outline-none text-left w-full max-w-[210px] aspect-[2/3] rounded-xl overflow-hidden border hover:border-cyan-400 transition-all duration-300 hover:shadow-xl cursor-pointer"
style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}
>
{/* Glowing edge highlight on hover */}
<div className="absolute inset-0 bg-cyan-900/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
<img
src={book.coverUrl || "https://images.unsplash.com/photo-1543002588-bfa74002ed7e?auto=format&fit=crop&q=80&w=300"}
@ -194,16 +248,48 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
referrerPolicy="no-referrer"
/>
{/* Transparent yet readable overlay, fully visible on hover */}
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-slate-950 via-slate-950/90 to-transparent p-4 opacity-80 group-hover:opacity-100 transition-all duration-350 z-20">
<p className="text-white text-xs font-bold truncate">{book.title}</p>
<p className="text-cyan-400 text-[10px] font-mono mt-0.5">Details auslesen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
<p className="text-[10px] font-mono mt-0.5" style={{ color: accentColor }}>Details auslesen <ArrowRight className="inline-block w-2.5 h-2.5 ml-0.5" /></p>
</div>
</button>
))}
</div>
</section>
{/* Section 4: Custom Highlight Section if present */}
{(data.customSectionTitle || data.customSectionContent) && (
<section className="p-8 rounded-2xl border relative overflow-hidden" style={{ backgroundColor: cardBgColor, borderColor: `${accentColor}30` }}>
<div className="absolute top-0 right-0 p-3 opacity-10 font-mono text-xs uppercase" style={{ color: accentColor }}>
CUSTOM_INFO_MODULE
</div>
{data.customSectionTitle && (
<h2 className="text-2xl font-bold text-white mb-4 flex items-center gap-2">
<Star className="w-5 h-5" style={{ color: accentColor }} />
<span>{data.customSectionTitle}</span>
</h2>
)}
{data.customSectionContent && (
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
<Markdown
components={{
a: ({ node, ...props }) => (
<a
target="_blank"
rel="noopener noreferrer"
className="underline text-cyan-400 hover:text-cyan-300 font-medium"
{...props}
/>
)
}}
>
{data.customSectionContent}
</Markdown>
</div>
)}
</section>
)}
</main>
{/* Book details modular interface modal */}
@ -379,10 +465,10 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
)}
{/* Styled Footer */}
<footer className="bg-slate-950 border-t border-indigo-900/30 py-12 px-4 text-center text-xs text-slate-500 font-mono">
<footer className="border-t border-slate-800 py-12 px-4 text-center text-xs text-slate-500 font-mono" style={{ backgroundColor: bgColor }}>
<div className="max-w-7xl mx-auto space-y-4">
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
<p className="text-slate-600">Dokumentation, Fragmente und stellare Chiffren. Verschlüsselt mit moderner Node.JS Technologie.</p>
<p className="text-slate-500">{data.footerText || "Dokumentation, Fragmente und stellare Chiffren. Verschlüsselt mit moderner Node.JS Technologie."}</p>
</div>
</footer>
</div>

View file

@ -48,14 +48,35 @@ export const defaultAuthorData: AuthorData = {
}
],
customSectionTitle: "Vorträge & Messen",
customSectionContent: "Besuchen Sie mich auf der nächsten Buchmesse am Stand für spekulative Phantastik. Ich freue mich auf den intergalaktischen Austausch mit meinen Leserinnen und Lesern!"
customSectionContent: "Besuchen Sie mich auf der nächsten Buchmesse am Stand für spekulative Phantastik. Ich freue mich auf den intergalaktischen Austausch mit meinen Leserinnen und Lesern!",
badgeText: "Cyber-Mystisches Archive",
aboutTitle: "Über den Autor",
bioTag1Label: "Fokus",
bioTag1Value: "Sci-Fi & Fantasy",
bioTag2Label: "Status",
bioTag2Value: "Aktiv Forschend",
bioTag3Label: "Archiv",
bioTag3Value: "Verschlüsselt",
projectsTitle: "Aktuelle Projekte",
projectsSubtitle: "Ein exklusiver Blick auf das, was sich gerade auf meinem Schreib-Amboss befindet.",
booksTitle: "Bücherregal",
booksSubtitle: "Entdecken Sie meine verlegten Romane und E-Books mit Hörbeispielen.",
footerText: "Dokumentation, Fragmente und stellare Chiffren. Verschlüsselt mit moderner Node.JS Technologie.",
customDomain: "hesse-sf.de, danielhesse.de",
customPath: "/sci-fi, /daniel-hesse",
accentColor: "#06b6d4",
secondaryColor: "#a855f7",
backgroundColor: "#020617",
cardBgColor: "#0f172a",
fontFamily: "sans",
heroBannerUrl: ""
},
erotica: {
name: "M. S. Velvet",
name: "Annie Slone",
avatarUrl: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=300",
heroTitle: "M. S. VELVET",
heroTitle: "ANNIE SLONE",
heroSubtitle: "ELEGANT-SINNLICHE LITERATUR",
bio: "Willkommen in meiner privaten Welt der Versuchung. Unter dem Pseudonym M. S. Velvet schreibe ich stilvolle, hocherotische Romane und prickelnde Erzählungen. Meine Leidenschaft liegt darin, das feine Spiel zwischen Verlangen und Hingabe, Macht und Vertrauen in all seinen schillernden Facetten zu beleuchten. Meine Geschichten richten sich an Kenner anspruchsvoller Erotikliteratur, die Wert auf tiefgründige Charaktere, knisternde Spannung und ästhetische Beschreibungen legen. Treten Sie näher und lassen Sie sich entführen.",
bio: "Willkommen in meiner privaten Welt der Versuchung. Unter dem Pseudonym Annie Slone schreibe ich stilvolle, hocherotische Romane und prickelnde Erzählungen. Meine Leidenschaft liegt darin, das feine Spiel zwischen Verlangen und Hingabe, Macht und Vertrauen in all seinen schillernden Facetten zu beleuchten. Meine Geschichten richten sich an Kenner anspruchsvoller Erotikliteratur, die Wert auf tiefgründige Charaktere, knisternde Spannung und ästhetische Beschreibungen legen. Treten Sie näher und lassen Sie sich entführen.",
projects: [
{
id: "proj_er_1",
@ -97,7 +118,28 @@ export const defaultAuthorData: AuthorData = {
}
],
customSectionTitle: "Sinnliche Lesungen & Salon-Abende",
customSectionContent: "Tragen Sie sich in meinen privaten Briefverteiler ein, um exklusive Einladungen zu meinen intimen Salon-Lesungen in Berlin und Wien zu erhalten. Die Plätze sind streng limitiert."
customSectionContent: "Tragen Sie sich in meinen privaten Briefverteiler ein, um exklusive Einladungen zu meinen intimen Salon-Lesungen in Berlin und Wien zu erhalten. Die Plätze sind streng limitiert.",
badgeText: "Stilvolle Sinnlichkeit & Romantik",
aboutTitle: "Das Geheimnis hinter der Feder",
bioTag1Label: "Genre",
bioTag1Value: "Sinnliche Romance",
bioTag2Label: "Tonalität",
bioTag2Value: "Elegant & Knisternd",
bioTag3Label: "Identität",
bioTag3Value: "Diskret / Pseudonym",
projectsTitle: "In der Entstehung",
projectsSubtitle: "Ein Blick in mein privates Arbeitszimmer. Hier reifen neue Kapitel voller Leidenschaft.",
booksTitle: "Das Samtregal",
booksSubtitle: "Verführerische Geschichten und sinnliche Romane für besondere Stunden.",
footerText: "Mit Liebe und Leidenschaft geschrieben. Alle Werke geschützt.",
customDomain: "annieslone.de, marcvelvet.de",
customPath: "/sensual-moments, /annie-slone, /marc-velvet",
accentColor: "#f97316",
secondaryColor: "#f59e0b",
backgroundColor: "#0a0a0a",
cardBgColor: "#171717",
fontFamily: "serif",
heroBannerUrl: ""
},
clara: {
name: "Clara Finch",
@ -146,6 +188,97 @@ export const defaultAuthorData: AuthorData = {
}
],
customSectionTitle: "Die Philosophie des Mooses",
customSectionContent: "Auf meinen geführten Waldspaziergängen und Schreib-Retreats lade ich Sie ein, die Stille des Waldes neu zu erfahren und Ihre Gedanken auf das Papier fließen zu lassen. Schreiben Sie mir gern für Termine und Buchungsanfragen."
customSectionContent: "Auf meinen geführten Waldspaziergängen und Schreib-Retreats lade ich Sie ein, die Stille des Waldes neu zu erfahren und Ihre Gedanken auf das Papier fließen zu lassen. Schreiben Sie mir gern für Termine und Buchungsanfragen.",
badgeText: "Lyrik, Essays & Naturphilosophie",
aboutTitle: "Aus meiner Schreibstube",
bioTag1Label: "Genre",
bioTag1Value: "Lyrik & Essays",
bioTag2Label: "Ästhetik",
bioTag2Value: "Natur & Poesie",
bioTag3Label: "Fokus",
bioTag3Value: "Naturkontemplation",
projectsTitle: "Laufende Notizen & Skizzen",
projectsSubtitle: "Gedankenfragmente und Manuskripte, die leise im Winde reifen.",
booksTitle: "Veröffentlichte Bände",
booksSubtitle: "Lyrikbände und Naturprosa zum Verweilen und Innehalten.",
footerText: "Gedruckt auf virtuellem Pergament. Stille Notizen aus Wald und Flur.",
customDomain: "clarafinch.de, clara-finch.de",
customPath: "/clara-finch, /clara",
accentColor: "#10b981",
secondaryColor: "#84cc16",
backgroundColor: "#064e3b",
cardBgColor: "#022c22",
fontFamily: "serif",
heroBannerUrl: ""
},
renee: {
name: "Renee Heart",
avatarUrl: "https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&q=80&w=300",
heroTitle: "RENEE HEART",
heroSubtitle: "HERZENSGESCHICHTEN & EMOTIONALE ROMANE",
bio: "Herzlich willkommen in meinem literarischen Zuhause. Ich bin Renee Heart. Ich schreibe herzerwärmende, gefühlvolle Romane über die großen Wendepunkte im Leben, tiefe Freundschaften, unvergessliche Liebesgeschichten und den Mut, dem eigenen Herzen zu folgen. Meine Geschichten handeln von echten Emotionen, leisen wie auch stürmischen Momenten und Charakteren, die man sofort ins Herz schließt. Lassen Sie sich von meinen Büchern berühren und begleiten!",
projects: [
{
id: "proj_rh_1",
title: "Das Lächeln der Sonnenblumen",
status: "Schreibphase",
progress: 70,
description: "Ein berührender Roman über Neuanfänge auf einem malerischen Hof in der Toskana und die Heilung alter Wunden."
},
{
id: "proj_rh_2",
title: "Herbstzauber im kleinen Buchladen",
status: "Lektorat",
progress: 90,
description: "Eine herzerwärmende Liebesgeschichte über unverhoffte Begegnungen zwischen Büchern und Zimtschnecken."
}
],
books: [
{
id: "book_rh_1",
title: "Wo das Herz Zuhause ist",
subtitle: "Herzensroman",
coverUrl: "https://images.unsplash.com/photo-1512820790803-83ca734da794?auto=format&fit=crop&q=80&w=600",
description: "Als Maya das alte Haus ihrer Großmutter an der Küste erbt, ahnt sie nicht, dass der Neuanfang ihr ganzes Leben verändern wird. Zwischen stürmischen Wellen und warmem Tee findet sie nicht nur vergessene Familiengeheimnisse, sondern auch die Liebe ihres Lebens.",
publishedYear: 2025,
buyLink: "https://amazon.de",
spotifyPlaylistId: "37i9dQZF1DX4p1vB8meRoo",
genres: ["Contemporary Romance", "Herzensgeschichte", "Feelgood"]
},
{
id: "book_rh_2",
title: "Sterne über den Dächern",
subtitle: "Romantische Komödie",
coverUrl: "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&q=80&w=600",
description: "Eine humorvolle und tiefgründige Geschichte über zwei Gegensätze, die sich in einer nächtlichen Sternwarte näherkommen als je geahnt.",
publishedYear: 2024,
buyLink: "https://amazon.de",
spotifyPlaylistId: "37i9dQZF1DX76W2S233Aif",
genres: ["Romantic Comedy", "Gefühlsvoll", "Hoffnung"]
}
],
customSectionTitle: "Herzenspost & Exklusive Kapitel",
customSectionContent: "Möchten Sie vorab Leseproben erhalten und mich auf meinem Schreibweg begleiten? Melden Sie sich für meine monatliche Herzenspost an!",
badgeText: "Herzklopfen & Gefühlsstark",
aboutTitle: "Über Renee Heart",
bioTag1Label: "Genre",
bioTag1Value: "Emotionale Romance",
bioTag2Label: "Tonalität",
bioTag2Value: "Herzlich & Hoffnungsvoll",
bioTag3Label: "Fokus",
bioTag3Value: "Gefühl & Tiefe",
projectsTitle: "Aus der Schreibstube",
projectsSubtitle: "Ein Blick auf meine aktuellen Geschichten in Entstehung – voller Hoffnung und Herzklopfen.",
booksTitle: "Gefühlvolle Romane",
booksSubtitle: "Geschichten zum Mitfühlen, Träumen und Genießen.",
footerText: "Geschrieben mit viel Gefühl und Herzblut.",
customDomain: "reneeheart.de, renee-heart.de",
customPath: "/renee-heart, /renee",
accentColor: "#eab308",
secondaryColor: "#ec4899",
backgroundColor: "#0f172a",
cardBgColor: "#1e293b",
fontFamily: "sans",
heroBannerUrl: ""
}
};

View file

@ -28,12 +28,43 @@ export interface AuthorProfile {
books: Book[];
customSectionTitle?: string;
customSectionContent?: string;
// Domain & Path Routing
customDomain?: string; // e.g., "annieslone.de, annie-slone.de"
customPath?: string; // e.g., "/sensual-moments, /annie"
// Custom static texts
badgeText?: string;
aboutTitle?: string;
// Dynamic Bio Highlights / Schlagworte (Über mich)
bioTag1Label?: string;
bioTag1Value?: string;
bioTag2Label?: string;
bioTag2Value?: string;
bioTag3Label?: string;
bioTag3Value?: string;
projectsTitle?: string;
projectsSubtitle?: string;
booksTitle?: string;
booksSubtitle?: string;
footerText?: string;
// Custom design & theme controls
accentColor?: string;
secondaryColor?: string;
backgroundColor?: string;
cardBgColor?: string;
fontFamily?: "sans" | "serif" | "mono";
heroBannerUrl?: string;
}
export interface AuthorData {
scifi: AuthorProfile;
erotica: AuthorProfile;
clara: AuthorProfile;
renee: AuthorProfile;
}
export interface AdminSettings {