8
.dockerignore
Normal 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
|
|
@ -6,5 +6,5 @@ coverage/
|
||||||
*.log
|
*.log
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
data/database.json
|
data/
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 938 KiB |
|
Before Width: | Height: | Size: 6.1 MiB |
|
Before Width: | Height: | Size: 6.7 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 6.9 MiB |
|
Before Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 5.8 MiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 163 KiB |
57
server.ts
|
|
@ -5,7 +5,7 @@ import { fileURLToPath } from "url";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { GoogleGenAI } from "@google/genai";
|
import { GoogleGenAI } from "@google/genai";
|
||||||
import { defaultAuthorData } from "./src/defaultData.js";
|
import { defaultAuthorData } from "./src/defaultData.js";
|
||||||
import { AuthorData } from "./src/types.js";
|
import { AuthorData, AuthorProfile } from "./src/types.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
|
@ -32,36 +32,43 @@ async function initDatabase(): Promise<AuthorData> {
|
||||||
try {
|
try {
|
||||||
const existingData = await fs.readFile(DATA_FILE, "utf-8");
|
const existingData = await fs.readFile(DATA_FILE, "utf-8");
|
||||||
const parsed = JSON.parse(existingData) as AuthorData;
|
const parsed = JSON.parse(existingData) as AuthorData;
|
||||||
|
|
||||||
// Auto-heal database: make sure all profiles from defaultAuthorData exist
|
|
||||||
let modified = false;
|
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;
|
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
|
// Migration check: update erotica default name from M. S. Velvet / Marc Velvet to Annie Slone if unchanged
|
||||||
if (parsed.scifi && parsed.scifi.customSectionTitle === undefined) {
|
if (parsed.erotica && (parsed.erotica.name === "M. S. Velvet" || parsed.erotica.name === "Marc Velvet")) {
|
||||||
parsed.scifi.customSectionTitle = defaultAuthorData.scifi.customSectionTitle;
|
parsed.erotica.name = "Annie Slone";
|
||||||
parsed.scifi.customSectionContent = defaultAuthorData.scifi.customSectionContent;
|
parsed.erotica.heroTitle = "ANNIE SLONE";
|
||||||
modified = true;
|
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;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,7 +161,7 @@ app.post("/api/admin/login", (req, res) => {
|
||||||
// 3. Save modified profile configurations (About, Projects, Books)
|
// 3. Save modified profile configurations (About, Projects, Books)
|
||||||
app.post("/api/admin/save-profile", verifyToken, async (req, res) => {
|
app.post("/api/admin/save-profile", verifyToken, async (req, res) => {
|
||||||
const { profileKey, profileData } = req.body;
|
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." });
|
res.status(400).json({ error: "Ungültiger Profilschlüssel." });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
67
src/App.tsx
|
|
@ -3,6 +3,7 @@ import { AuthorData } from "./types";
|
||||||
import SciFiPortfolio from "./components/SciFiPortfolio";
|
import SciFiPortfolio from "./components/SciFiPortfolio";
|
||||||
import EroticaPortfolio from "./components/EroticaPortfolio";
|
import EroticaPortfolio from "./components/EroticaPortfolio";
|
||||||
import ClaraPortfolio from "./components/ClaraPortfolio";
|
import ClaraPortfolio from "./components/ClaraPortfolio";
|
||||||
|
import ReneePortfolio from "./components/ReneePortfolio";
|
||||||
import AdminPanel from "./components/AdminPanel";
|
import AdminPanel from "./components/AdminPanel";
|
||||||
import { Lock, Eye, Sparkles, RefreshCw, Leaf } from "lucide-react";
|
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)
|
// 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 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)
|
// 1. Pfad-basierte Routen (haben Vorrang, z.B. für Vorschau & Direktlinks)
|
||||||
if (currentPath === "/sensual-moments" || currentPath.startsWith("/sensual-moments/")) {
|
for (const key of profileKeys) {
|
||||||
return "erotica";
|
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)
|
// 2. Dynamische Domain-basierte Routen (für Live-Domains)
|
||||||
if (hostname.includes("annieslone.de")) {
|
for (const key of profileKeys) {
|
||||||
return "erotica";
|
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)
|
// Standard-Fallback (Daniel Hesse SciFi)
|
||||||
|
|
@ -81,9 +106,11 @@ export default function App() {
|
||||||
|
|
||||||
const activeSuite = getActiveSuite();
|
const activeSuite = getActiveSuite();
|
||||||
if (activeSuite === "erotica") {
|
if (activeSuite === "erotica") {
|
||||||
document.title = authorData.erotica.name || "Marc Velvet";
|
document.title = authorData.erotica.name || "Annie Slone";
|
||||||
} else if (activeSuite === "clara") {
|
} else if (activeSuite === "clara") {
|
||||||
document.title = authorData.clara.name || "Clara Finch";
|
document.title = authorData.clara.name || "Clara Finch";
|
||||||
|
} else if (activeSuite === "renee") {
|
||||||
|
document.title = authorData.renee.name || "Renee Heart";
|
||||||
} else {
|
} else {
|
||||||
document.title = authorData.scifi.name || "Daniel Hesse";
|
document.title = authorData.scifi.name || "Daniel Hesse";
|
||||||
}
|
}
|
||||||
|
|
@ -123,10 +150,7 @@ export default function App() {
|
||||||
return <AdminPanel onLogout={handleAdminLogout} />;
|
return <AdminPanel onLogout={handleAdminLogout} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Sensual/Erotica Portfolio pseudonym
|
// Render active portfolio
|
||||||
// 3. Poetic Clara Finch Portfolio pseudonym
|
|
||||||
// 4. SciFi Daniel Hesse Portfolio
|
|
||||||
|
|
||||||
const renderActivePortfolio = () => {
|
const renderActivePortfolio = () => {
|
||||||
const activeSuite = getActiveSuite();
|
const activeSuite = getActiveSuite();
|
||||||
if (activeSuite === "erotica") {
|
if (activeSuite === "erotica") {
|
||||||
|
|
@ -135,6 +159,9 @@ export default function App() {
|
||||||
if (activeSuite === "clara") {
|
if (activeSuite === "clara") {
|
||||||
return <ClaraPortfolio data={authorData.clara} />;
|
return <ClaraPortfolio data={authorData.clara} />;
|
||||||
}
|
}
|
||||||
|
if (activeSuite === "renee") {
|
||||||
|
return <ReneePortfolio data={authorData.renee} />;
|
||||||
|
}
|
||||||
return <SciFiPortfolio data={authorData.scifi} />;
|
return <SciFiPortfolio data={authorData.scifi} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import React, { useState, useEffect, FormEvent } from "react";
|
||||||
import { AuthorData, AuthorProfile, Book, Project } from "../types";
|
import { AuthorData, AuthorProfile, Book, Project } from "../types";
|
||||||
import {
|
import {
|
||||||
Lock, Save, Plus, Trash2, Edit2, Check, RefreshCw, Sparkles,
|
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";
|
} from "lucide-react";
|
||||||
import ImagePicker from "./ImagePicker";
|
import ImagePicker from "./ImagePicker";
|
||||||
|
|
||||||
|
|
@ -20,11 +21,11 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
// Author Data State
|
// Author Data State
|
||||||
const [authorData, setAuthorData] = useState<AuthorData | null>(null);
|
const [authorData, setAuthorData] = useState<AuthorData | null>(null);
|
||||||
|
|
||||||
// Selected Profile state ("scifi" vs "erotica" vs "clara")
|
// Selected Profile state ("scifi" vs "erotica" vs "clara" vs "renee")
|
||||||
const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara">("scifi");
|
const [activeProfile, setActiveProfile] = useState<"scifi" | "erotica" | "clara" | "renee">("scifi");
|
||||||
|
|
||||||
// Sub-tab selection state ("profile" | "books" | "projects")
|
// Sub-tab selection state ("profile" | "texts" | "design" | "books" | "projects")
|
||||||
const [activeTab, setActiveTab] = useState<"profile" | "books" | "projects">("profile");
|
const [activeTab, setActiveTab] = useState<"profile" | "texts" | "design" | "books" | "projects">("profile");
|
||||||
|
|
||||||
// Editing forms state
|
// Editing forms state
|
||||||
const [editingBookId, setEditingBookId] = useState<string | null>(null);
|
const [editingBookId, setEditingBookId] = useState<string | null>(null);
|
||||||
|
|
@ -98,7 +99,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Profile save handler
|
// Profile save handler
|
||||||
const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara") => {
|
const handleSaveProfileInfo = async (profileKey: "scifi" | "erotica" | "clara" | "renee") => {
|
||||||
if (!authorData) return;
|
if (!authorData) return;
|
||||||
setError("");
|
setError("");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -411,24 +412,30 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* Swapping genre profile profile */}
|
{/* 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
|
<button
|
||||||
onClick={() => { setActiveProfile("scifi"); setActiveTab("profile"); }}
|
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"}`}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setActiveProfile("erotica"); setActiveTab("profile"); }}
|
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"}`}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setActiveProfile("clara"); setActiveTab("profile"); }}
|
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"}`}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -472,6 +479,20 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
<Compass className="w-4 h-4" />
|
<Compass className="w-4 h-4" />
|
||||||
<span>Profil & Biografie info</span>
|
<span>Profil & Biografie info</span>
|
||||||
</button>
|
</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
|
<button
|
||||||
onClick={() => setActiveTab("projects")}
|
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"}`}
|
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>
|
</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 */}
|
{/* Custom Highlight Section */}
|
||||||
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
|
<div className="md:col-span-2 border-t border-slate-800/80 pt-6 mt-2 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -595,7 +747,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 md:col-span-2">
|
<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
|
<textarea
|
||||||
rows={4}
|
rows={4}
|
||||||
value={profile.customSectionContent || ""}
|
value={profile.customSectionContent || ""}
|
||||||
|
|
@ -610,6 +762,317 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
</div>
|
</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 */}
|
{/* TAB 2: Projects manager */}
|
||||||
{activeTab === "projects" && (
|
{activeTab === "projects" && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|
@ -720,7 +1183,7 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 md:col-span-2">
|
<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
|
<textarea
|
||||||
rows={3}
|
rows={3}
|
||||||
value={projectForm.description || ""}
|
value={projectForm.description || ""}
|
||||||
|
|
|
||||||
|
|
@ -11,48 +11,69 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
||||||
const [selectedProject, setSelectedProject] = useState<Project | 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 (
|
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">
|
<div
|
||||||
{/* Delicate organic gradients - Sage (Salbei) and Mauve (Malve) */}
|
className={`min-h-screen text-stone-200 ${fontClass} selection:bg-[#B39EAF]/30 selection:text-white overflow-x-hidden relative`}
|
||||||
<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" />
|
style={{ backgroundColor: bgColor }}
|
||||||
<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" />
|
{/* 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 / 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="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">
|
<div
|
||||||
<Leaf className="w-3.5 h-3.5 text-[#8FA797] animate-pulse" />
|
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"
|
||||||
<span className="font-serif italic text-stone-300">Lyrik & Naturphilosophie</span>
|
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>
|
</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)]">
|
<h1 className="text-4xl md:text-6xl font-serif tracking-widest text-stone-100 uppercase font-light drop-shadow-sm">
|
||||||
{data.heroTitle || "CLARA FINCH"}
|
{data.heroTitle || data.name || "CLARA FINCH"}
|
||||||
</h1>
|
</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"}
|
{data.heroSubtitle || "LYRIK, ESSAYS & NATURPHILOSOPHIE"}
|
||||||
</p>
|
</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">
|
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-sans">
|
||||||
<a
|
<a
|
||||||
href="#ueber-mich"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#projekte"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#buecherregal"
|
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>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,8 +87,8 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
<div className="md:col-span-4 flex justify-center">
|
<div className="md:col-span-4 flex justify-center">
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
{/* Outer soft natural glow */}
|
{/* 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="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-[#4A5D50]/30 bg-stone-900">
|
<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
|
<img
|
||||||
src={data.avatarUrl || "https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=400"}
|
src={data.avatarUrl || "https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&q=80&w=400"}
|
||||||
alt={data.name}
|
alt={data.name}
|
||||||
|
|
@ -79,48 +100,54 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="md:col-span-8 space-y-6">
|
<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">
|
<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]" />
|
<Feather className="w-6 h-6" style={{ color: accentColor }} />
|
||||||
<span>Normal ist das, was heilt.</span>
|
<span>{data.aboutTitle || "Das Atmen zwischen den Zeilen"}</span>
|
||||||
</h2>
|
</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>
|
<p className="whitespace-pre-wrap italic">{data.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 text-xs">
|
<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">
|
{(data.bioTag1Label || data.bioTag1Value) && (
|
||||||
<Leaf className="w-5 h-5 text-[#8FA797]" />
|
<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>
|
||||||
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Genre</div>
|
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag1Label || "Genre"}</div>
|
||||||
<div className="text-stone-200 font-semibold">Deep Romance & Contemporary</div>
|
<div className="text-stone-200 font-semibold">{data.bioTag1Value || "Lyrik & Essays"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Ästhetik</div>
|
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag2Label || "Ästhetik"}</div>
|
||||||
<div className="text-stone-200 font-semibold">Cozy, analog & geerdet</div>
|
<div className="text-stone-200 font-semibold">{data.bioTag2Value || "Natur & Poesie"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-stone-500 uppercase tracking-wider text-[10px]">Fokus</div>
|
<div className="text-stone-500 uppercase tracking-wider text-[10px]">{data.bioTag3Label || "Fokus"}</div>
|
||||||
<div className="text-stone-200 font-semibold">Heilung, Akzeptanz & Safe Spaces</div>
|
<div className="text-stone-200 font-semibold">{data.bioTag3Value || "Naturkontemplation"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Dynamic ongoing work writing progress */}
|
{/* Dynamic ongoing work writing progress */}
|
||||||
<section id="projekte" className="space-y-8">
|
<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>
|
<div>
|
||||||
<h2 className="text-2xl md:text-3xl font-serif text-stone-100 flex items-center gap-3">
|
<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]" />
|
<Feather className="w-6 h-6" style={{ color: accentColor }} />
|
||||||
<span>Aus den Notizbüchern</span>
|
<span>{data.projectsTitle || "Aus den Notizbüchern"}</span>
|
||||||
</h2>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -129,33 +156,47 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
<button
|
<button
|
||||||
key={project.id}
|
key={project.id}
|
||||||
onClick={() => setSelectedProject(project)}
|
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="space-y-4 w-full">
|
||||||
<div className="flex items-center justify-between">
|
<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"}
|
{project.status || "In Arbeit"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-stone-400 italic">
|
<span className="text-xs text-stone-400 italic">
|
||||||
Reifegrad: {project.progress}%
|
Reifegrad: {project.progress}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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}
|
{project.title}
|
||||||
</h3>
|
</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}
|
{project.description}
|
||||||
</p>
|
</Markdown>
|
||||||
<p className="text-[#8FA797] font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic">
|
</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" />
|
Details einsehen <ArrowRight className="w-3 h-3" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Sage & Mauve loading progress meter */}
|
|
||||||
<div className="mt-6 space-y-1.5 w-full">
|
<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-1 w-full bg-stone-900 rounded-full overflow-hidden">
|
||||||
<div
|
<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)]"
|
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -166,12 +207,12 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
|
|
||||||
{/* Bookshelf display */}
|
{/* Bookshelf display */}
|
||||||
<section id="buecherregal" className="space-y-8">
|
<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">
|
<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]" />
|
<BookOpen className="w-6 h-6" style={{ color: accentColor }} />
|
||||||
<span>Gedruckte Werke</span>
|
<span>{data.booksTitle || "Gedruckte Werke"}</span>
|
||||||
</h2>
|
</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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
|
<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
|
<button
|
||||||
key={book.id}
|
key={book.id}
|
||||||
onClick={() => setSelectedBook(book)}
|
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
|
<img
|
||||||
src={book.coverUrl || "https://images.unsplash.com/photo-1501504905252-473c47e087f8?auto=format&fit=crop&q=80&w=300"}
|
src={book.coverUrl || "https://images.unsplash.com/photo-1501504905252-473c47e087f8?auto=format&fit=crop&q=80&w=300"}
|
||||||
alt={book.title}
|
alt={book.title}
|
||||||
|
|
@ -189,29 +231,42 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
referrerPolicy="no-referrer"
|
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">
|
<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-stone-100 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-[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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Customizable Custom Section */}
|
{/* Custom Highlight Section if present */}
|
||||||
{data.customSectionContent && data.customSectionContent.trim() !== "" && (
|
{(data.customSectionTitle || data.customSectionContent) && (
|
||||||
<section id="garten-notiz" className="space-y-6 pt-8 border-t border-[#4A5D50]/20">
|
<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` }}>
|
||||||
<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">
|
|
||||||
{data.customSectionTitle && (
|
{data.customSectionTitle && (
|
||||||
<h3 className="text-xl md:text-2xl font-serif text-stone-100">
|
<h2 className="text-2xl font-serif text-stone-100 mb-4 flex items-center gap-2">
|
||||||
{data.customSectionTitle}
|
<Sparkles className="w-5 h-5" style={{ color: accentColor }} />
|
||||||
</h3>
|
<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}
|
{data.customSectionContent}
|
||||||
</p>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -385,10 +440,10 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* 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">
|
<div className="max-w-7xl mx-auto space-y-2">
|
||||||
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,48 +11,69 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
||||||
const [selectedProject, setSelectedProject] = useState<Project | 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 (
|
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 */}
|
{/* 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 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 bg-orange-500/5 rounded-full blur-3xl 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 bg-amber-500/5 rounded-full blur-3xl pointer-events-none" />
|
<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 / 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="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">
|
<div
|
||||||
<Heart className="w-3.5 h-3.5 text-orange-400 fill-orange-400/20 animate-pulse" />
|
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"
|
||||||
<span className="font-sans italic">Stilvolle Sinnlichkeit & Romantik</span>
|
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>
|
</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)]">
|
<h1 className="text-4xl md:text-6xl tracking-wide text-amber-100 uppercase font-light drop-shadow-sm font-serif">
|
||||||
{data.heroTitle || "M. S. VELVET"}
|
{data.heroTitle || data.name || "M. S. VELVET"}
|
||||||
</h1>
|
</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"}
|
{data.heroSubtitle || "ELEGANT-SINNLICHE LITERATUR"}
|
||||||
</p>
|
</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">
|
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-sans">
|
||||||
<a
|
<a
|
||||||
href="#ueber-mich"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#projekte"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#buecherregal"
|
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>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,8 +87,8 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
<div className="md:col-span-4 flex justify-center">
|
<div className="md:col-span-4 flex justify-center">
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
{/* Outer warm glow */}
|
{/* 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="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-orange-950 bg-neutral-900">
|
<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
|
<img
|
||||||
src={data.avatarUrl || "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400"}
|
src={data.avatarUrl || "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=400"}
|
||||||
alt={data.name}
|
alt={data.name}
|
||||||
|
|
@ -79,48 +100,54 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="md:col-span-8 space-y-6">
|
<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">
|
<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" />
|
<Sparkles className="w-6 h-6" style={{ color: accentColor }} />
|
||||||
<span>Zwischen Spielzeugautos und der Stille der Nacht...</span>
|
<span>{data.aboutTitle || "Das Geheimnis hinter der Feder"}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="prose prose-invert prose-stone max-w-none text-neutral-300 leading-relaxed font-sans space-y-4">
|
<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>
|
<p className="whitespace-pre-wrap italic">{data.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 text-xs">
|
<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">
|
{(data.bioTag1Label || data.bioTag1Value) && (
|
||||||
<Heart className="w-5 h-5 text-orange-450 text-orange-400 fill-orange-400/10" />
|
<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>
|
||||||
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Genre</div>
|
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag1Label || "Genre"}</div>
|
||||||
<div className="text-neutral-200 font-semibold">Spicy Romance & Slice of Life</div>
|
<div className="text-neutral-200 font-semibold">{data.bioTag1Value || "Sinnliche Romance"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Ästhetik</div>
|
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag2Label || "Tonalität"}</div>
|
||||||
<div className="text-neutral-200 font-semibold">Hautnah, cozy & ungefiltert</div>
|
<div className="text-neutral-200 font-semibold">{data.bioTag2Value || "Elegant & Knisternd"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">Fokus</div>
|
<div className="text-neutral-500 uppercase tracking-wider text-[10px]">{data.bioTag3Label || "Identität"}</div>
|
||||||
<div className="text-neutral-200 font-semibold">Bedingungsloses Vertrauen & Intime Facetten</div>
|
<div className="text-neutral-200 font-semibold">{data.bioTag3Value || "Diskret / Pseudonym"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Dynamic ongoing work writing progress */}
|
{/* Dynamic ongoing work writing progress */}
|
||||||
<section id="projekte" className="space-y-8">
|
<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>
|
<div>
|
||||||
<h2 className="text-2xl md:text-3xl font-serif text-amber-100 flex items-center gap-3">
|
<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" />
|
<Heart className="w-6 h-6" style={{ color: accentColor }} />
|
||||||
<span>In der Entstehung...</span>
|
<span>{data.projectsTitle || "In der Entstehung..."}</span>
|
||||||
</h2>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -129,11 +156,12 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
<button
|
<button
|
||||||
key={project.id}
|
key={project.id}
|
||||||
onClick={() => setSelectedProject(project)}
|
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="space-y-4 w-full">
|
||||||
<div className="flex items-center justify-between">
|
<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"}
|
{project.status || "In Arbeit"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-neutral-400 italic">
|
<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">
|
<h3 className="text-lg font-serif text-amber-100 group-hover:text-orange-400 transition-colors">
|
||||||
{project.title}
|
{project.title}
|
||||||
</h3>
|
</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}
|
{project.description}
|
||||||
</p>
|
</Markdown>
|
||||||
<p className="text-orange-400 font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic">
|
</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" />
|
Details einsehen <ArrowRight className="w-3 h-3" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Luxurious peach-golden loading progress meter */}
|
|
||||||
<div className="mt-6 space-y-1.5 w-full">
|
<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-1 w-full bg-neutral-900 rounded-full overflow-hidden">
|
||||||
<div
|
<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)]"
|
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -164,14 +205,14 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Bookshelf display, strictly elegant and minimalist */}
|
{/* Bookshelf display */}
|
||||||
<section id="buecherregal" className="space-y-8">
|
<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">
|
<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" />
|
<BookOpen className="w-6 h-6" style={{ color: secondaryColor }} />
|
||||||
<span>Das Samtregal</span>
|
<span>{data.booksTitle || "Das Samtregal"}</span>
|
||||||
</h2>
|
</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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
|
<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
|
<button
|
||||||
key={book.id}
|
key={book.id}
|
||||||
onClick={() => setSelectedBook(book)}
|
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" />
|
<div className="absolute inset-0 bg-orange-950/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
|
||||||
<img
|
<img
|
||||||
|
|
@ -189,29 +231,42 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
referrerPolicy="no-referrer"
|
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">
|
<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-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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Customizable Custom Section */}
|
{/* Custom Highlight Section if present */}
|
||||||
{data.customSectionContent && data.customSectionContent.trim() !== "" && (
|
{(data.customSectionTitle || data.customSectionContent) && (
|
||||||
<section id="exklusiv-notiz" className="space-y-6 pt-8 border-t border-orange-950/20">
|
<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` }}>
|
||||||
<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">
|
|
||||||
{data.customSectionTitle && (
|
{data.customSectionTitle && (
|
||||||
<h3 className="text-xl md:text-2xl font-serif text-amber-100">
|
<h2 className="text-2xl font-serif text-amber-100 mb-4 flex items-center gap-2">
|
||||||
{data.customSectionTitle}
|
<Sparkles className="w-5 h-5" style={{ color: accentColor }} />
|
||||||
</h3>
|
<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}
|
{data.customSectionContent}
|
||||||
</p>
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -385,10 +440,10 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* 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">
|
<div className="max-w-7xl mx-auto space-y-2">
|
||||||
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
439
src/components/ReneePortfolio.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,49 +11,79 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
const [selectedBook, setSelectedBook] = useState<Book | null>(null);
|
||||||
const [selectedProject, setSelectedProject] = useState<Project | 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 (
|
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">
|
<div
|
||||||
{/* Decorative starry background with magical purple aura */}
|
className={`min-h-screen text-slate-100 ${fontClass} selection:bg-cyan-500 selection:text-white overflow-x-hidden relative`}
|
||||||
<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" />
|
style={{ backgroundColor: bgColor }}
|
||||||
<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" />
|
{/* 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 */}
|
{/* 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">
|
<div className="text-center max-w-4xl mx-auto">
|
||||||
{/* Subtle magical/futuristic accent */}
|
{/* 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">
|
<div
|
||||||
<Star className="w-3 h-3 text-cyan-400 animate-pulse" />
|
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"
|
||||||
<span>Cyber-Mystisches Archive</span>
|
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>
|
</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">
|
<h1 className="text-5xl md:text-7xl tracking-tight text-white mb-4 uppercase drop-shadow-md font-bold">
|
||||||
{data.heroTitle || "DANIEL HESSE"}
|
{data.heroTitle || data.name || "DANIEL HESSE"}
|
||||||
</h1>
|
</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"}
|
{data.heroSubtitle || "WELTENWANDLER & STERNENZEICHNER"}
|
||||||
</p>
|
</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 */}
|
{/* Quick Section Navigation */}
|
||||||
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-mono">
|
<nav className="mt-8 flex flex-wrap justify-center gap-3 text-xs font-mono">
|
||||||
<a
|
<a
|
||||||
href="#ueber-mich"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#projekte"
|
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>
|
||||||
<a
|
<a
|
||||||
href="#buecherregal"
|
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>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -67,8 +97,11 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
<div className="md:col-span-4 flex justify-center">
|
<div className="md:col-span-4 flex justify-center">
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
{/* Outer neon ring */}
|
{/* 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
|
||||||
<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">
|
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
|
<img
|
||||||
src={data.avatarUrl || "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&q=80&w=400"}
|
src={data.avatarUrl || "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&q=80&w=400"}
|
||||||
alt={data.name}
|
alt={data.name}
|
||||||
|
|
@ -80,50 +113,56 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
</div>
|
</div>
|
||||||
<div className="md:col-span-8 space-y-6">
|
<div className="md:col-span-8 space-y-6">
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
<Compass className="w-7 h-7 text-cyan-400" />
|
<Compass className="w-7 h-7" style={{ color: accentColor }} />
|
||||||
<span>Über den Autor</span>
|
<span>{data.aboutTitle || "Über den Autor"}</span>
|
||||||
</h2>
|
</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>
|
<p className="whitespace-pre-wrap">{data.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 pt-4 font-mono text-xs">
|
<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">
|
{(data.bioTag1Label || data.bioTag1Value) && (
|
||||||
<BookOpen className="w-5 h-5 text-indigo-400" />
|
<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>
|
||||||
<div className="text-slate-500 uppercase tracking-widest">Fokus</div>
|
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag1Label || "Fokus"}</div>
|
||||||
<div className="text-white font-semibold">Sci-Fi & Fantasy</div>
|
<div className="text-white font-semibold">{data.bioTag1Value || "Sci-Fi & Fantasy"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-slate-500 uppercase tracking-widest">Welten</div>
|
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag2Label || "Welten"}</div>
|
||||||
<div className="text-white font-semibold">Techno-Magie</div>
|
<div className="text-white font-semibold">{data.bioTag2Value || "Techno-Magie"}</div>
|
||||||
</div>
|
</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>
|
||||||
<div className="text-slate-500 uppercase tracking-widest">Stil</div>
|
<div className="text-slate-500 uppercase tracking-widest">{data.bioTag3Label || "Stil"}</div>
|
||||||
<div className="text-white font-semibold">Episch & Atmosphärisch</div>
|
<div className="text-white font-semibold">{data.bioTag3Value || "Episch & Atmosphärisch"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Section 2: Aktuelle Projekte */}
|
{/* Section 2: Aktuelle Projekte */}
|
||||||
<section id="projekte" className="space-y-8">
|
<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>
|
<div>
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
<Star className="w-7 h-7 text-indigo-400" />
|
<Star className="w-7 h-7" style={{ color: accentColor }} />
|
||||||
<span>Aktuelle Projekte</span>
|
<span>{data.projectsTitle || "Aktuelle Projekte"}</span>
|
||||||
</h2>
|
</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>
|
||||||
<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
|
Laufendes Telemetrie-Tracking
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -133,33 +172,48 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
<button
|
<button
|
||||||
key={project.id}
|
key={project.id}
|
||||||
onClick={() => setSelectedProject(project)}
|
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="space-y-4 w-full">
|
||||||
<div className="flex items-center justify-between">
|
<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"}
|
{project.status || "In Arbeit"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-slate-500 font-mono">
|
<span className="text-xs text-slate-400 font-mono">
|
||||||
Progression: {project.progress}%
|
Progression: {project.progress}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-white group-hover:text-cyan-300 transition-colors">
|
<h3 className="text-xl font-bold text-white group-hover:text-cyan-300 transition-colors">
|
||||||
{project.title}
|
{project.title}
|
||||||
</h3>
|
</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}
|
{project.description}
|
||||||
</p>
|
</Markdown>
|
||||||
<p className="text-cyan-400 font-mono text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1">
|
</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" />
|
Details anzeigen <ArrowRight className="w-3 h-3" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Custom glowing progress bar */}
|
{/* Custom glowing progress bar */}
|
||||||
<div className="mt-6 space-y-1.5 w-full">
|
<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
|
<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)]"
|
className="h-full rounded-full transition-all duration-1000 ease-out shadow-sm"
|
||||||
style={{ width: `${project.progress}%` }}
|
style={{ width: `${project.progress}%`, background: `linear-gradient(to right, ${accentColor}, ${secondaryColor})` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -170,12 +224,12 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
|
|
||||||
{/* Section 3: Bücherregal */}
|
{/* Section 3: Bücherregal */}
|
||||||
<section id="buecherregal" className="space-y-8">
|
<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">
|
<h2 className="text-3xl font-bold tracking-tight text-white flex items-center gap-3">
|
||||||
<BookOpen className="w-7 h-7 text-purple-400" />
|
<BookOpen className="w-7 h-7" style={{ color: secondaryColor }} />
|
||||||
<span>Das Bücherregal</span>
|
<span>{data.booksTitle || "Das Bücherregal"}</span>
|
||||||
</h2>
|
</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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-6 justify-items-center">
|
<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
|
<button
|
||||||
key={book.id}
|
key={book.id}
|
||||||
onClick={() => setSelectedBook(book)}
|
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" />
|
<div className="absolute inset-0 bg-cyan-900/10 opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none" />
|
||||||
<img
|
<img
|
||||||
src={book.coverUrl || "https://images.unsplash.com/photo-1543002588-bfa74002ed7e?auto=format&fit=crop&q=80&w=300"}
|
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"
|
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">
|
<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-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>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
</main>
|
||||||
|
|
||||||
{/* Book details modular interface modal */}
|
{/* Book details modular interface modal */}
|
||||||
|
|
@ -379,10 +465,10 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Styled Footer */}
|
{/* 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">
|
<div className="max-w-7xl mx-auto space-y-4">
|
||||||
<p>© {new Date().getFullYear()} {data.name}. Alle Rechte vorbehalten.</p>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,35 @@ export const defaultAuthorData: AuthorData = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
customSectionTitle: "Vorträge & Messen",
|
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: {
|
erotica: {
|
||||||
name: "M. S. Velvet",
|
name: "Annie Slone",
|
||||||
avatarUrl: "https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?auto=format&fit=crop&q=80&w=300",
|
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",
|
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: [
|
projects: [
|
||||||
{
|
{
|
||||||
id: "proj_er_1",
|
id: "proj_er_1",
|
||||||
|
|
@ -97,7 +118,28 @@ export const defaultAuthorData: AuthorData = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
customSectionTitle: "Sinnliche Lesungen & Salon-Abende",
|
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: {
|
clara: {
|
||||||
name: "Clara Finch",
|
name: "Clara Finch",
|
||||||
|
|
@ -146,6 +188,97 @@ export const defaultAuthorData: AuthorData = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
customSectionTitle: "Die Philosophie des Mooses",
|
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: ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
31
src/types.ts
|
|
@ -28,12 +28,43 @@ export interface AuthorProfile {
|
||||||
books: Book[];
|
books: Book[];
|
||||||
customSectionTitle?: string;
|
customSectionTitle?: string;
|
||||||
customSectionContent?: 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 {
|
export interface AuthorData {
|
||||||
scifi: AuthorProfile;
|
scifi: AuthorProfile;
|
||||||
erotica: AuthorProfile;
|
erotica: AuthorProfile;
|
||||||
clara: AuthorProfile;
|
clara: AuthorProfile;
|
||||||
|
renee: AuthorProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminSettings {
|
export interface AdminSettings {
|
||||||
|
|
|
||||||