Key-Features now editable
Section headers editable Added markdown to text fields
This commit is contained in:
parent
4f6a31dfa3
commit
302bdff052
8 changed files with 347 additions and 23 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { X, Lock, Save, RotateCcw, Download, Upload, Plus, Trash2, Check, Sparkles, Layout, BookOpen, Image as ImageIcon, Newspaper, BarChart3, KeyRound, AlertCircle } from "lucide-react";
|
||||
import { X, Lock, Save, RotateCcw, Download, Upload, Plus, Trash2, Check, Sparkles, Layout, BookOpen, Image as ImageIcon, Newspaper, BarChart3, KeyRound, AlertCircle, FileText } from "lucide-react";
|
||||
import { CMSData, StoryTab, ScreenshotItem, NewsPost, DevCategoryProgress, Milestone } from "../types";
|
||||
|
||||
// Helper function to process and compress uploaded images to sharp Base64 Data URLs
|
||||
|
|
@ -170,7 +170,7 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
|
||||
const [pinInput, setPinInput] = useState("");
|
||||
const [pinError, setPinError] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<"hero" | "story" | "gallery" | "news" | "progress" | "backup">("hero");
|
||||
const [activeTab, setActiveTab] = useState<"hero" | "sections" | "story" | "gallery" | "news" | "progress" | "backup">("hero");
|
||||
|
||||
// Local draft state for editing - sync when cms updates
|
||||
const [formData, setFormData] = useState<CMSData>(cms);
|
||||
|
|
@ -460,6 +460,18 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
<span>Allgemein & Hero</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setActiveTab("sections")}
|
||||
className={`w-full flex items-center gap-2.5 px-3.5 py-2.5 rounded-lg font-serif text-xs font-semibold tracking-wider transition-colors text-left shrink-0 ${
|
||||
activeTab === "sections"
|
||||
? "bg-amber-900/60 text-amber-100 border border-amber-600/50"
|
||||
: "text-zinc-400 hover:text-amber-200 hover:bg-zinc-900"
|
||||
}`}
|
||||
>
|
||||
<FileText className="w-4 h-4 text-amber-400" />
|
||||
<span>Sektionen & Überschriften</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setActiveTab("story")}
|
||||
className={`w-full flex items-center gap-2.5 px-3.5 py-2.5 rounded-lg font-serif text-xs font-semibold tracking-wider transition-colors text-left shrink-0 ${
|
||||
|
|
@ -624,6 +636,146 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB SECTIONS & OVERVIEW TITLES */}
|
||||
{activeTab === "sections" && (
|
||||
<div className="space-y-6">
|
||||
<div className="border-b border-amber-900/40 pb-2">
|
||||
<h3 className="text-lg font-serif font-bold text-amber-200">
|
||||
Sektionen & Überschriften anpassen
|
||||
</h3>
|
||||
<p className="text-xs text-zinc-400 mt-1">
|
||||
Hier kannst du alle Haupt-Überschriften und Kopfzeilen (Eyebrows) der einzelnen Bereiche der Webseite frei anpassen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Story Section */}
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-lg p-4 space-y-3">
|
||||
<h4 className="text-xs font-mono font-bold text-amber-400 uppercase">1. Story & Tabs Sektion</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Kopfzeile (Eyebrow)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.storyEyebrow ?? "Projekt-Informationen"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), storyEyebrow: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Hauptüberschrift</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.storyTitle ?? "Die Welt, Features & Fraktionen"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), storyTitle: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Gallery Section */}
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-lg p-4 space-y-3">
|
||||
<h4 className="text-xs font-mono font-bold text-amber-400 uppercase">2. Screenshots & Galerie Sektion</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Kopfzeile (Eyebrow)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.galleryEyebrow ?? "Impressionen & Ingame-Showcase"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), galleryEyebrow: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Hauptüberschrift (z.B. Screenshots aus ...)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.galleryTitle ?? "Screenshots aus Khorinis"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), galleryTitle: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* News Section */}
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-lg p-4 space-y-3">
|
||||
<h4 className="text-xs font-mono font-bold text-amber-400 uppercase">3. News & Blog Sektion</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Kopfzeile (Eyebrow)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.newsEyebrow ?? "Neuigkeiten & Entwickler-Tagebuch"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), newsEyebrow: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Hauptüberschrift</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.newsTitle ?? "Aktuelle News & Blog"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), newsTitle: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Section */}
|
||||
<div className="bg-zinc-950 border border-zinc-800 rounded-lg p-4 space-y-3">
|
||||
<h4 className="text-xs font-mono font-bold text-amber-400 uppercase">4. Entwicklungsfortschritt Sektion</h4>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Kopfzeile (Eyebrow)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.progressEyebrow ?? "Transparente Entwicklung"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), progressEyebrow: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Hauptüberschrift</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.sectionTitles?.progressTitle ?? "Entwicklungsfortschritt"}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
sectionTitles: { ...(formData.sectionTitles || {}), progressTitle: e.target.value }
|
||||
})}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-sm text-zinc-100 focus:border-amber-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAB 2: STORY & TABS */}
|
||||
{activeTab === "story" && (
|
||||
<div className="space-y-6">
|
||||
|
|
@ -639,6 +791,14 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{/* Markdown Info Banner */}
|
||||
<div className="bg-amber-950/40 border border-amber-800/50 rounded-lg p-3 text-xs text-amber-200/90 flex items-start gap-2">
|
||||
<Sparkles className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<strong>Vollständiges Markdown unterstützt:</strong> Du kannst in allen Inhalts- und News-Feldern Markdown nutzen (z.B. <code>**Fett**</code>, <code>*Kursiv*</code>, <code>- Aufzählungspunkte</code>, <code>1. Nummerierte Listen</code>, <code>### Überschriften</code> etc.).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formData.tabs.map((tab, idx) => (
|
||||
<div key={tab.id} className="bg-zinc-950 border border-zinc-800 rounded-lg p-4 space-y-3 relative">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
|
|
@ -679,7 +839,9 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">Haupttext</label>
|
||||
<label className="block text-[11px] font-mono text-zinc-400 mb-1">
|
||||
Haupttext (Markdown unterstützt)
|
||||
</label>
|
||||
<textarea
|
||||
rows={4}
|
||||
value={tab.content}
|
||||
|
|
@ -687,6 +849,39 @@ export const AdminCMSModal: React.FC<AdminCMSModalProps> = ({
|
|||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-sm text-zinc-200"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Key Features / Kernaspekte Box Editor */}
|
||||
<div className="pt-3 border-t border-zinc-800 space-y-3">
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-amber-400 mb-1">
|
||||
Kernaspekte-Kasten Überschrift
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Wichtigste Merkmale & Kernaspekte"
|
||||
value={tab.keyFeaturesTitle ?? "Wichtigste Merkmale & Kernaspekte"}
|
||||
onChange={(e) => handleTabChange(idx, "keyFeaturesTitle", e.target.value)}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-1.5 text-xs text-amber-200 font-mono"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-[11px] font-mono text-amber-400 mb-1 flex items-center justify-between">
|
||||
<span>Kernaspekte / Highlights (Ein Punkt pro Zeile)</span>
|
||||
<span className="text-[10px] text-zinc-500 font-sans">Unterstützt Markdown</span>
|
||||
</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="Merkmal 1 Merkmal 2 Merkmal 3"
|
||||
value={(tab.highlights || []).join("\n")}
|
||||
onChange={(e) => {
|
||||
const lines = e.target.value.split("\n");
|
||||
handleTabChange(idx, "highlights", lines);
|
||||
}}
|
||||
className="w-full bg-zinc-900 border border-zinc-700 rounded px-3 py-2 text-xs text-zinc-200 font-mono"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -49,14 +49,17 @@ export const GallerySection: React.FC<GallerySectionProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const galleryEyebrow = cms.sectionTitles?.galleryEyebrow || "Impressionen & Ingame-Showcase";
|
||||
const galleryTitle = cms.sectionTitles?.galleryTitle || "Screenshots aus Khorinis";
|
||||
|
||||
return (
|
||||
<section id="screenshots" className="py-20 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto relative">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-xs font-mono font-bold uppercase tracking-widest text-amber-500/80 block mb-2">
|
||||
Impressionen & Ingame-Showcase
|
||||
{galleryEyebrow}
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-serif font-extrabold text-amber-100 tracking-wide mb-4">
|
||||
Screenshots aus Oakhaven
|
||||
{galleryTitle}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-amber-600 to-transparent mx-auto rounded-full" />
|
||||
</div>
|
||||
|
|
|
|||
87
src/components/MarkdownRenderer.tsx
Normal file
87
src/components/MarkdownRenderer.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
interface MarkdownRendererProps {
|
||||
content?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content = "", className = "" }) => {
|
||||
if (!content) return null;
|
||||
|
||||
return (
|
||||
<div className={`markdown-content ${className}`}>
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-xl font-serif font-bold text-amber-200 mt-4 mb-2 border-b border-amber-900/40 pb-1">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-lg font-serif font-bold text-amber-300 mt-3 mb-2">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-base font-serif font-semibold text-amber-400 mt-2 mb-1">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<p className="mb-3 leading-relaxed font-sans text-zinc-300">
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc list-inside my-3 space-y-1.5 pl-2 text-zinc-300">
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal list-inside my-3 space-y-1.5 pl-2 text-zinc-300">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="text-amber-100/90 leading-relaxed font-sans inline-block w-full">
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-bold text-amber-200">
|
||||
{children}
|
||||
</strong>
|
||||
),
|
||||
em: ({ children }) => (
|
||||
<em className="italic text-amber-300/90 font-serif">
|
||||
{children}
|
||||
</em>
|
||||
),
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-amber-600 bg-amber-950/30 italic my-3 py-2 px-4 rounded-r text-amber-200/90 font-serif">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
code: ({ children }) => (
|
||||
<code className="bg-zinc-950 text-amber-400 font-mono text-xs px-1.5 py-0.5 rounded border border-amber-900/50">
|
||||
{children}
|
||||
</code>
|
||||
),
|
||||
a: ({ href, children }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-amber-400 hover:text-amber-200 underline decoration-amber-600 underline-offset-2 transition-colors"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { Newspaper, Calendar, User, Tag, ChevronRight, X, Plus, Edit3 } from "lucide-react";
|
||||
import { CMSData, NewsPost } from "../types";
|
||||
import { MarkdownRenderer } from "./MarkdownRenderer";
|
||||
|
||||
interface NewsSectionProps {
|
||||
cms: CMSData;
|
||||
|
|
@ -15,14 +16,17 @@ export const NewsSection: React.FC<NewsSectionProps> = ({
|
|||
}) => {
|
||||
const [activeNewsModal, setActiveNewsModal] = useState<NewsPost | null>(null);
|
||||
|
||||
const newsEyebrow = cms.sectionTitles?.newsEyebrow || "Neuigkeiten & Entwickler-Tagebuch";
|
||||
const newsTitle = cms.sectionTitles?.newsTitle || "Aktuelle News & Blog";
|
||||
|
||||
return (
|
||||
<section id="news" className="py-20 px-4 sm:px-6 lg:px-8 max-w-6xl mx-auto relative">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-xs font-mono font-bold uppercase tracking-widest text-amber-500/80 block mb-2">
|
||||
Neuigkeiten & Entwickler-Tagebuch
|
||||
{newsEyebrow}
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-serif font-extrabold text-amber-100 tracking-wide mb-4">
|
||||
Aktuelle News & Blog
|
||||
{newsTitle}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-amber-600 to-transparent mx-auto rounded-full" />
|
||||
</div>
|
||||
|
|
@ -58,9 +62,9 @@ export const NewsSection: React.FC<NewsSectionProps> = ({
|
|||
{post.title}
|
||||
</h3>
|
||||
|
||||
<p className="text-zinc-300 text-sm leading-relaxed mb-6 flex-1 line-clamp-3">
|
||||
{post.summary}
|
||||
</p>
|
||||
<div className="text-zinc-300 text-sm leading-relaxed mb-6 flex-1 line-clamp-3">
|
||||
<MarkdownRenderer content={post.summary} />
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-zinc-800/80 flex items-center justify-between mt-auto">
|
||||
<span className="text-xs text-zinc-400 font-mono flex items-center gap-1">
|
||||
|
|
@ -125,8 +129,8 @@ export const NewsSection: React.FC<NewsSectionProps> = ({
|
|||
</div>
|
||||
)}
|
||||
|
||||
<div className="overflow-y-auto pr-2 space-y-4 text-zinc-300 leading-relaxed font-sans text-base whitespace-pre-line flex-1">
|
||||
{activeNewsModal.content}
|
||||
<div className="overflow-y-auto pr-2 text-zinc-300 leading-relaxed font-sans text-base flex-1">
|
||||
<MarkdownRenderer content={activeNewsModal.content} />
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-zinc-800 flex justify-end">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Code, Compass, Mic, Box, Swords, CheckCircle2, Clock, Calendar, Edit3, Sparkles } from "lucide-react";
|
||||
import { CMSData, DevCategoryProgress, Milestone } from "../types";
|
||||
import { MarkdownRenderer } from "./MarkdownRenderer";
|
||||
|
||||
interface ProgressSectionProps {
|
||||
cms: CMSData;
|
||||
|
|
@ -28,6 +29,9 @@ export const ProgressSection: React.FC<ProgressSectionProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const progressEyebrow = cms.sectionTitles?.progressEyebrow || "Transparente Entwicklung";
|
||||
const progressTitle = cms.sectionTitles?.progressTitle || "Entwicklungsfortschritt";
|
||||
|
||||
const getStatusBadge = (status: Milestone["status"]) => {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
|
|
@ -58,10 +62,10 @@ export const ProgressSection: React.FC<ProgressSectionProps> = ({
|
|||
<section id="progress" className="py-20 px-4 sm:px-6 lg:px-8 max-w-6xl mx-auto relative">
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-xs font-mono font-bold uppercase tracking-widest text-amber-500/80 block mb-2">
|
||||
Transparente Entwicklung
|
||||
{progressEyebrow}
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-serif font-extrabold text-amber-100 tracking-wide mb-4">
|
||||
Entwicklungsfortschritt
|
||||
{progressTitle}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-amber-600 to-transparent mx-auto rounded-full" />
|
||||
</div>
|
||||
|
|
@ -116,7 +120,9 @@ export const ProgressSection: React.FC<ProgressSectionProps> = ({
|
|||
<h4 className="font-serif font-semibold text-amber-100 text-base">
|
||||
{cat.name}
|
||||
</h4>
|
||||
<p className="text-xs text-zinc-400">{cat.details}</p>
|
||||
<div className="text-xs text-zinc-400">
|
||||
<MarkdownRenderer content={cat.details} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="font-mono font-bold text-amber-400 text-base">
|
||||
|
|
@ -181,7 +187,9 @@ export const ProgressSection: React.FC<ProgressSectionProps> = ({
|
|||
</h4>
|
||||
<span className="text-xs font-mono text-amber-500/80">({ms.date})</span>
|
||||
</div>
|
||||
<p className="text-zinc-400 text-xs">{ms.description}</p>
|
||||
<div className="text-zinc-400 text-xs">
|
||||
<MarkdownRenderer content={ms.description} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="shrink-0">{getStatusBadge(ms.status)}</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState } from "react";
|
||||
import { BookOpen, Sparkles, Shield, HelpCircle, CheckCircle2, FileText, Plus, Edit3 } from "lucide-react";
|
||||
import { CMSData, StoryTab } from "../types";
|
||||
import { MarkdownRenderer } from "./MarkdownRenderer";
|
||||
|
||||
interface StoryTabsSectionProps {
|
||||
cms: CMSData;
|
||||
|
|
@ -32,15 +33,18 @@ export const StoryTabsSection: React.FC<StoryTabsSectionProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const storyEyebrow = cms.sectionTitles?.storyEyebrow || "Projekt-Informationen";
|
||||
const storyTitle = cms.sectionTitles?.storyTitle || "Die Welt, Features & Fraktionen";
|
||||
|
||||
return (
|
||||
<section id="story" className="py-20 px-4 sm:px-6 lg:px-8 max-w-6xl mx-auto relative">
|
||||
{/* Medieval Header */}
|
||||
<div className="text-center mb-12">
|
||||
<span className="text-xs font-mono font-bold uppercase tracking-widest text-amber-500/80 block mb-2">
|
||||
Projekt-Informationen
|
||||
{storyEyebrow}
|
||||
</span>
|
||||
<h2 className="text-3xl sm:text-4xl font-serif font-extrabold text-amber-100 tracking-wide mb-4">
|
||||
Die Welt, Features & Fraktionen
|
||||
{storyTitle}
|
||||
</h2>
|
||||
<div className="w-24 h-1 bg-gradient-to-r from-transparent via-amber-600 to-transparent mx-auto rounded-full" />
|
||||
</div>
|
||||
|
|
@ -99,22 +103,24 @@ export const StoryTabsSection: React.FC<StoryTabsSectionProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
{/* Main Content Paragraphs */}
|
||||
<div className="text-zinc-300 leading-relaxed space-y-4 font-sans text-base whitespace-pre-line">
|
||||
{activeTab.content}
|
||||
{/* Main Content Paragraphs (Markdown supported) */}
|
||||
<div className="text-zinc-300 leading-relaxed font-sans text-base">
|
||||
<MarkdownRenderer content={activeTab.content} />
|
||||
</div>
|
||||
|
||||
{/* Key Highlights Bullet Box */}
|
||||
{activeTab.highlights && activeTab.highlights.length > 0 && (
|
||||
<div className="mt-8 pt-6 border-t border-zinc-800/80 bg-zinc-950/60 rounded-lg p-5 border-l-4 border-l-amber-600">
|
||||
<h4 className="text-xs font-mono font-bold uppercase tracking-widest text-amber-400 mb-3">
|
||||
Wichtigste Merkmale & Kernaspekte
|
||||
{activeTab.keyFeaturesTitle || "Wichtigste Merkmale & Kernaspekte"}
|
||||
</h4>
|
||||
<ul className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
|
||||
{activeTab.highlights.map((item, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2.5 text-sm text-amber-100/90">
|
||||
<CheckCircle2 className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
|
||||
<span>{item}</span>
|
||||
<div className="flex-1">
|
||||
<MarkdownRenderer content={item} />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -223,5 +223,15 @@ Wenn der Held tiefer in die Stollen vordringt, verfinstert sich die Atmosphäre
|
|||
intensity: 40,
|
||||
enabled: true
|
||||
},
|
||||
sectionTitles: {
|
||||
storyEyebrow: "Projekt-Informationen",
|
||||
storyTitle: "Die Welt, Features & Fraktionen",
|
||||
galleryEyebrow: "Impressionen & Ingame-Showcase",
|
||||
galleryTitle: "Screenshots aus Khorinis",
|
||||
newsEyebrow: "Neuigkeiten & Entwickler-Tagebuch",
|
||||
newsTitle: "Aktuelle News & Blog",
|
||||
progressEyebrow: "Transparente Entwicklung",
|
||||
progressTitle: "Entwicklungsfortschritt",
|
||||
},
|
||||
adminPin: "gothic2026"
|
||||
};
|
||||
|
|
|
|||
11
src/types.ts
11
src/types.ts
|
|
@ -4,6 +4,7 @@ export interface StoryTab {
|
|||
iconName: string; // lucide icon identifier
|
||||
content: string;
|
||||
highlights?: string[];
|
||||
keyFeaturesTitle?: string;
|
||||
}
|
||||
|
||||
export interface ScreenshotItem {
|
||||
|
|
@ -63,5 +64,15 @@ export interface CMSData {
|
|||
intensity: number; // 0 to 100
|
||||
enabled: boolean;
|
||||
};
|
||||
sectionTitles?: {
|
||||
storyEyebrow?: string;
|
||||
storyTitle?: string;
|
||||
galleryEyebrow?: string;
|
||||
galleryTitle?: string;
|
||||
newsEyebrow?: string;
|
||||
newsTitle?: string;
|
||||
progressEyebrow?: string;
|
||||
progressTitle?: string;
|
||||
};
|
||||
adminPin: string;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue