diff --git a/frontend/src/markdown.jsx b/frontend/src/markdown.jsx
index dc19c0a..0b53339 100644
--- a/frontend/src/markdown.jsx
+++ b/frontend/src/markdown.jsx
@@ -1,5 +1,6 @@
// Mini-Markdown-Renderer für Devlog-Prosa (Discord-Stil): **fett**, *kursiv*,
-// `code`, [Links](url). Bewusst ohne Library — Input wird escaped, kein HTML-Injection.
+// `code`, [Links](url), * Bullet-Listen, --- Trenner, ## Überschriften.
+// Bewusst ohne Library — Input wird nie als HTML interpretiert, kein Injection.
const TOKEN = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^\s)]+\))/g;
function renderInline(text) {
@@ -26,16 +27,78 @@ function renderInline(text) {
});
}
-/** Devlog-Text als React-Elemente (Absätze + Inline-Formatierung) */
+/** Devlog-Text als React-Elemente: Absätze, Listen, Trenner, Überschriften */
export function Markdown({ text }) {
- return text.split(/\n{2,}/).map((block, i) => (
-
- {block.split('\n').map((line, j, arr) => (
-
- {renderInline(line)}
- {j < arr.length - 1 &&
}
-
- ))}
-
- ));
+ const out = [];
+ let para = [];
+ let list = [];
+ let key = 0;
+
+ const flushPara = () => {
+ if (para.length === 0) return;
+ out.push(
+
+ {para.map((line, j) => (
+
+ {renderInline(line)}
+ {j < para.length - 1 &&
}
+
+ ))}
+
+ );
+ para = [];
+ };
+ const flushList = () => {
+ if (list.length === 0) return;
+ out.push(
+
+ {list.map((item, j) => (
+ - {renderInline(item)}
+ ))}
+
+ );
+ list = [];
+ };
+
+ for (const raw of text.split('\n')) {
+ const line = raw.trim();
+
+ if (!line) {
+ flushPara();
+ flushList();
+ continue;
+ }
+ // --- → Trenner
+ if (/^-{3,}$/.test(line)) {
+ flushPara();
+ flushList();
+ out.push(
);
+ continue;
+ }
+ // * Punkt / - Punkt → Liste (aber nicht *kursiv am Zeilenanfang*)
+ const li = line.match(/^[*-]\s+(.*)$/);
+ if (li) {
+ flushPara();
+ list.push(li[1]);
+ continue;
+ }
+ // ## Überschrift (1-3 Raute-Ebenen)
+ const heading = line.match(/^(#{1,3})\s+(.*)$/);
+ if (heading) {
+ flushPara();
+ flushList();
+ out.push(
+
+ {renderInline(heading[2])}
+
+ );
+ continue;
+ }
+ flushList();
+ para.push(raw);
+ }
+ flushPara();
+ flushList();
+
+ return out;
}
diff --git a/frontend/src/style.css b/frontend/src/style.css
index f3aef19..e78727f 100644
--- a/frontend/src/style.css
+++ b/frontend/src/style.css
@@ -300,6 +300,39 @@ body::after {
padding: .06rem .4rem;
color: var(--success);
}
+.card-body .md-list {
+ margin: .55rem 0;
+ padding-left: 1.2rem;
+ list-style: none;
+}
+.card-body .md-list li {
+ position: relative;
+ padding-left: .2rem;
+ margin: .25rem 0;
+}
+.card-body .md-list li::before {
+ content: '▪';
+ position: absolute;
+ left: -1.1rem;
+ color: var(--neon);
+ font-size: .8em;
+ top: .15em;
+}
+.card-body .md-hr {
+ border: none;
+ height: 1px;
+ background: linear-gradient(to right, var(--border), transparent);
+ margin: 1.1rem 0;
+}
+.card-body .md-h {
+ font-family: var(--mono);
+ font-size: .78rem;
+ letter-spacing: .2em;
+ text-transform: uppercase;
+ color: var(--neon);
+ margin: 1rem 0 .4rem;
+}
+.card-body .md-h2, .card-body .md-h3 { color: var(--muted); }
/* Bildergalerie (heruntergeladene Devlog-Screenshots) */
.card-images {