' . inline_markdown(trim($paragraph)) . "

\n"; $paragraph = ''; } }; $close_list = function () use (&$html, &$in_list, &$in_ol) { if ($in_list) { $html .= "\n"; $in_list = false; } if ($in_ol) { $html .= "\n"; $in_ol = false; } }; foreach ($lines as $line) { // Fenced code blocks if (preg_match('/^```(\w*)/', $line, $m)) { $flush_paragraph(); $close_list(); if (!$in_code) { $lang = $m[1] ? ' class="language-' . e($m[1]) . '"' : ''; $html .= "
";
                $in_code = true;
            } else {
                $html .= "
\n"; $in_code = false; } continue; } if ($in_code) { $html .= e($line) . "\n"; continue; } // Blank line if (trim($line) === '') { $flush_paragraph(); $close_list(); if ($in_blockquote) { $html .= "\n"; $in_blockquote = false; } continue; } // Headings if (preg_match('/^(#{1,6})\s+(.+)/', $line, $m)) { $flush_paragraph(); $close_list(); $level = strlen($m[1]); $id = slugify($m[2]); $html .= "" . inline_markdown($m[2]) . "\n"; continue; } // Horizontal rule if (preg_match('/^(-{3,}|\*{3,}|_{3,})$/', trim($line))) { $flush_paragraph(); $close_list(); $html .= "
\n"; continue; } // Blockquote if (preg_match('/^>\s?(.*)/', $line, $m)) { $flush_paragraph(); $close_list(); if (!$in_blockquote) { $html .= "
\n"; $in_blockquote = true; } $html .= '

' . inline_markdown($m[1]) . "

\n"; continue; } // Unordered list if (preg_match('/^[\-\*]\s+(.+)/', $line, $m)) { $flush_paragraph(); if ($in_ol) { $html .= "\n"; $in_ol = false; } if (!$in_list) { $html .= "\n"; $in_list = false; } if (!$in_ol) { $html .= "
    \n"; $in_ol = true; } $html .= '
  1. ' . inline_markdown($m[1]) . "
  2. \n"; continue; } // Paragraph text $paragraph .= $line . "\n"; } $flush_paragraph(); $close_list(); if ($in_code) $html .= "\n"; if ($in_blockquote) $html .= "
\n"; return $html; } /** * Process inline markdown: bold, italic, code, links, images. */ function inline_markdown(string $text): string { // Images: ![alt](src) $text = preg_replace('/!\[([^\]]*)\]\(([^)]+)\)/', '$1', $text); // Links: [text](url) $text = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '$1', $text); // Bold+italic $text = preg_replace('/\*\*\*(.+?)\*\*\*/', '$1', $text); // Bold $text = preg_replace('/\*\*(.+?)\*\*/', '$1', $text); // Italic $text = preg_replace('/\*(.+?)\*/', '$1', $text); // Inline code $text = preg_replace('/`([^`]+)`/', '$1', $text); return $text; } /** * HTML-escape shorthand. */ function e(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } /** * Generate a URL-friendly slug from text. */ function slugify(string $text): string { $text = strtolower(strip_tags($text)); $text = preg_replace('/[^a-z0-9\s-]/', '', $text); return preg_replace('/[\s-]+/', '-', trim($text)); } /** * Load and render a markdown content file. Returns HTML or null. */ function load_content(string $slug): ?string { global $CONTENT_MAP; $file = $CONTENT_MAP[$slug] ?? null; if (!$file) return null; $path = CONTENT_DIR . $file; if (!file_exists($path)) return null; return markdown_to_html(file_get_contents($path)); } /** * Check if current page matches slug (for nav highlighting). */ function is_active(string $slug, string $current): string { return ($slug === $current) ? ' class="active"' : ''; } /** * Get the URL for a page slug. */ function page_url(string $slug): string { if ($slug === 'home') return SITE_BASE_URL; return SITE_BASE_URL . $slug; }