Index: src/routes/ward.js
===================================================================
--- src/routes/ward.js	(revision 303bfc4daeb122010e84e14370b612f2e7e286f4)
+++ src/routes/ward.js	(revision a5a62490bec7673afe4e4c5c788f48466eb96b8b)
@@ -25,5 +25,5 @@
 import { carriesGuardians } from '../services/guardianship/context.js';
 import { t as i18nT, resolveLang } from '../services/i18n.js';
-import { injectCspNonce } from '../middleware/render.js';
+import { injectCspNonce, renderNoteBody, formatDateTime } from '../middleware/render.js';
 
 const router = express.Router();
@@ -91,4 +91,11 @@
       waiting: i18nT(L, 'ward.waiting'),
       help: i18nT(L, 'ward.help'),
+      feed: i18nT(L, 'ward.feed'),
+      feedEmpty: i18nT(L, 'ward.feed_empty'),
+      buoyHint: i18nT(L, 'ward.buoy_hint'),
+      helpWords: i18nT(L, 'ward.help_words'),
+      helpSend: i18nT(L, 'ward.help_send'),
+      helpSent: i18nT(L, 'ward.help_sent'),
+      cancel: i18nT(L, 'ward.cancel'),
     },
   };
@@ -144,4 +151,68 @@
   }));
   res.json({ checks: out });
+});
+
+/**
+ * De feed: wat er binnenkomt van de mensen die je volgt.
+ *
+ * Zelfde bron en dezelfde partial als de Krant, Berichten en het
+ * guardian-dashboard (renderNoteBody), zodat een post er overal hetzelfde
+ * uitziet: opmaak, media, quote-kaart en embed. Kale content sturen zou hier
+ * een lege regel opleveren waar een foto hoort.
+ */
+router.get('/api/feed', requireAuth, (req, res) => {
+  const site = siteForUser(req);
+  if (!site) return res.status(404).json({ error: 'no_site' });
+  const L = resolveLang(req);
+  const items = AP.getTimeline(site.slug, 60, 0).map((p) => ({
+    id: p.id,
+    uri: p.object_uri || p.url,
+    author: p.author_handle || p.author_name || p.author_uri,
+    authorName: p.author_name,
+    authorIcon: p.author_icon,
+    url: p.url,
+    published: p.published || p.created_at,
+    whenText: formatDateTime(p.published || p.created_at),
+    cw: p.cw || null,
+    bodyHtml: renderNoteBody(p, L),
+  }));
+  res.json({ items });
+});
+
+/**
+ * De reddingsboei (§5.2.1): sleep hem op een post en je guardians krijgen hem.
+ *
+ * Shaer maakt hier een SCHERMAFDRUK van de kaart, met al het gedoe van buiten
+ * beeld renderen. Op de server is dat onnodig: wij weten wélke note het is, dus
+ * de hulpvraag draagt de note zelf mee. Dat is niet alleen minder werk maar ook
+ * eerlijker: een guardian ziet de echte post en niet een plaatje dat er ooit op
+ * leek.
+ *
+ * Gaat naar ALLE guardians tegelijk, als direct bericht. Geen post, nooit
+ * boostbaar. Zonder guardians is er niets om te vragen en zegt dat het ook.
+ */
+router.post('/api/help', requireAuth, express.json({ limit: '4kb' }), async (req, res) => {
+  const site = siteForUser(req);
+  if (!site) return res.status(404).json({ error: 'no_site' });
+  const guardians = Guardianship.listGuardians(site.slug).map((g) => g.other_uri).filter(Boolean);
+  if (!guardians.length) return res.status(409).json({ error: 'no_guardians' });
+
+  const noteId = String(req.body?.note || '').trim();
+  const words = String(req.body?.words || '').trim().slice(0, 500);
+  // De post waar het om gaat, als die van deze tijdlijn komt. Niet verplicht:
+  // een kind mag ook om hulp vragen zonder dat er een post bij hoort.
+  const row = noteId ? AP.getTimeline(site.slug, 200, 0).find((p) => String(p.id) === noteId) : null;
+  const link = row && (row.url || row.object_uri);
+
+  const text = [words || 'Ik heb hulp nodig.', link ? `\n\n${link}` : ''].join('');
+  const sent = await Guardianship.deliverDirectNote(site, {
+    recipients: guardians,
+    text,
+    language: site.language || 'nl',
+    helpRequest: true,
+  }).catch(() => null);
+
+  if (!sent) return res.status(502).json({ error: 'not_delivered' });
+  res.json({ ok: true, guardians: guardians.length, note: link || null });
 });
 
