diff --git a/robots.txt b/robots.txt
new file mode 100644
index 0000000..f0f2c9b
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,38 @@
+User-agent: facebookexternalhit
+Allow: /
+
+User-agent: Facebot
+Allow: /
+
+User-agent: meta-externalagent
+Allow: /
+
+User-agent: *
+Content-Signal: search=yes,ai-train=no
+Allow: /
+
+User-agent: Amazonbot
+Disallow: /
+
+User-agent: Applebot-Extended
+Disallow: /
+
+User-agent: Bytespider
+Disallow: /
+
+User-agent: CCBot
+Disallow: /
+
+User-agent: ClaudeBot
+Disallow: /
+
+User-agent: CloudflareBrowserRenderingCrawler
+Disallow: /
+
+User-agent: Google-Extended
+Disallow: /
+
+User-agent: GPTBot
+Disallow: /
+
+Sitemap: https://bubulescu.org/sitemap.xml
diff --git a/sitemap.php b/sitemap.php
new file mode 100644
index 0000000..431f53e
--- /dev/null
+++ b/sitemap.php
@@ -0,0 +1,97 @@
+ $loc,
+ 'lastmod' => $lastmod,
+ 'changefreq' => $changefreq,
+ 'priority' => $priority,
+ ];
+}
+
+$urls = [];
+
+$staticPages = [
+ ['https://bubulescu.org/', $root . '/index.php', 'daily', '1.0'],
+ ['https://bubulescu.org/contact.php', $root . '/contact.php', 'monthly', '0.5'],
+ ['https://bubulescu.org/feed.php', $root . '/feed.php', 'monthly', '0.3'],
+];
+foreach ($staticPages as [$loc, $path, $changefreq, $priority]) {
+ if (is_file($path)) {
+ sitemap_add_url($urls, $loc, sitemap_lastmod_from_ts((int)filemtime($path)), $changefreq, $priority);
+ }
+}
+
+foreach (glob(rtrim($uploadsDir, '/') . '/*', GLOB_ONLYDIR) ?: [] as $dir) {
+ if (!is_file($dir . '/.mvlog-permalink')) continue;
+
+ $idFile = $dir . '/.mvlog-id';
+ $articleId = is_file($idFile) ? strtolower(trim((string)@file_get_contents($idFile))) : '';
+ if (!sitemap_article_id_is_valid($articleId)) continue;
+
+ $state = [];
+ $stateFile = $dir . '/.movmaker-state.json';
+ if (is_file($stateFile)) {
+ $decoded = json_decode((string)@file_get_contents($stateFile), true);
+ if (is_array($decoded)) $state = $decoded;
+ }
+
+ $lastmodCandidates = [];
+ foreach ([$dir . '/data.txt', $stateFile, $idFile, $dir . '/.mvlog-permalink'] as $candidate) {
+ if (is_file($candidate)) $lastmodCandidates[] = (int)filemtime($candidate);
+ }
+
+ $output = isset($state['output']) && is_string($state['output']) ? basename($state['output']) : '';
+ if ($output !== '') {
+ $videoPath = rtrim($videosDir, '/') . '/' . $output;
+ if (is_file($videoPath)) $lastmodCandidates[] = (int)filemtime($videoPath);
+ }
+
+ $lastmodTs = $lastmodCandidates ? max($lastmodCandidates) : (int)filemtime($dir);
+ sitemap_add_url(
+ $urls,
+ 'https://bubulescu.org/?id=' . rawurlencode($articleId),
+ sitemap_lastmod_from_ts($lastmodTs),
+ 'monthly',
+ '0.8'
+ );
+}
+
+ksort($urls, SORT_STRING);
+
+header('Content-Type: application/xml; charset=UTF-8');
+header('X-MVLog-Sitemap: dynamic');
+header('Cache-Control: public, max-age=300');
+
+echo '' . "\n";
+echo '' . "\n";
+foreach ($urls as $url) {
+ echo " \n";
+ echo ' ' . sitemap_xml_escape($url['loc']) . "\n";
+ echo ' ' . sitemap_xml_escape($url['lastmod']) . "\n";
+ echo ' ' . sitemap_xml_escape($url['changefreq']) . "\n";
+ echo ' ' . sitemap_xml_escape($url['priority']) . "\n";
+ echo " \n";
+}
+echo "\n";
diff --git a/sitemap.xml b/sitemap.xml
new file mode 100644
index 0000000..9ed8161
--- /dev/null
+++ b/sitemap.xml
@@ -0,0 +1,51 @@
+
+
+
+ https://bubulescu.org/
+ 2026-06-20
+ daily
+ 1.0
+
+
+ https://bubulescu.org/contact.php
+ 2026-06-07
+ monthly
+ 0.5
+
+
+ https://bubulescu.org/feed.php
+ 2026-06-05
+ monthly
+ 0.3
+
+
+ https://bubulescu.org/?id=202605311436027136dfbfe683edb2
+ 2026-06-05
+ monthly
+ 0.8
+
+
+ https://bubulescu.org/?id=2026053114360277f7ac3306fe8287
+ 2026-06-15
+ monthly
+ 0.8
+
+
+ https://bubulescu.org/?id=202605311436143f593bdf63a42a1b
+ 2026-06-15
+ monthly
+ 0.8
+
+
+ https://bubulescu.org/?id=202606041612488d1c9612336a71c3
+ 2026-06-14
+ monthly
+ 0.8
+
+
+ https://bubulescu.org/?id=20260613064744a37e9ccc4442a143
+ 2026-06-20
+ monthly
+ 0.8
+
+