diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9d3e26f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +# Ignoriere lokale Daten und Abhängigkeiten beim Docker-Build +node_modules +dist +data +.git +.env +.env.* +*.log diff --git a/.gitignore b/.gitignore index 9c53d56..66f88ff 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,5 @@ coverage/ *.log .env* !.env.example -data/database.json +data/ diff --git a/public/books/slone/FestGehalten_web.png b/public/books/slone/FestGehalten_web.png deleted file mode 100644 index 5e9d72e..0000000 Binary files a/public/books/slone/FestGehalten_web.png and /dev/null differ diff --git a/public/books/slone/Zirkel_web.png b/public/books/slone/Zirkel_web.png deleted file mode 100644 index c120c8d..0000000 Binary files a/public/books/slone/Zirkel_web.png and /dev/null differ diff --git a/public/books/slone/ausgeliefert.png b/public/books/slone/ausgeliefert.png deleted file mode 100644 index c2cc0c5..0000000 Binary files a/public/books/slone/ausgeliefert.png and /dev/null differ diff --git a/public/books/slone/dreieck.png b/public/books/slone/dreieck.png deleted file mode 100644 index 86c8859..0000000 Binary files a/public/books/slone/dreieck.png and /dev/null differ diff --git a/public/books/slone/familienspiele.png b/public/books/slone/familienspiele.png deleted file mode 100644 index 7dc1f0b..0000000 Binary files a/public/books/slone/familienspiele.png and /dev/null differ diff --git a/public/books/slone/festgewickelt.png b/public/books/slone/festgewickelt.png deleted file mode 100644 index cd3dd37..0000000 Binary files a/public/books/slone/festgewickelt.png and /dev/null differ diff --git a/public/books/slone/geschwisterliebe.png b/public/books/slone/geschwisterliebe.png deleted file mode 100644 index e62558e..0000000 Binary files a/public/books/slone/geschwisterliebe.png and /dev/null differ diff --git a/public/books/slone/mutter.png b/public/books/slone/mutter.png deleted file mode 100644 index d61314d..0000000 Binary files a/public/books/slone/mutter.png and /dev/null differ diff --git a/public/books/slone/sabrina.png b/public/books/slone/sabrina.png deleted file mode 100644 index ea77198..0000000 Binary files a/public/books/slone/sabrina.png and /dev/null differ diff --git a/public/books/slone/zweifrauen.png b/public/books/slone/zweifrauen.png deleted file mode 100644 index 6d75b4c..0000000 Binary files a/public/books/slone/zweifrauen.png and /dev/null differ diff --git a/public/profiles/slone/photo.jpg b/public/profiles/slone/photo.jpg deleted file mode 100644 index 41c38fd..0000000 Binary files a/public/profiles/slone/photo.jpg and /dev/null differ diff --git a/public/profiles/slone/photo2.jpg b/public/profiles/slone/photo2.jpg deleted file mode 100644 index de63163..0000000 Binary files a/public/profiles/slone/photo2.jpg and /dev/null differ diff --git a/public/profiles/slone/photo3.jpg b/public/profiles/slone/photo3.jpg deleted file mode 100644 index 7b1b3f2..0000000 Binary files a/public/profiles/slone/photo3.jpg and /dev/null differ diff --git a/server.ts b/server.ts index 317a1ab..94d8208 100644 --- a/server.ts +++ b/server.ts @@ -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,39 @@ async function initDatabase(): Promise { 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; - modified = true; - } - if (!parsed.erotica) { - parsed.erotica = defaultAuthorData.erotica; - modified = true; - } - if (!parsed.clara) { - parsed.clara = defaultAuthorData.clara; - modified = true; + + // 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", + "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; + } + } + } } - // 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; - } - 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; + // 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"); + } modified = true; } @@ -154,7 +157,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; } diff --git a/src/App.tsx b/src/App.tsx index 0a16a4c..a1ac216 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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,7 +40,7 @@ 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(); // 1. Pfad-basierte Routen (haben Vorrang, z.B. für die Vorschau oder Admin-Interaktionen) @@ -49,6 +50,9 @@ export default function App() { if (currentPath === "/clara-finch" || currentPath.startsWith("/clara-finch/")) { return "clara"; } + if (currentPath === "/renee-heart" || currentPath.startsWith("/renee-heart/") || currentPath === "/renee" || currentPath.startsWith("/renee/")) { + return "renee"; + } if (currentPath === "/sci-fi" || currentPath.startsWith("/sci-fi/")) { return "scifi"; } @@ -60,6 +64,9 @@ export default function App() { if (hostname.includes("clarafinch.de")) { return "clara"; } + if (hostname.includes("reneeheart.de") || hostname.includes("renee-heart.de")) { + return "renee"; + } if (hostname.includes("hesse-sf.de")) { return "scifi"; } @@ -81,9 +88,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 +132,7 @@ export default function App() { return ; } - // 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 +141,9 @@ export default function App() { if (activeSuite === "clara") { return ; } + if (activeSuite === "renee") { + return ; + } return ; }; diff --git a/src/components/AdminPanel.tsx b/src/components/AdminPanel.tsx index 1df3749..3fba463 100644 --- a/src/components/AdminPanel.tsx +++ b/src/components/AdminPanel.tsx @@ -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 } from "lucide-react"; import ImagePicker from "./ImagePicker"; @@ -20,11 +21,11 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) { // Author Data State const [authorData, setAuthorData] = useState(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(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) {
{/* Swapping genre profile profile */} -
+
+
@@ -472,6 +479,20 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) { Profil & Biografie info + +
)} + {/* TAB 2: Static Texts & Headings */} + {activeTab === "texts" && ( +
+
+
+

+ + Statische Texte & Sektionsüberschriften +

+

Passen Sie alle Beschriftungen, Sektions-Header und Fußzeilen für {profile.name} an.

+
+ +
+ +
+
+ + 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" + /> +

Wird über dem Haupttitel mit feinem Glanz-Effekt angezeigt.

+
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+ +
+ + 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" + /> +
+
+
+ )} + + {/* TAB 3: Design & Dynamic Theme Controls */} + {activeTab === "design" && ( +
+
+
+

+ + Design, Farben & Themes +

+

Gestalten Sie das Erscheinungsbild für {profile.name} nach Ihren Wünschen.

+
+ +
+ + {/* Preset Theme Selection */} +
+ +
+ + + + + + + +
+
+ +
+
+ +
+ updateProfileField("accentColor", e.target.value)} + className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1" + /> + 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" + /> +
+
+ +
+ +
+ updateProfileField("secondaryColor", e.target.value)} + className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1" + /> + 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" + /> +
+
+ +
+ +
+ updateProfileField("backgroundColor", e.target.value)} + className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1" + /> + 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" + /> +
+
+ +
+ +
+ updateProfileField("cardBgColor", e.target.value)} + className="w-10 h-10 rounded-lg cursor-pointer bg-slate-950 border border-slate-800 p-1" + /> + 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" + /> +
+
+ +
+ + +
+ +
+ + updateProfileField("heroBannerUrl", url)} + token={token} + label="Hero Banner-Bild" + /> +

