Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision f99c8305dde0033c37bbf49e2e1d233dc9f2f9b5)
+++ src/services/ActivityPubService.js	(revision 837fc9c1dc50db6b6a042e84da63ed9989edf2fa)
@@ -246,5 +246,9 @@
     tag: Array.isArray(post.tags) ? post.tags.map((t) => ({ type: 'Hashtag', name: '#' + String(t).replace(/\s+/g, '') })) : [],
     replies: `${id}/replies`,
+    // NSFW → Mastodon-style content warning: sensitive (blurs media) + a summary/spoiler
+    // (hides the whole post behind a "Gevoelige inhoud" button until the reader opens it).
+    sensitive: !!post.nsfw,
   };
+  if (post.nsfw) note.summary = 'Gevoelige inhoud';
   if (attachment.length) note.attachment = attachment;
   // Playable-audio posts suppress the cover attachment (player card). Still expose
@@ -771,5 +775,5 @@
   if (!site) return;
   const recent = db.prepare(
-    `SELECT id, slug, title, content, cover_image_url, published_at, created_at
+    `SELECT id, slug, title, content, cover_image_url, nsfw, published_at, created_at
      FROM posts WHERE site_id = ? AND status = 'published' AND (fan_only IS NULL OR fan_only = 0)
      ORDER BY COALESCE(published_at, created_at) DESC LIMIT 20`
Index: src/services/Scheduler.js
===================================================================
--- src/services/Scheduler.js	(revision f99c8305dde0033c37bbf49e2e1d233dc9f2f9b5)
+++ src/services/Scheduler.js	(revision 837fc9c1dc50db6b6a042e84da63ed9989edf2fa)
@@ -15,5 +15,5 @@
   try {
     const due = db.prepare(`
-      SELECT p.id, p.site_id, p.slug, p.title, p.content, p.cover_image_url, p.fan_only,
+      SELECT p.id, p.site_id, p.slug, p.title, p.content, p.cover_image_url, p.fan_only, p.nsfw,
              p.published_at, p.publish_at, p.created_at, u.username
       FROM posts p JOIN users u ON u.id = p.author_id
@@ -39,5 +39,5 @@
             id: p.id, slug: p.slug, title: p.title || p.slug,
             content: p.content, cover_image_url: p.cover_image_url || null,
-            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only,
+            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only, nsfw: p.nsfw,
           }).catch(() => { /* best-effort */ });
         }
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision f99c8305dde0033c37bbf49e2e1d233dc9f2f9b5)
+++ src/services/i18n.js	(revision 837fc9c1dc50db6b6a042e84da63ed9989edf2fa)
@@ -771,5 +771,5 @@
     'pedit.pin_top': 'bovenaan',
     'pedit.pin_nth_suffix': 'e van boven',
-    'pedit.noindex_label': 'noindex (verberg voor zoekmachines)',
+    'pedit.noindex_label': 'noindex (verberg voor zoekmachines)', 'pedit.nsfw_label': 'NSFW / gevoelige inhoud', 'post.nsfw_warning': 'Gevoelige inhoud', 'post.nsfw_show': 'Tonen',
     'pedit.fan_only_label': 'Alleen voor ingelogde fans',
     'pedit.schedule_label': 'Publicatie inplannen',
@@ -1782,5 +1782,5 @@
     'pedit.pin_top': 'at the top',
     'pedit.pin_nth_suffix': 'th from top',
-    'pedit.noindex_label': 'noindex (hide from search engines)',
+    'pedit.noindex_label': 'noindex (hide from search engines)', 'pedit.nsfw_label': 'NSFW / sensitive content', 'post.nsfw_warning': 'Sensitive content', 'post.nsfw_show': 'Show',
     'pedit.fan_only_label': 'Logged-in fans only',
     'pedit.schedule_label': 'Schedule publication',
@@ -2793,5 +2793,5 @@
     'pedit.pin_top': 'ganz oben',
     'pedit.pin_nth_suffix': '. von oben',
-    'pedit.noindex_label': 'noindex (vor Suchmaschinen verbergen)',
+    'pedit.noindex_label': 'noindex (vor Suchmaschinen verbergen)', 'pedit.nsfw_label': 'NSFW / sensibler Inhalt', 'post.nsfw_warning': 'Sensibler Inhalt', 'post.nsfw_show': 'Anzeigen',
     'pedit.fan_only_label': 'Nur für angemeldete Fans',
     'pedit.schedule_label': 'Veröffentlichung planen',
