diff --git a/Core.js b/Core.js
new file mode 100644
index 0000000..c3441b3
--- /dev/null
+++ b/Core.js
@@ -0,0 +1,380 @@
+/**
+ * SimonUp Core Compiler (Core.js)
+ * Eine moderne, fluss- und blockorientierte Auszeichnungssprache.
+ */
+class SimonUp {
+ constructor() {
+ this.plugins = [];
+ this.headings = []; // Speichert alle gefundenen Überschriften für das TOC
+ this.counters = [0, 0, 0, 0, 0, 0]; // Zähler für die hierarchischen Ebenen 1 bis 6
+ }
+
+ /**
+ * Registriert eine Erweiterung (Plugin)
+ * @param {Object} plugin - Ein Objekt mit optionalen 'before'- und 'after'-Hooks
+ */
+ use(plugin) {
+ this.plugins.push(plugin);
+ return this; // Ermöglicht Method Chaining
+ }
+
+ /**
+ * Hauptfunktion: Wandelt SimonUp-Text (.up) in valides HTML um
+ * @param {string} text - Der rohe SimonUp-Text
+ * @returns {string} Das gerenderte HTML
+ */
+ parse(text) {
+ // Zustand bei jedem Parse-Vorgang zurücksetzen
+ this.headings = [];
+ this.counters = [0, 0, 0, 0, 0, 0];
+
+ let html = text;
+
+ // 1. Vorverarbeitung durch Plugins
+ for (const plugin of this.plugins) {
+ if (plugin.before) html = plugin.before(html);
+ }
+
+ // 2. Block-Parsing (Strukturelle Blöcke wie &t, &toc, &-, etc.)
+ html = this._parseBlocks(html);
+
+ // 3. Nachverarbeitung des Gesamtergebnisses & TOC-Injektion
+ html = this._injectTOC(html);
+
+ // 4. Nachverarbeitung durch Plugins
+ for (const plugin of this.plugins) {
+ if (plugin.after) html = plugin.after(html);
+ }
+
+ return html;
+ }
+
+ /**
+ * INTERN: Verarbeitet strukturelle Blöcke zeilenweise
+ */
+ _parseBlocks(text) {
+ // Normalisiere Zeilenumbrüche und teile den Text auf
+ const lines = text.replace(/\r\n/g, '\n').split('\n');
+ let result = [];
+ let inBlock = false;
+ let blockType = null;
+ let blockMeta = ""; // Für zusätzliche Infos in der Startzeile (z.B. "3x" oder "Titel")
+ let blockBuffer = [];
+
+ for (let i = 0; i < lines.length; i++) {
+ let line = lines[i];
+
+ // Block-Start erkennen (Zeile beginnt mit '&' und hat danach Inhalt)
+ if (!inBlock && line.startsWith('&') && line.trim().length > 1) {
+ inBlock = true;
+ const fullHeader = line.substring(1).trim();
+ const spaceIndex = fullHeader.indexOf(' ');
+
+ if (spaceIndex !== -1) {
+ blockType = fullHeader.substring(0, spaceIndex).toLowerCase();
+ blockMeta = fullHeader.substring(spaceIndex).trim();
+ } else {
+ blockType = fullHeader.toLowerCase();
+ blockMeta = "";
+ }
+ blockBuffer = [];
+ continue;
+ }
+
+ // Block-Ende erkennen (Ein einzelnes '&' auf einer eigenen Zeile)
+ if (inBlock && line.trim() === '&') {
+ result.push(this._renderBlock(blockType, blockMeta, blockBuffer));
+ inBlock = false;
+ blockType = null;
+ blockMeta = "";
+ continue;
+ }
+
+ // Inhalt sammeln oder normalen Fließtext verarbeiten
+ if (inBlock) {
+ blockBuffer.push(line);
+ } else {
+ // Horizontale Linie als blockfreie Ausnahme abfangen
+ if (line.trim() === '___') {
+ result.push('
');
+ } else {
+ // Normaler Fließtext außerhalb von Blöcken wandert in den Inline-Parser
+ result.push(this._parseInline(line));
+ }
+ }
+ }
+
+ // Am Ende leere Zeilen (Soft Breaks vs. Hard Breaks) korrekt verknüpfen
+ // Zwei aufeinanderfolgende leere Absätze erzeugen , einfache Zeilenumbrüche
+ return this._processParagraphs(result);
+ }
+
+ /**
+ * INTERN: Erzeugt das HTML für einen geschlossenen Block
+ */
+ _renderBlock(type, meta, lines) {
+ const content = lines.join('\n');
+
+ switch (type) {
+ case 'toc':
+ // Unser neuer, intelligenter TOC-Block
+ let tocHtml = `
\n`;
+ if (meta) tocHtml += `
${meta}
\n`;
+ if (content.trim()) {
+ const intro = lines.map(l => this._parseInline(l)).join('
');
+ tocHtml += `
${intro}
\n`;
+ }
+ // Ein eindeutiger HTML-Kommentar als Platzhalter für die spätere Injektion
+ tocHtml += `\n
`;
+ return tocHtml;
+
+ case '-': // Unsortierte Liste
+ return '\n' + lines.filter(l => l.trim()).map(l => `- ${this._parseInline(l)}
`).join('\n') + '\n
';
+
+ case '+': // Nummerierte Liste
+ return '\n' + lines.filter(l => l.trim()).map(l => `- ${this._parseInline(l)}
`).join('\n') + '\n
';
+
+ case 'c': // Code-Block (Ignoriert Fluss-Formatierungen)
+ const langClass = meta ? ` class="language-${meta}"` : '';
+ // Escaping für HTML-Sonderzeichen im Code-Block
+ const escapedCode = content.replace(/&/g, '&').replace(//g, '>');
+ return `${escapedCode}
`;
+
+ case 'q': // Zitatblock
+ return `${lines.map(l => this._parseInline(l)).join('
')}
`;
+
+ case 'b': // Bibeltext
+ return `${lines.map(l => this._parseInline(l)).join('
')}
`;
+
+ case 'f': // Spoiler / Fold
+ return `\n${meta || 'Details'}
\n${lines.map(l => this._parseInline(l)).join('
')}
\n `;
+
+ case 'i': // Infobox
+ return `\n${lines.map(l => this._parseInline(l)).join('
')}\n
`;
+
+ case 't': // Komplexe Multi-line-Tabellen-Logik & Galerien
+ return this._renderTable(meta, lines);
+
+ default:
+ // Fallback für unbekannte Blocktypen
+ return `${lines.map(l => this._parseInline(l)).join('
')}
`;
+ }
+ }
+
+ /**
+ * INTERN: Hilfsfunktion zum Rendern von Multi-line-Tabellen und Galerien
+ */
+ _renderTable(meta, lines) {
+ let tableHtml = '\n';
+ // Einfache Breiten-Zuweisung über CSS (z.B. "3x" oder Prozentangaben) im Stylesheet spiegeln
+ // Der Kürze halber generieren wir hier das Grundgerüst:
+ let inRow = false;
+ let currentCells = [];
+ let cellBuffer = [];
+
+ for (let line of lines) {
+ if (line.trim() === '') continue;
+
+ // Zerlege die Zeile anhand des Spaltenrenners '|'
+ // Achtung: Ein escaped '|' (falls vorhanden) müsste beachtet werden
+ let parts = line.split('|');
+
+ for (let i = 0; i < parts.length; i++) {
+ let part = parts[i];
+ if (i === parts.length - 1 && part.trim() === '') {
+ // Zeile wurde mit einem | sauber beendet
+ if (cellBuffer.length > 0 || part !== '') {
+ cellBuffer.push(part);
+ }
+ if (cellBuffer.length > 0) {
+ currentCells.push(cellBuffer.join('\n'));
+ cellBuffer = [];
+ }
+ // Zeile ist komplett!
+ tableHtml += '\n' + currentCells.map(c => `| ${this._parseInline(c.trim())} | `).join('\n') + '\n
\n';
+ currentCells = [];
+ } else {
+ cellBuffer.push(part);
+ if (i < parts.length - 1) {
+ currentCells.push(cellBuffer.join('\n'));
+ cellBuffer = [];
+ }
+ }
+ }
+ }
+ tableHtml += '
';
+ return tableHtml;
+ }
+
+ /**
+ * INTERN: Verarbeitet Fluss-Formatierungen (Inline) mittels RegEx
+ */
+ _parseInline(text) {
+ if (!text) return '';
+ let html = text;
+
+ // 1. ESCAPE: Temporär schützen, damit Formatierungen sie nicht überschreiben
+ html = html.replace(/\\(.)/g, (match, char) => ``);
+
+ // 2. ÜBERSCHRIFTEN (Exakt nach unseren neuen Regeln verarbeitet)
+
+ // Regel A: Automatische Nummerierung (3#x Titel 3#x)
+ html = html.replace(/([1-6])#x\s*(.*?)\s*\1#x/g, (match, level, title) => {
+ const lvl = parseInt(level);
+ const numStr = this._getAutoNumber(lvl);
+ const fullTitle = `${numStr} ${title}`;
+ const id = this._slugify(fullTitle);
+
+ // Für das TOC erfassen
+ this.headings.push({ level: lvl, text: fullTitle, id: id });
+ return `${fullTitle}`;
+ });
+
+ // Regel B: Vom TOC ausgeschlossene Titel (3#- Titel 3#-)
+ html = html.replace(/([1-6])#-\s*(.*?)\s*\1#-/g, (match, level, title) => {
+ const lvl = parseInt(level);
+ return `${title}`; // Wird NICHT in this.headings gespeichert
+ });
+
+ // Regel C: Manuelle Nummerierung (3# Titel 3#)
+ html = html.replace(/([1-6])#\s*(.*?)\s*\1#/g, (match, level, title) => {
+ const lvl = parseInt(level);
+ const id = this._slugify(title);
+
+ this.headings.push({ level: lvl, text: title, id: id });
+ return `${title}`;
+ });
+
+ // Regel D: Das schnelle, solitäre # für H1 (# Titel #)
+ html = html.replace(/#([^#]+)#/g, (match, title) => {
+ const id = this._slugify(title);
+ this.headings.push({ level: 1, text: title, id: id });
+ return `${title}
`;
+ });
+
+ // 3. STANDARD FLUSS-FORMATIERUNGEN
+ html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // Fett
+ html = html.replace(/\*([^*]+)\*/g, '$1'); // Medium
+ html = html.replace(/\/([^/]+)\//g, '$1'); // Kursiv
+ html = html.replace(/_([^_]+)_/g, '$1'); // Unterstrichen
+ html = html.replace(/~([^~]+)~/g, '$1'); // Durchgestrichen
+ html = html.replace(/=([^=]+)=/g, '$1'); // Highlight
+ html = html.replace(/\^([^^]+)\^/g, '$1'); // Hochgestellt
+ html = html.replace(/°([^°]+)°/g, '$1'); // Tiefgestellt
+ html = html.replace(/`([^`]+)`/g, '$1'); // Inline-Code
+
+ // 4. AUSRICHTUNG & EINRÜCKUNG (Basiszeichen '>')
+ html = html.replace(/>l ([^>]+)>/g, '$1
');
+ html = html.replace(/>r ([^>]+)>/g, '$1
');
+ html = html.replace(/>c ([^>]+)>/g, '$1
');
+ html = html.replace(/>j ([^>]+)>/g, '$1
');
+ html = html.replace(/>tt ([^>]+)>/g, '$1
');
+ html = html.replace(/>t ([^>]+)>/g, '$1
');
+
+ // 5. LINKS & MEDIEN
+ // Bilder mit definierter Breite: 
+ html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '
');
+ // Standard Bilder: 
+ html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '
');
+ // Komfort-Links (Neuer Tab): [Text]{URL}
+ html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1');
+ // Standard-Links: [Text](URL)
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1');
+
+ // 6. FUSSNOTEN (Manuell ^1[...]^ und Automatisch ^x[...]^)
+ html = html.replace(/\^x\[([^\]]+)\]\^/g, ''); // Logik für Fußnoten-Endleiste optional
+ html = html.replace(/\^(\d+)\[([^\]]+)\]\^/g, '');
+
+ // 7. ESCAPE ZURÜCKWANDELN
+ html = html.replace(//g, (match, code) => String.fromCharCode(parseInt(code)));
+
+ return html;
+ }
+
+ /**
+ * INTERN: Errechnet die nächste fortlaufende Kapitelnummer (z.B. "1.2.1.")
+ */
+ _getAutoNumber(level) {
+ let idx = level - 1;
+ this.counters[idx]++;
+ // Alle untergeordneten Ebenen radikal auf 0 zurücksetzen
+ for (let i = idx + 1; i < 6; i++) this.counters[i] = 0;
+
+ // Array filtern, um nur die bis hierhin aktiven Nummernzähler zu extrahieren
+ let activeParts = this.counters.slice(0, level);
+ return activeParts.join('.') + '.';
+ }
+
+ /**
+ * INTERN: Macht aus Text eine URL-konforme ID (Anker für das TOC)
+ */
+ _slugify(text) {
+ return text.toLowerCase()
+ .replace(/[^a-z0-9\s-]/g, '') // Entferne Sonderzeichen
+ .replace(/\s+/g, '-'); // Ersetze Leerzeichen durch Bindestriche
+ }
+
+ /**
+ * INTERN: Erzeugt die fertige HTML-Liste des TOC und injiziert sie in den Platzhalter
+ */
+ _injectTOC(html) {
+ // Falls kein TOC-Block deklariert wurde oder keine Überschriften da sind, Platzhalter entfernen
+ if (this.headings.length === 0) {
+ return html.replace('', '');
+ }
+
+ let tocListHtml = '\n
\n';
+ let currentLevel = 1;
+
+ for (const heading of this.headings) {
+ if (heading.level > currentLevel) {
+ tocListHtml += '\n'.repeat(heading.level - currentLevel);
+ } else if (heading.level < currentLevel) {
+ tocListHtml += '
\n'.repeat(currentLevel - heading.level);
+ }
+ currentLevel = heading.level;
+
+ tocListHtml += `- ${heading.text}
\n`;
+ }
+
+ if (currentLevel > 1) {
+ tocListHtml += '
\n'.repeat(currentLevel - 1);
+ }
+ tocListHtml += '\n
';
+
+ return html.replace('', tocListHtml);
+ }
+
+ /**
+ * INTERN: Analysiert leere Zeilen, um Absätze () und Zeilenumbrüche (
) zu trennen
+ */
+ _processParagraphs(elements) {
+ let processed = [];
+ let paragraphBuffer = [];
+
+ for (let element of elements) {
+ // Wenn es ein fertiger HTML-Block (z.B. Table, List, H1) ist, schließe offenen Absatz ab
+ if (element.startsWith('<') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('
0) {
+ processed.push(`${paragraphBuffer.join('
')}
`);
+ paragraphBuffer = [];
+ }
+ processed.push(element);
+ } else if (element.trim() === '') {
+ // Bei einer echten Leerzeile (2x Enter) wird der Absatz gebunden
+ if (paragraphBuffer.length > 0) {
+ processed.push(`${paragraphBuffer.join('
')}
`);
+ paragraphBuffer = [];
+ }
+ } else {
+ paragraphBuffer.push(element);
+ }
+ }
+
+ if (paragraphBuffer.length > 0) {
+ processed.push(`${paragraphBuffer.join('
')}
`);
+ }
+
+ return processed.join('\n');
+ }
+}
\ No newline at end of file