Wenn angegeben, wird dieses Bild als stimmungsvoller Hintergrund im oberen Bereich der Webseite hinterlegt.

+
+
+
+ )} + {/* TAB 2: Projects manager */} {activeTab === "projects" && (
diff --git a/src/components/ClaraPortfolio.tsx b/src/components/ClaraPortfolio.tsx index 54d6f19..3b83c9a 100644 --- a/src/components/ClaraPortfolio.tsx +++ b/src/components/ClaraPortfolio.tsx @@ -11,48 +11,69 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) { const [selectedBook, setSelectedBook] = useState(null); const [selectedProject, setSelectedProject] = useState(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 ( -
- {/* Delicate organic gradients - Sage (Salbei) and Mauve (Malve) */} -
-
-
+
+ {/* Hero Banner Background if present */} + {data.heroBannerUrl && ( +
+ +
+
+ )} + + {/* Delicate organic gradients */} +
+
+
{/* Header / Hero */} -
+
-
- - Lyrik & Naturphilosophie +
+ + {data.badgeText || "Lyrik & Naturphilosophie"}
-

- {data.heroTitle || "CLARA FINCH"} +

+ {data.heroTitle || data.name || "CLARA FINCH"}

-

+

{data.heroSubtitle || "LYRIK, ESSAYS & NATURPHILOSOPHIE"}

-
+ @@ -66,8 +87,8 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
{/* Outer soft natural glow */} -
-
+
+
{data.name}

- - Normal ist das, was heilt. + + {data.aboutTitle || "Das Atmen zwischen den Zeilen"}

-
+

{data.bio}

-
- +
+
Genre
-
Deep Romance & Contemporary
+
Lyrik & Essays
-
- +
+
Ästhetik
-
Cozy, analog & geerdet
+
Natur & Poesie
-
- +
+
Fokus
-
Heilung, Akzeptanz & Safe Spaces
+
Naturkontemplation
@@ -114,13 +135,13 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) { {/* Dynamic ongoing work writing progress */}
-
+

- - Aus den Notizbüchern + + {data.projectsTitle || "Aus den Notizbüchern"}

-

Gedanken im Entwurf, Fragmente und Sammlungen, die bald das Licht der Welt erblicken.

+

{data.projectsSubtitle || "Gedanken im Entwurf, Fragmente und Sammlungen, die bald das Licht der Welt erblicken."}

@@ -129,33 +150,33 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) { ))}
- {/* Customizable Custom Section */} - {data.customSectionContent && data.customSectionContent.trim() !== "" && ( -
-
- {data.customSectionTitle && ( -

- {data.customSectionTitle} -

- )} -

+ {/* Custom Highlight Section if present */} + {(data.customSectionTitle || data.customSectionContent) && ( +

+ {data.customSectionTitle && ( +

+ + {data.customSectionTitle} +

+ )} + {data.customSectionContent && ( +
{data.customSectionContent} -

-
+
+ )}
)} @@ -385,10 +407,10 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) { )} {/* Footer */} -
+

© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.

-

Betrachtungen, verwoben mit dem Rhythmus der wachsenden Natur.

+

{data.footerText || "Betrachtungen, verwoben mit dem Rhythmus der wachsenden Natur."}

diff --git a/src/components/EroticaPortfolio.tsx b/src/components/EroticaPortfolio.tsx index 98af67a..043346a 100644 --- a/src/components/EroticaPortfolio.tsx +++ b/src/components/EroticaPortfolio.tsx @@ -11,48 +11,69 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) { const [selectedBook, setSelectedBook] = useState(null); const [selectedProject, setSelectedProject] = useState(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 ( -
+
+ {/* Hero Banner Background if present */} + {data.heroBannerUrl && ( +
+ +
+
+ )} + {/* Decorative Warm background gradient and peach glow vibes */} -
-
-
+
+
+
{/* Header / Hero */} -
+
-
- - Stilvolle Sinnlichkeit & Romantik +
+ + {data.badgeText || "Stilvolle Sinnlichkeit & Romantik"}
-

- {data.heroTitle || "M. S. VELVET"} +

+ {data.heroTitle || data.name || "M. S. VELVET"}

-

+

{data.heroSubtitle || "ELEGANT-SINNLICHE LITERATUR"}

-
+ @@ -66,8 +87,8 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
{/* Outer warm glow */} -
-
+
+
{data.name}

- - Zwischen Spielzeugautos und der Stille der Nacht... + + {data.aboutTitle || "Das Geheimnis hinter der Feder"}

{data.bio}

-
- +
+
Genre
-
Spicy Romance & Slice of Life
+
Sinnliche Romance
-
- +
+
-
Ästhetik
-
Hautnah, cozy & ungefiltert
+
Tonalität
+
Elegant & Knisternd
-
Fokus
-
Bedingungsloses Vertrauen & Intime Facetten
+
Identität
+
Diskret / Pseudonym
@@ -114,13 +135,13 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) { {/* Dynamic ongoing work writing progress */}
-
+

- - In der Entstehung... + + {data.projectsTitle || "In der Entstehung..."}

-

Ein diskreter Blick hinter die Samtvorhänge meiner aktuellen Manuskripte.

+

{data.projectsSubtitle || "Ein diskreter Blick hinter die Samtvorhänge meiner aktuellen Manuskripte."}

@@ -129,11 +150,12 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
- {/* Bookshelf display, strictly elegant and minimalist */} + {/* Bookshelf display */}
-
+

- - Das Samtregal + + {data.booksTitle || "Das Samtregal"}

-

Wählen Sie ein Buch aus, um tiefer in die Versuchung einzutauchen.

+

{data.booksSubtitle || "Wählen Sie ein Buch aus, um tiefer in die Versuchung einzutauchen."}

@@ -179,7 +200,8 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) { ))}
- {/* Customizable Custom Section */} - {data.customSectionContent && data.customSectionContent.trim() !== "" && ( -
-
- {data.customSectionTitle && ( -

- {data.customSectionTitle} -

- )} -

+ {/* Custom Highlight Section if present */} + {(data.customSectionTitle || data.customSectionContent) && ( +

+ {data.customSectionTitle && ( +

+ + {data.customSectionTitle} +

+ )} + {data.customSectionContent && ( +
{data.customSectionContent} -

-
+
+ )}
)} @@ -385,10 +407,10 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) { )} {/* Footer */} -
+

© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.

-

Geschlossenes Refugium für feine, hochästhetische Phantasiereisen.

+

{data.footerText || "Geschlossenes Refugium für feine, hochästhetische Phantasiereisen."}

diff --git a/src/components/ReneePortfolio.tsx b/src/components/ReneePortfolio.tsx new file mode 100644 index 0000000..6d5ead8 --- /dev/null +++ b/src/components/ReneePortfolio.tsx @@ -0,0 +1,379 @@ +import React, { useState } from "react"; +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(null); + const [selectedProject, setSelectedProject] = useState(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 ( +
+ {/* Hero Banner Background if present */} + {data.heroBannerUrl && ( +
+ +
+
+ )} + + {/* Decorative Yellow & Pink Warm Glows */} +
+
+
+ + {/* Header / Hero */} +
+
+
+ + {data.badgeText || "Herzklopfen & Gefühlsstark"} +
+ +

+ {data.heroTitle || data.name || "RENEE HEART"} +

+

+ {data.heroSubtitle || "HERZENSGESCHICHTEN & EMOTIONALE ROMANE"} +

+ +
+ + {/* Main Content Area */} +
+ + {/* Author Bio Section */} +
+
+
+ {/* Outer yellow/pink glow */} +
+
+ {data.name} +
+
+
+
+

+ + {data.aboutTitle || "Über Renee Heart"} +

+
+

{data.bio}

+
+ +
+
+ +
+
Genre
+
Emotionale Romance
+
+
+
+ +
+
Tonalität
+
Herzlich & Hoffungsvoll
+
+
+
+ +
+
Fokus
+
Gefühl & Tiefe
+
+
+
+
+
+ + {/* Dynamic ongoing work writing progress */} +
+
+
+

+ + {data.projectsTitle || "Aus der Schreibstube"} +

+

{data.projectsSubtitle || "Ein Blick auf meine aktuellen Geschichten in Entstehung – voller Hoffnung und Herzklopfen."}

+
+
+ +
+ {data.projects.map((project) => ( + + ))} +
+
+ + {/* Bookshelf display */} +
+
+

+ + {data.booksTitle || "Gefühlvolle Romane"} +

+

{data.booksSubtitle || "Geschichten zum Mitfühlen, Träumen und Genießen."}

+
+ +
+ {data.books.map((book) => ( + + ))} +
+
+ + {/* Custom Highlight Section if present */} + {(data.customSectionTitle || data.customSectionContent) && ( +
+ {data.customSectionTitle && ( +

+ + {data.customSectionTitle} +

+ )} + {data.customSectionContent && ( +
+ {data.customSectionContent} +
+ )} +
+ )} + +
+ + {/* Book Details Modal */} + {selectedBook && ( +
+
+ + +
+
+ {selectedBook.title} +
+
+
+ Erscheinungsjahr {selectedBook.publishedYear} +

{selectedBook.title}

+ {selectedBook.subtitle && ( +

{selectedBook.subtitle}

+ )} +
+ + {selectedBook.genres && selectedBook.genres.length > 0 && ( +
+ {selectedBook.genres.map((g, idx) => ( + + {g} + + ))} +
+ )} + +

+ {selectedBook.description} +

+ + {/* Spotify Playlist player if configured */} + {selectedBook.spotifyPlaylistId && ( +
+
+ + Klangwelt zum Buch auf Spotify +
+