Modularization done #9
9 changed files with 256 additions and 53 deletions
|
|
@ -44,6 +44,7 @@ async function initDatabase(): Promise<AuthorData> {
|
||||||
const defaults = defaultAuthorData[pKey];
|
const defaults = defaultAuthorData[pKey];
|
||||||
const keysToEnsure: (keyof AuthorProfile)[] = [
|
const keysToEnsure: (keyof AuthorProfile)[] = [
|
||||||
"customSectionTitle", "customSectionContent",
|
"customSectionTitle", "customSectionContent",
|
||||||
|
"customDomain", "customPath",
|
||||||
"badgeText", "aboutTitle", "projectsTitle", "projectsSubtitle",
|
"badgeText", "aboutTitle", "projectsTitle", "projectsSubtitle",
|
||||||
"booksTitle", "booksSubtitle", "footerText",
|
"booksTitle", "booksSubtitle", "footerText",
|
||||||
"accentColor", "secondaryColor", "backgroundColor", "cardBgColor",
|
"accentColor", "secondaryColor", "backgroundColor", "cardBgColor",
|
||||||
|
|
|
||||||
66
src/App.tsx
66
src/App.tsx
|
|
@ -42,33 +42,51 @@ 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" | "renee" => {
|
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
|
||||||
if (currentPath === "/clara-finch" || currentPath.startsWith("/clara-finch/")) {
|
? p.customPath.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
||||||
return "clara";
|
: [];
|
||||||
}
|
|
||||||
if (currentPath === "/renee-heart" || currentPath.startsWith("/renee-heart/") || currentPath === "/renee" || currentPath.startsWith("/renee/")) {
|
// Statische Standard-Fallbacks
|
||||||
return "renee";
|
if (key === "erotica") customPaths.push("/sensual-moments", "/annie-slone", "/marc-velvet");
|
||||||
}
|
if (key === "clara") customPaths.push("/clara-finch", "/clara");
|
||||||
if (currentPath === "/sci-fi" || currentPath.startsWith("/sci-fi/")) {
|
if (key === "renee") customPaths.push("/renee-heart", "/renee");
|
||||||
return "scifi";
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
if (hostname.includes("clarafinch.de")) {
|
? p.customDomain.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
||||||
return "clara";
|
: [];
|
||||||
}
|
|
||||||
if (hostname.includes("reneeheart.de") || hostname.includes("renee-heart.de")) {
|
// Statische Standard-Fallbacks
|
||||||
return "renee";
|
if (key === "erotica") customDomains.push("annieslone.de", "marcvelvet.de");
|
||||||
}
|
if (key === "clara") customDomains.push("clarafinch.de", "clara-finch.de");
|
||||||
if (hostname.includes("hesse-sf.de")) {
|
if (key === "renee") customDomains.push("reneeheart.de", "renee-heart.de");
|
||||||
return "scifi";
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standard-Fallback (Daniel Hesse SciFi)
|
// Standard-Fallback (Daniel Hesse SciFi)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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
|
Palette, Type, Image as ImageIcon, Globe
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import ImagePicker from "./ImagePicker";
|
import ImagePicker from "./ImagePicker";
|
||||||
|
|
||||||
|
|
@ -594,6 +594,43 @@ export default function AdminPanel({ onLogout }: AdminPanelProps) {
|
||||||
/>
|
/>
|
||||||
</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>
|
||||||
|
|
@ -616,7 +653,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 || ""}
|
||||||
|
|
@ -1052,7 +1089,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 || ""}
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,23 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
<h3 className="text-lg font-serif text-stone-100 group-hover:text-stone-300 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">
|
||||||
{project.description}
|
<Markdown
|
||||||
</p>
|
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}
|
||||||
|
</Markdown>
|
||||||
|
</div>
|
||||||
<p className="font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic" style={{ color: accentColor }}>
|
<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>
|
||||||
|
|
@ -230,8 +244,21 @@ export default function ClaraPortfolio({ data }: ClaraPortfolioProps) {
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{data.customSectionContent && (
|
{data.customSectionContent && (
|
||||||
<div className="text-stone-300 text-sm md:text-base leading-relaxed whitespace-pre-wrap italic">
|
<div className="text-stone-300 text-sm md:text-base leading-relaxed italic markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{data.customSectionContent}
|
<Markdown
|
||||||
|
components={{
|
||||||
|
a: ({ node, ...props }) => (
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline text-[#8FA797] hover:text-stone-200 font-medium"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.customSectionContent}
|
||||||
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,23 @@ 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">
|
||||||
{project.description}
|
<Markdown
|
||||||
</p>
|
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}
|
||||||
|
</Markdown>
|
||||||
|
</div>
|
||||||
<p className="font-sans text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1 italic" style={{ color: accentColor }}>
|
<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>
|
||||||
|
|
@ -230,8 +244,21 @@ export default function EroticaPortfolio({ data }: EroticaPortfolioProps) {
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{data.customSectionContent && (
|
{data.customSectionContent && (
|
||||||
<div className="text-neutral-300 text-sm md:text-base leading-relaxed whitespace-pre-wrap italic">
|
<div className="text-neutral-300 text-sm md:text-base leading-relaxed italic markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{data.customSectionContent}
|
<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}
|
||||||
|
</Markdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
import { AuthorProfile, Book, Project } from "../types";
|
import { AuthorProfile, Book, Project } from "../types";
|
||||||
import {
|
import {
|
||||||
Heart, Sparkles, BookOpen, ExternalLink, X, ArrowRight, Sun, Music
|
Heart, Sparkles, BookOpen, ExternalLink, X, ArrowRight, Sun, Music
|
||||||
|
|
@ -165,9 +166,23 @@ export default function ReneePortfolio({ data }: ReneePortfolioProps) {
|
||||||
<h3 className="text-lg font-bold text-white group-hover:text-yellow-300 transition-colors">
|
<h3 className="text-lg font-bold text-white group-hover:text-yellow-300 transition-colors">
|
||||||
{project.title}
|
{project.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-300 text-sm line-clamp-3">
|
<div className="text-slate-300 text-sm line-clamp-3 markdown-body [&_p]:inline [&_p]:m-0">
|
||||||
{project.description}
|
<Markdown
|
||||||
</p>
|
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 }}>
|
<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" />
|
Details einsehen <ArrowRight className="w-3 h-3" />
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -232,8 +247,21 @@ export default function ReneePortfolio({ data }: ReneePortfolioProps) {
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{data.customSectionContent && (
|
{data.customSectionContent && (
|
||||||
<div className="text-slate-300 text-sm md:text-base leading-relaxed whitespace-pre-wrap">
|
<div className="text-slate-300 text-sm md:text-base leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{data.customSectionContent}
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -283,9 +311,22 @@ export default function ReneePortfolio({ data }: ReneePortfolioProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-slate-300 text-sm leading-relaxed whitespace-pre-wrap">
|
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{selectedBook.description}
|
<Markdown
|
||||||
</p>
|
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 */}
|
{/* Spotify Playlist player if configured */}
|
||||||
{selectedBook.spotifyPlaylistId && (
|
{selectedBook.spotifyPlaylistId && (
|
||||||
|
|
@ -359,9 +400,22 @@ export default function ReneePortfolio({ data }: ReneePortfolioProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-slate-300 text-sm leading-relaxed whitespace-pre-wrap">
|
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{selectedProject.description}
|
<Markdown
|
||||||
</p>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -181,9 +181,23 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
<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-300 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">
|
||||||
{project.description}
|
<Markdown
|
||||||
</p>
|
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}
|
||||||
|
</Markdown>
|
||||||
|
</div>
|
||||||
<p className="font-mono text-[11px] mt-2 group-hover:translate-x-1 transition-transform flex items-center gap-1" style={{ color: accentColor }}>
|
<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>
|
||||||
|
|
@ -250,8 +264,21 @@ export default function SciFiPortfolio({ data }: SciFiPortfolioProps) {
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
{data.customSectionContent && (
|
{data.customSectionContent && (
|
||||||
<div className="text-slate-300 text-sm leading-relaxed whitespace-pre-wrap">
|
<div className="text-slate-300 text-sm leading-relaxed markdown-body [&_p]:mb-4 [&_p:last-child]:mb-0">
|
||||||
{data.customSectionContent}
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ export const defaultAuthorData: AuthorData = {
|
||||||
booksTitle: "Bücherregal",
|
booksTitle: "Bücherregal",
|
||||||
booksSubtitle: "Entdecken Sie meine verlegten Romane und E-Books mit Hörbeispielen.",
|
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.",
|
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",
|
accentColor: "#06b6d4",
|
||||||
secondaryColor: "#a855f7",
|
secondaryColor: "#a855f7",
|
||||||
backgroundColor: "#020617",
|
backgroundColor: "#020617",
|
||||||
|
|
@ -118,6 +120,8 @@ export const defaultAuthorData: AuthorData = {
|
||||||
booksTitle: "Das Samtregal",
|
booksTitle: "Das Samtregal",
|
||||||
booksSubtitle: "Verführerische Geschichten und sinnliche Romane für besondere Stunden.",
|
booksSubtitle: "Verführerische Geschichten und sinnliche Romane für besondere Stunden.",
|
||||||
footerText: "Mit Liebe und Leidenschaft geschrieben. Alle Werke geschützt.",
|
footerText: "Mit Liebe und Leidenschaft geschrieben. Alle Werke geschützt.",
|
||||||
|
customDomain: "annieslone.de, marcvelvet.de",
|
||||||
|
customPath: "/sensual-moments, /annie-slone, /marc-velvet",
|
||||||
accentColor: "#f97316",
|
accentColor: "#f97316",
|
||||||
secondaryColor: "#f59e0b",
|
secondaryColor: "#f59e0b",
|
||||||
backgroundColor: "#0a0a0a",
|
backgroundColor: "#0a0a0a",
|
||||||
|
|
@ -180,6 +184,8 @@ export const defaultAuthorData: AuthorData = {
|
||||||
booksTitle: "Veröffentlichte Bände",
|
booksTitle: "Veröffentlichte Bände",
|
||||||
booksSubtitle: "Lyrikbände und Naturprosa zum Verweilen und Innehalten.",
|
booksSubtitle: "Lyrikbände und Naturprosa zum Verweilen und Innehalten.",
|
||||||
footerText: "Gedruckt auf virtuellem Pergament. Stille Notizen aus Wald und Flur.",
|
footerText: "Gedruckt auf virtuellem Pergament. Stille Notizen aus Wald und Flur.",
|
||||||
|
customDomain: "clarafinch.de, clara-finch.de",
|
||||||
|
customPath: "/clara-finch, /clara",
|
||||||
accentColor: "#10b981",
|
accentColor: "#10b981",
|
||||||
secondaryColor: "#84cc16",
|
secondaryColor: "#84cc16",
|
||||||
backgroundColor: "#064e3b",
|
backgroundColor: "#064e3b",
|
||||||
|
|
@ -242,6 +248,8 @@ export const defaultAuthorData: AuthorData = {
|
||||||
booksTitle: "Gefühlvolle Romane",
|
booksTitle: "Gefühlvolle Romane",
|
||||||
booksSubtitle: "Geschichten zum Mitfühlen, Träumen und Genießen.",
|
booksSubtitle: "Geschichten zum Mitfühlen, Träumen und Genießen.",
|
||||||
footerText: "Geschrieben mit viel Gefühl und Herzblut.",
|
footerText: "Geschrieben mit viel Gefühl und Herzblut.",
|
||||||
|
customDomain: "reneeheart.de, renee-heart.de",
|
||||||
|
customPath: "/renee-heart, /renee",
|
||||||
accentColor: "#eab308",
|
accentColor: "#eab308",
|
||||||
secondaryColor: "#ec4899",
|
secondaryColor: "#ec4899",
|
||||||
backgroundColor: "#0f172a",
|
backgroundColor: "#0f172a",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ export interface AuthorProfile {
|
||||||
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
|
// Custom static texts
|
||||||
badgeText?: string;
|
badgeText?: string;
|
||||||
aboutTitle?: string;
|
aboutTitle?: string;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue