Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/config/database.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -87,4 +87,5 @@
   ensureColumn('posts', 'content_warning', 'TEXT');        // custom CW label (empty = default "Gevoelige inhoud")
   ensureColumn('posts', 'type',    "TEXT DEFAULT 'post'");  // post | foto | video | audio
+  ensureColumn('posts', 'poll_json', 'TEXT');              // a poll WE host → federates as AS2 Question: {multiple,options[{name}],endTime,closed}
 
   // Statistics (premium module) — bare counters, cookie-free.
@@ -331,4 +332,13 @@
     );
     CREATE INDEX IF NOT EXISTS idx_ap_delivery_due ON ap_delivery(next_at);
+    CREATE TABLE IF NOT EXISTS poll_votes (
+      id INTEGER PRIMARY KEY AUTOINCREMENT,
+      post_id INTEGER NOT NULL,     -- our local poll post (posts.id)
+      actor_uri TEXT NOT NULL,      -- the remote voter's AP actor URI
+      choice TEXT NOT NULL,         -- the chosen option's name (matches poll_json options[].name)
+      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+      UNIQUE(post_id, actor_uri, choice)
+    );
+    CREATE INDEX IF NOT EXISTS idx_poll_votes_post ON poll_votes(post_id);
   `);
   // "Feature" a followed account: its posts show in the local Cirkel.
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/routes/posts.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -116,4 +116,29 @@
   if (!Number.isFinite(n) || n < 0) return 0;
   return n;
+}
+
+// Poll durations offered in the editor (seconds) — the Mastodon set (5m … 7d).
+const POLL_DURATIONS = new Set([300, 1800, 3600, 21600, 43200, 86400, 259200, 604800]);
+// Parse the editor's poll fields into the poll_json we store on the post (which
+// buildNote federates as an AS2 Question). Returns null when no valid poll (< 2
+// options or the poll checkbox is off). endTime is set from the chosen duration
+// (default 1 day) so the Scheduler can close it.
+function parsePollForm(body) {
+  if (!body || !body.poll_enabled) return null;
+  const raw = body.poll_option == null ? [] : (Array.isArray(body.poll_option) ? body.poll_option : [body.poll_option]);
+  const options = [];
+  const seen = new Set();
+  for (const o of raw) {
+    const name = String(o == null ? '' : o).trim().slice(0, 100);
+    if (!name) continue;
+    const key = name.toLowerCase();
+    if (seen.has(key)) continue; seen.add(key);
+    options.push({ name });
+    if (options.length >= 8) break;
+  }
+  if (options.length < 2) return null;
+  const dur = parseInt(body.poll_duration, 10);
+  const secs = POLL_DURATIONS.has(dur) ? dur : 86400;
+  return JSON.stringify({ multiple: !!body.poll_multiple, options, endTime: new Date(Date.now() + secs * 1000).toISOString(), closed: false });
 }
 
@@ -241,4 +266,5 @@
   const validTypes = new Set(['post', 'foto', 'video', 'audio']);
   const finalType = validTypes.has(type) ? type : 'post';
+  const pollJson = parsePollForm(req.body);   // AS2 Question definition, or null
   const postId = uuid();
   const now = new Date().toISOString();
@@ -258,7 +284,7 @@
     INSERT INTO posts (
       id, site_id, slug, author_id, title, content, excerpt,
-      status, cover_image_url, cover_video_url, pinned, tags, type, noindex, fan_only, nsfw, content_warning, publish_at,
+      status, cover_image_url, cover_video_url, pinned, tags, type, noindex, fan_only, nsfw, content_warning, poll_json, publish_at,
       created_at, updated_at, published_at
-    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
   `).run(
     postId, site.id, finalSlug, req.session.user.id,
@@ -266,5 +292,5 @@
     finalStatus, cover_image_url || null, (req.body.cover_video_url || null), parsePinnedRank(pinned),
     JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)),
-    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, publishAt,
+    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, pollJson, publishAt,
     now, now, publishedAt
   );
@@ -287,5 +313,5 @@
         id: postId, slug: finalSlug, title: title || finalSlug,
         content: cleanContent, cover_image_url: cover_image_url || null, cover_video_url: req.body.cover_video_url || null,
-        published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw, content_warning: cw,
+        published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw, content_warning: cw, poll_json: pollJson,
       }).catch(() => { /* best-effort */ });
     }
@@ -321,7 +347,12 @@
   }
 
+  // A poll with votes is frozen (options can't change) — flag it so the editor disables the poll fields.
+  let pollLocked = false;
+  try { pollLocked = !!(post.poll_json && db.prepare('SELECT 1 FROM poll_votes WHERE post_id = ? LIMIT 1').get(post.id)); } catch { /* ignore */ }
+
   renderPage(req, res, 'pages/post-edit', {
     post,
     isNew: false,
+    pollLocked,
     fediOpenAudio: postAudioFediOpen(site.id, post.content),
     pageTitle: 'Edit: ' + (post.title || 'Untitled'),
@@ -353,4 +384,10 @@
   const finalType = validTypes.has(type) ? type : (post.type || 'post');
 
+  // A poll that has already received votes is frozen (you can still edit the surrounding
+  // post, but not the options) — changing options after votes would scramble the tally and
+  // is disallowed on the fediverse too. Otherwise re-parse the poll form (add/remove/disable).
+  const hasVotes = !!(post.poll_json && (() => { try { return db.prepare('SELECT 1 FROM poll_votes WHERE post_id = ? LIMIT 1').get(post.id); } catch { return false; } })());
+  const pollJson = hasVotes ? post.poll_json : parsePollForm(req.body);
+
   // Sanitize before storage — same pipeline as create.
   const cleanContent = HtmlSanitizerService.sanitize(content || '');
@@ -386,5 +423,5 @@
       title = ?, content = ?, excerpt = ?, status = ?,
       cover_image_url = ?, cover_video_url = ?, pinned = ?, tags = ?,
-      type = ?, noindex = ?, fan_only = ?, nsfw = ?, content_warning = ?, publish_at = ?,
+      type = ?, noindex = ?, fan_only = ?, nsfw = ?, content_warning = ?, poll_json = ?, publish_at = ?,
       slug = ?, published_at = ?, updated_at = ?
     WHERE id = ?
@@ -393,5 +430,5 @@
     cover_image_url || null, (req.body.cover_video_url || null), parsePinnedRank(pinned),
     JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)),
-    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, publishAt,
+    finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, pollJson, publishAt,
     finalSlug, publishedAt, now, post.id
   );
@@ -418,5 +455,5 @@
       id: post.id, slug: finalSlug, title: title || finalSlug,
       content: cleanContent, cover_image_url: cover_image_url || null, cover_video_url: req.body.cover_video_url || null,
-      published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw, content_warning: cw,
+      published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw, content_warning: cw, poll_json: pollJson,
     };
     if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ });
@@ -1079,4 +1116,5 @@
   renderPage(req, res, 'pages/post', {
     post,
+    poll: ActivityPubService.ownPollView(post),
     newerPost,
     olderPost,
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/services/ActivityPubService.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -42,4 +42,8 @@
     value: 'schema:value',
     embedUrl: { '@id': 'schema:embedUrl', '@type': '@id' },
+    // Poll (Question) extension: Question/oneOf/anyOf/endTime/closed are AS2 core, but the
+    // per-poll unique-voter count is a Mastodon (toot) term — declare it so the emitted
+    // Question stays valid JSON-LD (a strict processor would otherwise drop votersCount).
+    votersCount: 'toot:votersCount',
   },
 ];
@@ -381,4 +385,8 @@
   // make it JSON-LD-clean with a context term, otherwise it degrades to the player card.
   if (playable) note.embedUrl = `${base}/embed?post=${encodeURIComponent(post.slug)}`;
+  // A hosted poll → federate as an AS2 Question (options + live tally). Do this last so it
+  // reuses the note's content/addressing/tags, then swaps the type and strips media.
+  const ownPoll = parseOwnPoll(post.poll_json);
+  if (ownPoll) applyPollToNote(note, post.id, ownPoll);
   return note;
 }
@@ -786,4 +794,111 @@
 }
 
+// ── Polls WE host (a local post with a poll) ──────────────────────
+// Parse the poll definition stored on our own post (posts.poll_json). Counts are
+// NOT stored here — they're derived from the poll_votes ballots so a re-render always
+// reflects the authoritative tally.
+export function parseOwnPoll(pollJson) {
+  if (!pollJson) return null;
+  let d; try { d = typeof pollJson === 'string' ? JSON.parse(pollJson) : pollJson; } catch { return null; }
+  if (!d || !Array.isArray(d.options)) return null;
+  const options = d.options.map((o) => ({ name: String((o && o.name != null ? o.name : o) || '').slice(0, 300) })).filter((o) => o.name);
+  if (options.length < 2) return null;
+  const endTime = d.endTime || null;
+  const closed = !!d.closed || (endTime ? Date.parse(endTime) <= Date.now() : false);
+  return { multiple: !!d.multiple, options, endTime, closed };
+}
+
+// Live tally of a hosted poll from its ballots: per-option counts + unique voters.
+export function pollTally(postId) {
+  const counts = {}; let voters = 0;
+  try {
+    for (const r of db.prepare('SELECT choice, COUNT(*) AS n FROM poll_votes WHERE post_id = ? GROUP BY choice').all(postId)) counts[r.choice] = r.n;
+    voters = db.prepare('SELECT COUNT(DISTINCT actor_uri) AS n FROM poll_votes WHERE post_id = ?').get(postId).n || 0;
+  } catch { /* table may not exist yet */ }
+  return { counts, voters };
+}
+
+// Render-ready view of a hosted poll (options with counts + percentages, totals, state).
+// Voting is fediverse-only, so this is display-only on the site.
+export function ownPollView(post) {
+  const poll = parseOwnPoll(post && post.poll_json);
+  if (!poll) return null;
+  const { counts, voters } = pollTally(post.id);
+  const total = Object.values(counts).reduce((a, b) => a + b, 0);
+  const denom = poll.multiple ? voters : total; // multiple-choice %: share of voters (can sum >100%)
+  const options = poll.options.map((o) => {
+    const count = counts[o.name] || 0;
+    return { name: o.name, count, pct: denom ? Math.round((count / denom) * 100) : 0 };
+  });
+  return { multiple: poll.multiple, options, total, voters, endTime: poll.endTime, closed: poll.closed };
+}
+
+// Attach the AS2 Question shape to a note built for a hosted poll. Mastodon renders a
+// status with either media OR a poll (never both), so a poll federates as content +
+// options with no media attachment. oneOf = single choice, anyOf = multiple.
+function applyPollToNote(note, postId, poll) {
+  const { counts, voters } = pollTally(postId);
+  const opts = poll.options.map((o) => ({
+    type: 'Note',
+    name: o.name,
+    replies: { type: 'Collection', totalItems: counts[o.name] || 0 },
+  }));
+  note.type = 'Question';
+  note[poll.multiple ? 'anyOf' : 'oneOf'] = opts;
+  if (poll.endTime) note.endTime = new Date(poll.endTime).toISOString();
+  // Once closed, Mastodon expects a `closed` timestamp (the effective end).
+  if (poll.closed) note.closed = poll.endTime ? new Date(poll.endTime).toISOString() : new Date().toISOString();
+  note.votersCount = voters;
+  delete note.attachment;   // media + poll are mutually exclusive on Mastodon
+  delete note.image;
+  return note;
+}
+
+// Record an inbound ballot on one of OUR polls. A vote arrives as a Create(Note) whose
+// `name` is the chosen option and `inReplyTo` is our poll note — the Mastodon-standard
+// vote form. Returns { handled } — handled=true means it was addressed to a poll (so the
+// caller must NOT also store it as a reply), false means "not a poll, fall through".
+function recordPollBallot(postId, actorUri, rawChoice) {
+  const choice = String(rawChoice == null ? '' : rawChoice).slice(0, 300);
+  if (!choice) return { handled: false };
+  let post; try { post = db.prepare('SELECT poll_json FROM posts WHERE id = ?').get(postId); } catch { return { handled: false }; }
+  const poll = post && parseOwnPoll(post.poll_json);
+  if (!poll) return { handled: false };               // not a poll → let the reply logic handle it
+  if (poll.closed) return { handled: true };          // voting closed → drop
+  if (!poll.options.some((o) => o.name === choice)) return { handled: true }; // unknown option → drop
+  try {
+    // Single choice = one ballot per actor: ignore a later/different vote. Multiple choice
+    // allows one ballot per distinct option (the UNIQUE(post,actor,choice) dedupes repeats).
+    if (!poll.multiple && db.prepare('SELECT 1 FROM poll_votes WHERE post_id = ? AND actor_uri = ? LIMIT 1').get(postId, actorUri)) return { handled: true };
+    db.prepare('INSERT OR IGNORE INTO poll_votes (post_id, actor_uri, choice) VALUES (?, ?, ?)').run(postId, actorUri, choice);
+  } catch { return { handled: true }; }
+  schedulePollUpdate(postId);
+  return { handled: true };
+}
+
+// Coalesce a burst of votes into ONE Update(Question) per poll: the first vote schedules a
+// refresh ~15s out; further votes in that window ride the same pending update (which carries
+// the accumulated tally). Non-follower voters re-fetch the Question (live tally) themselves.
+const _pollUpdTimers = new Map();
+function schedulePollUpdate(postId) {
+  if (_pollUpdTimers.has(postId)) return;
+  const t = setTimeout(() => { _pollUpdTimers.delete(postId); deliverPollUpdate(postId).catch(() => { /* best-effort */ }); }, 15000);
+  if (t.unref) t.unref();
+  _pollUpdTimers.set(postId, t);
+}
+
+// Push the fresh poll tally (or closed state) to followers as Update(Question).
+export async function deliverPollUpdate(postId) {
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  if (!base || !postId) return;
+  let post, site;
+  try {
+    post = db.prepare('SELECT * FROM posts WHERE id = ?').get(postId);
+    if (!post || !post.poll_json) return;
+    site = db.prepare('SELECT * FROM sites WHERE id = ?').get(post.site_id);
+  } catch { return; }
+  if (site) await deliverUpdate(site, post);
+}
+
 // Handle an incoming inbox POST. slugParam = null for the shared /ap/inbox.
 export async function handleInbox(req, slugParam) {
@@ -863,4 +978,14 @@
   if (type === 'Create' && act.object && (act.object.type === 'Note' || act.object.type === 'Article' || act.object.type === 'Question')) {
     const o = act.object;
+    // A poll ballot: a Note carrying a `name` (the chosen option) inReplyTo one of OUR poll
+    // posts. Record it (deduped per actor) BEFORE the reply logic so a vote is never stored
+    // as a comment. recordPollBallot returns handled=false only if the target isn't a poll.
+    if (o.name && o.inReplyTo && actorUri && !isLocalActor) {
+      const seg = postIdFromNoteUrl(o.inReplyTo, base);
+      if (seg && localPostExists(seg)) {
+        const rec = recordPollBallot(seg, actorUri, o.name);
+        if (rec.handled) { console.log('[AP] poll vote', actorUri, '→', seg); return 202; }
+      }
+    }
     const tgt = findThreadTarget(o.inReplyTo, base);
     if (tgt && actorUri && !isLocalActor) {
@@ -1969,4 +2094,5 @@
   listOutbox, deliverOutboxDelete, deliverOutboxUpdate,
   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, sendInteraction, voteOnPoll,
+  parseOwnPoll, pollTally, ownPollView, deliverPollUpdate,
   autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
   getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
Index: src/services/Scheduler.js
===================================================================
--- src/services/Scheduler.js	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/services/Scheduler.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -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.cover_video_url, p.fan_only, p.nsfw, p.content_warning,
+      SELECT p.id, p.site_id, p.slug, p.title, p.content, p.cover_image_url, p.cover_video_url, p.fan_only, p.nsfw, p.content_warning, p.poll_json,
              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, cover_video_url: p.cover_video_url || null,
-            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only, nsfw: p.nsfw, content_warning: p.content_warning,
+            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only, nsfw: p.nsfw, content_warning: p.content_warning, poll_json: p.poll_json,
           }).catch(() => { /* best-effort */ });
         }
@@ -48,9 +48,35 @@
 }
 
+// Close hosted polls whose endTime has passed: mark them closed (once) and push the final
+// tally + closed state to followers as Update(Question). The `closed` flag in poll_json
+// guards against re-sending — a poll is only processed on the tick that crosses its endTime.
+export function closeExpiredPolls() {
+  try {
+    const due = db.prepare(`
+      SELECT id, poll_json FROM posts
+      WHERE poll_json IS NOT NULL
+        AND status = 'published'
+        AND json_extract(poll_json, '$.endTime') IS NOT NULL
+        AND IFNULL(json_extract(poll_json, '$.closed'), 0) = 0
+        AND datetime(json_extract(poll_json, '$.endTime')) <= datetime('now')
+    `).all();
+    if (!due.length) return 0;
+    const upd = db.prepare('UPDATE posts SET poll_json = ? WHERE id = ?');
+    for (const p of due) {
+      let d; try { d = JSON.parse(p.poll_json); } catch { continue; }
+      d.closed = true;
+      upd.run(JSON.stringify(d), p.id);
+      ActivityPubService.deliverPollUpdate(p.id).catch(() => { /* best-effort */ });
+    }
+    return due.length;
+  } catch { return 0; }
+}
+
 let _timer = null;
+function tick() { flipScheduledPosts(); closeExpiredPolls(); }
 export function startScheduler() {
-  flipScheduledPosts();                 // run immediately on boot
+  tick();                               // run immediately on boot
   if (_timer) return;
-  _timer = setInterval(flipScheduledPosts, 60 * 1000); // every minute
+  _timer = setInterval(tick, 60 * 1000); // every minute
   if (_timer.unref) _timer.unref();
 }
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/services/i18n.js	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -113,5 +113,5 @@
     'fedi.heading': 'Vanuit de fediverse', 'fedi.likes': 'sterren', 'fedi.boosts': 'boosts', 'fedi.replies': 'Reacties uit de fediverse',
     'fedi.reply': 'Reageer', 'fedi.reply_ph': 'Je antwoord aan de fediverse…', 'fedi.send': 'Versturen', 'fedi.you': 'Jij',
-    'fedi.remote_title': 'Reageer via de fediverse', 'fedi.follow_heading': 'Volgen via de fediverse', 'fedi.profile_follow': 'Volg via de fediverse', 'profile.since': 'Op Klonkt sinds', 'profile.free': 'Gratis', 'fedi.follow_intro': 'Je staat op het punt te volgen:', 'fedi.follow_btn': 'Volgen', 'fedi.cancel': 'Annuleren', 'fedi.followed_title': 'Volgverzoek verstuurd ✅', 'fedi.followed_done': 'Je volgverzoek is onderweg. Zodra de andere kant het accepteert, verschijnen hun berichten in je tijdlijn.', 'fedi.view_profile': 'Bekijk profiel →', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres:', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Like intrekken', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is verstuurd. Hij verschijnt zo bij de originele post op de fediverse, niet op deze pagina. Bekijk hem daar:', 'fedi.reply_where': 'Je reactie verschijnt bij de originele post op de fediverse, niet op deze pagina. Via de link hierboven zie je hem daar.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.boost_btn': 'Boost deze post', 'fedi.boosted_title': 'Geboost', 'fedi.boosted_done': 'Je boost is onderweg naar de fediverse.', 'fedi.remote_interact': 'Interageer via de fediverse', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'fedi.goto_post': 'Naar de post', 'fedi.edit': 'Bewerken', 'fedi.save_edit': 'Opslaan', 'fedi.bm_label': 'Interageer via mijn site', 'fedi.bm_help': 'Sleep deze knop naar je bladwijzerbalk. Klik ’m daarna op elke fediverse-post (Mastodon, een andere Klonkt…) om er via jouw site op te reageren, te liken of te boosten.', 'tl.title': 'Krant', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.autoboost': 'Uitgelicht', 'tl.autoboost_follow': 'uitlichten in cirkel', 'tl.autoboost_hint': 'Hun nieuwe posts verschijnen doorlopend in jouw Cirkel (lokaal, geen fediverse-boost).', 'tl.pending': 'in afwachting', 'tl.unboost': 'Boost intrekken', 'tl.feed': 'Berichten', 'tl.tab_feed': 'Krant', 'tl.tab_following': 'Volgend', 'tl.tab_replies': 'Reacties', 'tl.empty_following': 'Je volgt nog niemand.', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →', 'tl.open_player': 'Open de speler', 'tl.paste_ph': 'Plak een fediverse-post-URL', 'tl.paste_go': 'Openen', 'tl.boosted': 'boostte dit', 'tl.read_more': 'Meer lezen', 'tl.show_less': 'Minder', 'poll.vote': 'Stem', 'poll.votes': 'stemmen', 'poll.closed': 'gesloten',
+    'fedi.remote_title': 'Reageer via de fediverse', 'fedi.follow_heading': 'Volgen via de fediverse', 'fedi.profile_follow': 'Volg via de fediverse', 'profile.since': 'Op Klonkt sinds', 'profile.free': 'Gratis', 'fedi.follow_intro': 'Je staat op het punt te volgen:', 'fedi.follow_btn': 'Volgen', 'fedi.cancel': 'Annuleren', 'fedi.followed_title': 'Volgverzoek verstuurd ✅', 'fedi.followed_done': 'Je volgverzoek is onderweg. Zodra de andere kant het accepteert, verschijnen hun berichten in je tijdlijn.', 'fedi.view_profile': 'Bekijk profiel →', 'fedi.remote_reply': 'Reageer via de fediverse', 'fedi.remote_prompt': 'Je fediverse-adres:', 'fedi.remote_notfound': 'Kon die post niet ophalen. Plak de volledige post-URL:', 'fedi.remote_load': 'Ophalen', 'fedi.remote_replying_to': 'Je reageert op', 'fedi.remote_as': 'Wordt verzonden als {site}.', 'fedi.remote_view_original': 'Bekijk de hele post + reacties op de bron →', 'fedi.remote_reply_short': 'via de fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Like intrekken', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'jouw server', 'fedi.remote_sent_title': 'Verzonden ✅', 'fedi.remote_sent': 'Je reactie is verstuurd. Hij verschijnt zo bij de originele post op de fediverse, niet op deze pagina. Bekijk hem daar:', 'fedi.reply_where': 'Je reactie verschijnt bij de originele post op de fediverse, niet op deze pagina. Via de link hierboven zie je hem daar.', 'fedi.remote_back': '← Terug naar je site', 'fedi.like_btn': 'Like deze post', 'fedi.or_reply': 'of reageer:', 'fedi.liked_title': 'Geliket', 'fedi.liked_done': 'Je like is onderweg naar de fediverse.', 'fedi.boost_btn': 'Boost deze post', 'fedi.boosted_title': 'Geboost', 'fedi.boosted_done': 'Je boost is onderweg naar de fediverse.', 'fedi.remote_interact': 'Interageer via de fediverse', 'fedi.delete_confirm': 'Deze reactie verwijderen?', 'fedi.manage_title': 'Mijn fediverse-reacties', 'fedi.manage_empty': 'Je hebt nog geen reacties verstuurd.', 'fedi.goto_post': 'Naar de post', 'fedi.edit': 'Bewerken', 'fedi.save_edit': 'Opslaan', 'fedi.bm_label': 'Interageer via mijn site', 'fedi.bm_help': 'Sleep deze knop naar je bladwijzerbalk. Klik ’m daarna op elke fediverse-post (Mastodon, een andere Klonkt…) om er via jouw site op te reageren, te liken of te boosten.', 'tl.title': 'Krant', 'tl.lead': 'Volg accounts in de fediverse en zie hun berichten hier.', 'tl.follow_btn': 'Volgen', 'tl.following': 'Je volgt', 'tl.unfollow': 'Ontvolgen', 'tl.autoboost': 'Uitgelicht', 'tl.autoboost_follow': 'uitlichten in cirkel', 'tl.autoboost_hint': 'Hun nieuwe posts verschijnen doorlopend in jouw Cirkel (lokaal, geen fediverse-boost).', 'tl.pending': 'in afwachting', 'tl.unboost': 'Boost intrekken', 'tl.feed': 'Berichten', 'tl.tab_feed': 'Krant', 'tl.tab_following': 'Volgend', 'tl.tab_replies': 'Reacties', 'tl.empty_following': 'Je volgt nog niemand.', 'tl.empty': 'Nog niks — volg iemand om hun berichten hier te zien.', 'tl.view_original': 'Bekijk origineel →', 'tl.open_player': 'Open de speler', 'tl.paste_ph': 'Plak een fediverse-post-URL', 'tl.paste_go': 'Openen', 'tl.boosted': 'boostte dit', 'tl.read_more': 'Meer lezen', 'tl.show_less': 'Minder', 'poll.vote': 'Stem', 'poll.votes': 'stemmen', 'poll.closed': 'gesloten', 'poll.aria': 'Peiling', 'poll.voter_one': 'stemmer', 'poll.voter_many': 'stemmers', 'poll.closes': 'sluit op', 'poll.multiple': 'meerkeuze', 'poll.fedi_only': 'Stemmen kan vanuit de fediverse — volg deze site en stem in je eigen app.',
     'comments.to_start': 'om de conversatie te starten.',
     'comments.reply': 'Reageer', 'comments.delete': 'Verwijder', 'comments.cancel': 'Annuleren',
@@ -697,5 +697,5 @@
     'pedit.pin_top': 'bovenaan',
     'pedit.pin_nth_suffix': 'e van boven',
-    'pedit.noindex_label': 'noindex (verberg voor zoekmachines)', 'pedit.nsfw_label': 'NSFW / gevoelige inhoud', 'pedit.fedi_audio_label': 'Audio openbaar delen op de fediverse (speelt inline in apps; bestand downloadbaar)', 'pedit.nsfw_cw_ph': 'Waarschuwingstekst (optioneel, standaard: Gevoelige inhoud)', 'post.nsfw_warning': 'Gevoelige inhoud', 'post.nsfw_show': 'Tonen', 'post.share': 'Deel', 'post.share_copied': 'Link gekopieerd ✓',
+    'pedit.noindex_label': 'noindex (verberg voor zoekmachines)', 'pedit.nsfw_label': 'NSFW / gevoelige inhoud', 'pedit.fedi_audio_label': 'Audio openbaar delen op de fediverse (speelt inline in apps; bestand downloadbaar)', 'pedit.nsfw_cw_ph': 'Waarschuwingstekst (optioneel, standaard: Gevoelige inhoud)', 'post.nsfw_warning': 'Gevoelige inhoud', 'post.nsfw_show': 'Tonen', 'post.share': 'Deel', 'post.share_copied': 'Link gekopieerd ✓', 'pedit.poll_label': 'Peiling toevoegen', 'pedit.poll_locked': 'Er is al gestemd — de opties kunnen niet meer wijzigen.', 'pedit.poll_option_ph': 'Optie', 'pedit.poll_add': 'Optie toevoegen', 'pedit.poll_multiple': 'Meerdere keuzes toestaan', 'pedit.poll_duration': 'Looptijd', 'pedit.poll_dur_5m': '5 minuten', 'pedit.poll_dur_30m': '30 minuten', 'pedit.poll_dur_1h': '1 uur', 'pedit.poll_dur_6h': '6 uur', 'pedit.poll_dur_12h': '12 uur', 'pedit.poll_dur_1d': '1 dag', 'pedit.poll_dur_3d': '3 dagen', 'pedit.poll_dur_7d': '7 dagen',
     'pedit.fan_only_label': 'Alleen voor ingelogde fans',
     'pedit.schedule_label': 'Publicatie inplannen',
@@ -1033,5 +1033,5 @@
     'fedi.heading': 'From the fediverse', 'fedi.likes': 'favourites', 'fedi.boosts': 'boosts', 'fedi.replies': 'Replies from the fediverse',
     'fedi.reply': 'Reply', 'fedi.reply_ph': 'Your reply to the fediverse…', 'fedi.send': 'Send', 'fedi.you': 'You',
-    'fedi.remote_title': 'Reply via the fediverse', 'fedi.follow_heading': 'Follow via the fediverse', 'fedi.profile_follow': 'Follow via the fediverse', 'profile.since': 'On Klonkt since', 'profile.free': 'Free', 'fedi.follow_intro': 'You are about to follow:', 'fedi.follow_btn': 'Follow', 'fedi.cancel': 'Cancel', 'fedi.followed_title': 'Follow request sent ✅', 'fedi.followed_done': 'Your follow request is on its way. Once accepted, their posts show up in your timeline.', 'fedi.view_profile': 'View profile →', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address:', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Unlike', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply has been sent. It will show up on the original post on the fediverse, not on this page. See it there:', 'fedi.reply_where': 'Your reply appears on the original post on the fediverse, not on this page. Use the link above to see it there.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.boost_btn': 'Boost this post', 'fedi.boosted_title': 'Boosted', 'fedi.boosted_done': 'Your boost is on its way to the fediverse.', 'fedi.remote_interact': 'Interact via the fediverse', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'fedi.goto_post': 'Go to post', 'fedi.edit': 'Edit', 'fedi.save_edit': 'Save', 'fedi.bm_label': 'Interact via my site', 'fedi.bm_help': 'Drag this button to your bookmarks bar. Then click it on any fediverse post (Mastodon, another Klonkt…) to reply, like or boost it via your own site.', 'tl.title': 'News', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.autoboost': 'Featured', 'tl.autoboost_follow': 'feature in circle', 'tl.autoboost_hint': 'Their new posts keep showing in your Circle (local, no fediverse boost).', 'tl.pending': 'pending', 'tl.unboost': 'Unboost', 'tl.feed': 'Posts', 'tl.tab_feed': 'News', 'tl.tab_following': 'Following', 'tl.tab_replies': 'Replies', 'tl.empty_following': 'You do not follow anyone yet.', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →', 'tl.open_player': 'Open the player', 'tl.paste_ph': 'Paste a fediverse post URL', 'tl.paste_go': 'Open', 'tl.boosted': 'boosted', 'tl.read_more': 'Read more', 'tl.show_less': 'Show less', 'poll.vote': 'Vote', 'poll.votes': 'votes', 'poll.closed': 'closed',
+    'fedi.remote_title': 'Reply via the fediverse', 'fedi.follow_heading': 'Follow via the fediverse', 'fedi.profile_follow': 'Follow via the fediverse', 'profile.since': 'On Klonkt since', 'profile.free': 'Free', 'fedi.follow_intro': 'You are about to follow:', 'fedi.follow_btn': 'Follow', 'fedi.cancel': 'Cancel', 'fedi.followed_title': 'Follow request sent ✅', 'fedi.followed_done': 'Your follow request is on its way. Once accepted, their posts show up in your timeline.', 'fedi.view_profile': 'View profile →', 'fedi.remote_reply': 'Reply via the fediverse', 'fedi.remote_prompt': 'Your fediverse address:', 'fedi.remote_notfound': 'Could not fetch that post. Paste the full post URL:', 'fedi.remote_load': 'Fetch', 'fedi.remote_replying_to': 'Replying to', 'fedi.remote_as': 'Sent as {site}.', 'fedi.remote_view_original': 'View the full post + comments on the source →', 'fedi.remote_reply_short': 'via the fediverse', 'fedi.like_short': 'Like', 'fedi.unlike_short': 'Unlike', 'fedi.boost_short': 'Boost', 'fedi.remote_ph': 'your server', 'fedi.remote_sent_title': 'Sent ✅', 'fedi.remote_sent': 'Your reply has been sent. It will show up on the original post on the fediverse, not on this page. See it there:', 'fedi.reply_where': 'Your reply appears on the original post on the fediverse, not on this page. Use the link above to see it there.', 'fedi.remote_back': '← Back to your site', 'fedi.like_btn': 'Like this post', 'fedi.or_reply': 'or reply:', 'fedi.liked_title': 'Liked', 'fedi.liked_done': 'Your like is on its way to the fediverse.', 'fedi.boost_btn': 'Boost this post', 'fedi.boosted_title': 'Boosted', 'fedi.boosted_done': 'Your boost is on its way to the fediverse.', 'fedi.remote_interact': 'Interact via the fediverse', 'fedi.delete_confirm': 'Delete this reply?', 'fedi.manage_title': 'My fediverse replies', 'fedi.manage_empty': 'You have not sent any replies yet.', 'fedi.goto_post': 'Go to post', 'fedi.edit': 'Edit', 'fedi.save_edit': 'Save', 'fedi.bm_label': 'Interact via my site', 'fedi.bm_help': 'Drag this button to your bookmarks bar. Then click it on any fediverse post (Mastodon, another Klonkt…) to reply, like or boost it via your own site.', 'tl.title': 'News', 'tl.lead': 'Follow accounts on the fediverse and see their posts here.', 'tl.follow_btn': 'Follow', 'tl.following': 'Following', 'tl.unfollow': 'Unfollow', 'tl.autoboost': 'Featured', 'tl.autoboost_follow': 'feature in circle', 'tl.autoboost_hint': 'Their new posts keep showing in your Circle (local, no fediverse boost).', 'tl.pending': 'pending', 'tl.unboost': 'Unboost', 'tl.feed': 'Posts', 'tl.tab_feed': 'News', 'tl.tab_following': 'Following', 'tl.tab_replies': 'Replies', 'tl.empty_following': 'You do not follow anyone yet.', 'tl.empty': 'Nothing yet — follow someone to see their posts here.', 'tl.view_original': 'View original →', 'tl.open_player': 'Open the player', 'tl.paste_ph': 'Paste a fediverse post URL', 'tl.paste_go': 'Open', 'tl.boosted': 'boosted', 'tl.read_more': 'Read more', 'tl.show_less': 'Show less', 'poll.vote': 'Vote', 'poll.votes': 'votes', 'poll.closed': 'closed', 'poll.aria': 'Poll', 'poll.voter_one': 'voter', 'poll.voter_many': 'voters', 'poll.closes': 'closes', 'poll.multiple': 'multiple choice', 'poll.fedi_only': 'Voting happens on the fediverse — follow this site and vote from your own app.',
     'comments.to_start': 'to start the conversation.',
     'comments.reply': 'Reply', 'comments.delete': 'Delete', 'comments.cancel': 'Cancel',
@@ -1615,5 +1615,5 @@
     'pedit.pin_top': 'at the top',
     'pedit.pin_nth_suffix': 'th from top',
-    'pedit.noindex_label': 'noindex (hide from search engines)', 'pedit.nsfw_label': 'NSFW / sensitive content', 'pedit.fedi_audio_label': 'Share audio openly on the fediverse (plays inline in apps; file downloadable)', 'pedit.nsfw_cw_ph': 'Warning text (optional, default: Sensitive content)', 'post.nsfw_warning': 'Sensitive content', 'post.nsfw_show': 'Show', 'post.share': 'Share', 'post.share_copied': 'Link copied ✓',
+    'pedit.noindex_label': 'noindex (hide from search engines)', 'pedit.nsfw_label': 'NSFW / sensitive content', 'pedit.fedi_audio_label': 'Share audio openly on the fediverse (plays inline in apps; file downloadable)', 'pedit.nsfw_cw_ph': 'Warning text (optional, default: Sensitive content)', 'post.nsfw_warning': 'Sensitive content', 'post.nsfw_show': 'Show', 'post.share': 'Share', 'post.share_copied': 'Link copied ✓', 'pedit.poll_label': 'Add a poll', 'pedit.poll_locked': 'Votes are in — the options can no longer change.', 'pedit.poll_option_ph': 'Option', 'pedit.poll_add': 'Add option', 'pedit.poll_multiple': 'Allow multiple choices', 'pedit.poll_duration': 'Duration', 'pedit.poll_dur_5m': '5 minutes', 'pedit.poll_dur_30m': '30 minutes', 'pedit.poll_dur_1h': '1 hour', 'pedit.poll_dur_6h': '6 hours', 'pedit.poll_dur_12h': '12 hours', 'pedit.poll_dur_1d': '1 day', 'pedit.poll_dur_3d': '3 days', 'pedit.poll_dur_7d': '7 days',
     'pedit.fan_only_label': 'Logged-in fans only',
     'pedit.schedule_label': 'Schedule publication',
@@ -1951,5 +1951,5 @@
     'fedi.heading': 'Aus dem Fediverse', 'fedi.likes': 'Favoriten', 'fedi.boosts': 'Boosts', 'fedi.replies': 'Antworten aus dem Fediverse',
     'fedi.reply': 'Antworten', 'fedi.reply_ph': 'Deine Antwort an das Fediverse…', 'fedi.send': 'Senden', 'fedi.you': 'Du',
-    'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.follow_heading': 'Über das Fediverse folgen', 'fedi.profile_follow': 'Über das Fediverse folgen', 'profile.since': 'Auf Klonkt seit', 'profile.free': 'Kostenlos', 'fedi.follow_intro': 'Du folgst gleich:', 'fedi.follow_btn': 'Folgen', 'fedi.cancel': 'Abbrechen', 'fedi.followed_title': 'Folge-Anfrage gesendet ✅', 'fedi.followed_done': 'Deine Folge-Anfrage ist unterwegs. Sobald sie akzeptiert wird, erscheinen ihre Beiträge in deiner Timeline.', 'fedi.view_profile': 'Profil ansehen →', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse:', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.like_short': 'Liken', 'fedi.unlike_short': 'Like zurücknehmen', 'fedi.boost_short': 'Boosten', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort wurde gesendet. Sie erscheint gleich beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Sieh sie dir dort an:', 'fedi.reply_where': 'Deine Antwort erscheint beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Über den Link oben siehst du sie dort.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.boost_btn': 'Diesen Beitrag boosten', 'fedi.boosted_title': 'Geboostet', 'fedi.boosted_done': 'Dein Boost ist unterwegs ins Fediverse.', 'fedi.remote_interact': 'Übers Fediverse interagieren', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'fedi.goto_post': 'Zur Post', 'fedi.edit': 'Bearbeiten', 'fedi.save_edit': 'Speichern', 'fedi.bm_label': 'Über meine Seite interagieren', 'fedi.bm_help': 'Zieh diesen Button in deine Lesezeichenleiste. Klick ihn dann auf einem beliebigen Fediverse-Beitrag (Mastodon, ein anderes Klonkt…), um über deine eigene Seite zu antworten, zu liken oder zu boosten.', 'tl.title': 'Zeitung', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.autoboost': 'Hervorgehoben', 'tl.autoboost_follow': 'im Zirkel hervorheben', 'tl.autoboost_hint': 'Ihre neuen Beiträge erscheinen laufend in deinem Zirkel (lokal, kein Fediverse-Boost).', 'tl.pending': 'ausstehend', 'tl.unboost': 'Boost zurücknehmen', 'tl.feed': 'Beiträge', 'tl.tab_feed': 'Zeitung', 'tl.tab_following': 'Folge ich', 'tl.tab_replies': 'Antworten', 'tl.empty_following': 'Du folgst noch niemandem.', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →', 'tl.open_player': 'Player öffnen', 'tl.paste_ph': 'URL eines Fediverse-Beitrags einfügen', 'tl.paste_go': 'Öffnen', 'tl.boosted': 'hat geteilt', 'tl.read_more': 'Mehr lesen', 'tl.show_less': 'Weniger', 'poll.vote': 'Abstimmen', 'poll.votes': 'Stimmen', 'poll.closed': 'geschlossen',
+    'fedi.remote_title': 'Über das Fediverse antworten', 'fedi.follow_heading': 'Über das Fediverse folgen', 'fedi.profile_follow': 'Über das Fediverse folgen', 'profile.since': 'Auf Klonkt seit', 'profile.free': 'Kostenlos', 'fedi.follow_intro': 'Du folgst gleich:', 'fedi.follow_btn': 'Folgen', 'fedi.cancel': 'Abbrechen', 'fedi.followed_title': 'Folge-Anfrage gesendet ✅', 'fedi.followed_done': 'Deine Folge-Anfrage ist unterwegs. Sobald sie akzeptiert wird, erscheinen ihre Beiträge in deiner Timeline.', 'fedi.view_profile': 'Profil ansehen →', 'fedi.remote_reply': 'Über das Fediverse antworten', 'fedi.remote_prompt': 'Deine Fediverse-Adresse:', 'fedi.remote_notfound': 'Beitrag konnte nicht geladen werden. Füge die vollständige Beitrags-URL ein:', 'fedi.remote_load': 'Laden', 'fedi.remote_replying_to': 'Antwort an', 'fedi.remote_as': 'Wird als {site} gesendet.', 'fedi.remote_view_original': 'Ganzen Beitrag + Kommentare an der Quelle ansehen →', 'fedi.remote_reply_short': 'übers Fediverse', 'fedi.like_short': 'Liken', 'fedi.unlike_short': 'Like zurücknehmen', 'fedi.boost_short': 'Boosten', 'fedi.remote_ph': 'dein Server', 'fedi.remote_sent_title': 'Gesendet ✅', 'fedi.remote_sent': 'Deine Antwort wurde gesendet. Sie erscheint gleich beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Sieh sie dir dort an:', 'fedi.reply_where': 'Deine Antwort erscheint beim Originalbeitrag im Fediverse, nicht auf dieser Seite. Über den Link oben siehst du sie dort.', 'fedi.remote_back': '← Zurück zu deiner Seite', 'fedi.like_btn': 'Diesen Beitrag liken', 'fedi.or_reply': 'oder antworten:', 'fedi.liked_title': 'Geliked', 'fedi.liked_done': 'Dein Like ist unterwegs ins Fediverse.', 'fedi.boost_btn': 'Diesen Beitrag boosten', 'fedi.boosted_title': 'Geboostet', 'fedi.boosted_done': 'Dein Boost ist unterwegs ins Fediverse.', 'fedi.remote_interact': 'Übers Fediverse interagieren', 'fedi.delete_confirm': 'Diese Antwort löschen?', 'fedi.manage_title': 'Meine Fediverse-Antworten', 'fedi.manage_empty': 'Du hast noch keine Antworten gesendet.', 'fedi.goto_post': 'Zur Post', 'fedi.edit': 'Bearbeiten', 'fedi.save_edit': 'Speichern', 'fedi.bm_label': 'Über meine Seite interagieren', 'fedi.bm_help': 'Zieh diesen Button in deine Lesezeichenleiste. Klick ihn dann auf einem beliebigen Fediverse-Beitrag (Mastodon, ein anderes Klonkt…), um über deine eigene Seite zu antworten, zu liken oder zu boosten.', 'tl.title': 'Zeitung', 'tl.lead': 'Folge Konten im Fediverse und sieh ihre Beiträge hier.', 'tl.follow_btn': 'Folgen', 'tl.following': 'Du folgst', 'tl.unfollow': 'Entfolgen', 'tl.autoboost': 'Hervorgehoben', 'tl.autoboost_follow': 'im Zirkel hervorheben', 'tl.autoboost_hint': 'Ihre neuen Beiträge erscheinen laufend in deinem Zirkel (lokal, kein Fediverse-Boost).', 'tl.pending': 'ausstehend', 'tl.unboost': 'Boost zurücknehmen', 'tl.feed': 'Beiträge', 'tl.tab_feed': 'Zeitung', 'tl.tab_following': 'Folge ich', 'tl.tab_replies': 'Antworten', 'tl.empty_following': 'Du folgst noch niemandem.', 'tl.empty': 'Noch nichts — folge jemandem, um Beiträge hier zu sehen.', 'tl.view_original': 'Original ansehen →', 'tl.open_player': 'Player öffnen', 'tl.paste_ph': 'URL eines Fediverse-Beitrags einfügen', 'tl.paste_go': 'Öffnen', 'tl.boosted': 'hat geteilt', 'tl.read_more': 'Mehr lesen', 'tl.show_less': 'Weniger', 'poll.vote': 'Abstimmen', 'poll.votes': 'Stimmen', 'poll.closed': 'geschlossen', 'poll.aria': 'Umfrage', 'poll.voter_one': 'Teilnehmer', 'poll.voter_many': 'Teilnehmer', 'poll.closes': 'endet am', 'poll.multiple': 'Mehrfachauswahl', 'poll.fedi_only': 'Abstimmen geht über das Fediverse — folge dieser Seite und stimme in deiner eigenen App ab.',
     'comments.to_start': 'um das Gespräch zu starten.',
     'comments.reply': 'Antworten', 'comments.delete': 'Löschen', 'comments.cancel': 'Abbrechen',
@@ -2533,5 +2533,5 @@
     'pedit.pin_top': 'ganz oben',
     'pedit.pin_nth_suffix': '. von oben',
-    'pedit.noindex_label': 'noindex (vor Suchmaschinen verbergen)', 'pedit.nsfw_label': 'NSFW / sensibler Inhalt', 'pedit.fedi_audio_label': 'Audio offen im Fediverse teilen (spielt inline in Apps; Datei herunterladbar)', 'pedit.nsfw_cw_ph': 'Warntext (optional, Standard: Sensibler Inhalt)', 'post.nsfw_warning': 'Sensibler Inhalt', 'post.nsfw_show': 'Anzeigen', 'post.share': 'Teilen', 'post.share_copied': 'Link kopiert ✓',
+    'pedit.noindex_label': 'noindex (vor Suchmaschinen verbergen)', 'pedit.nsfw_label': 'NSFW / sensibler Inhalt', 'pedit.fedi_audio_label': 'Audio offen im Fediverse teilen (spielt inline in Apps; Datei herunterladbar)', 'pedit.nsfw_cw_ph': 'Warntext (optional, Standard: Sensibler Inhalt)', 'post.nsfw_warning': 'Sensibler Inhalt', 'post.nsfw_show': 'Anzeigen', 'post.share': 'Teilen', 'post.share_copied': 'Link kopiert ✓', 'pedit.poll_label': 'Umfrage hinzufügen', 'pedit.poll_locked': 'Es wurde bereits abgestimmt — die Optionen lassen sich nicht mehr ändern.', 'pedit.poll_option_ph': 'Option', 'pedit.poll_add': 'Option hinzufügen', 'pedit.poll_multiple': 'Mehrfachauswahl erlauben', 'pedit.poll_duration': 'Laufzeit', 'pedit.poll_dur_5m': '5 Minuten', 'pedit.poll_dur_30m': '30 Minuten', 'pedit.poll_dur_1h': '1 Stunde', 'pedit.poll_dur_6h': '6 Stunden', 'pedit.poll_dur_12h': '12 Stunden', 'pedit.poll_dur_1d': '1 Tag', 'pedit.poll_dur_3d': '3 Tage', 'pedit.poll_dur_7d': '7 Tage',
     'pedit.fan_only_label': 'Nur für angemeldete Fans',
     'pedit.schedule_label': 'Veröffentlichung planen',
Index: src/views/pages/post-edit.ejs
===================================================================
--- src/views/pages/post-edit.ejs	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/views/pages/post-edit.ejs	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -257,4 +257,49 @@
           if (cw && nsfw && !cw.__nsfwWired) { cw.__nsfwWired = true;
             cw.addEventListener('input', function () { if (cw.value.trim()) nsfw.checked = true; });
+          }
+        })();
+        </script>
+        <% // Poll (federates as an AS2 Question). Free feature. A poll with votes is frozen.
+           var _poll = null; try { _poll = post.poll_json ? JSON.parse(post.poll_json) : null; } catch (e) { _poll = null; }
+           var _pollLocked = (typeof pollLocked !== 'undefined' && pollLocked);
+           var _pollOpts = (_poll && Array.isArray(_poll.options) && _poll.options.length) ? _poll.options : [{ name: '' }, { name: '' }]; %>
+        <label class="pe-checkbox" style="margin-top:8px">
+          <input type="checkbox" name="poll_enabled" value="1" id="pe-poll-toggle" <%= _poll ? 'checked' : '' %> <%= _pollLocked ? 'disabled' : '' %>>
+          <span>📊 <%= t('pedit.poll_label') %></span>
+        </label>
+        <div id="pe-poll-fields" style="margin-top:6px<%= _poll ? '' : ';display:none' %>">
+          <% if (_pollLocked) { %><p style="font-size:12px;opacity:.7;margin:0 0 6px"><%= t('pedit.poll_locked') %></p><% } %>
+          <div id="pe-poll-opts">
+            <% _pollOpts.forEach(function (o) { %>
+              <input type="text" name="poll_option" class="pe-poll-opt" maxlength="100" value="<%= (o && o.name) || '' %>" placeholder="<%= t('pedit.poll_option_ph') %>" <%= _pollLocked ? 'disabled' : '' %>
+                     style="display:block;width:100%;box-sizing:border-box;margin-bottom:5px;font-size:13px;padding:7px 9px;border-radius:7px;border:1px solid var(--rule,rgba(128,128,128,.35));background:transparent;color:inherit">
+            <% }); %>
+          </div>
+          <button type="button" id="pe-poll-add" class="btn" data-ph="<%= t('pedit.poll_option_ph') %>" <%= _pollLocked ? 'disabled' : '' %> style="font-size:12.5px;padding:5px 10px">+ <%= t('pedit.poll_add') %></button>
+          <label class="pe-checkbox" style="margin-top:8px">
+            <input type="checkbox" name="poll_multiple" value="1" <%= (_poll && _poll.multiple) ? 'checked' : '' %> <%= _pollLocked ? 'disabled' : '' %>>
+            <span><%= t('pedit.poll_multiple') %></span>
+          </label>
+          <label style="display:block;font-size:12.5px;opacity:.8;margin:6px 0 4px"><%= t('pedit.poll_duration') %></label>
+          <select name="poll_duration" <%= _pollLocked ? 'disabled' : '' %> style="padding:7px 9px;border-radius:7px;border:1px solid var(--rule,rgba(128,128,128,.35));background:transparent;color:inherit;font-size:13px">
+            <% [['300','5m'],['1800','30m'],['3600','1h'],['21600','6h'],['43200','12h'],['86400','1d'],['259200','3d'],['604800','7d']].forEach(function (d) { %>
+              <option value="<%= d[0] %>" <%= d[0] === '86400' ? 'selected' : '' %>><%= t('pedit.poll_dur_' + d[1]) %></option>
+            <% }); %>
+          </select>
+        </div>
+        <script>
+        (function () {
+          var tog = document.getElementById('pe-poll-toggle'), box = document.getElementById('pe-poll-fields');
+          if (tog && box && !tog.__wired) { tog.__wired = true; tog.addEventListener('change', function () { box.style.display = tog.checked ? '' : 'none'; }); }
+          var add = document.getElementById('pe-poll-add'), opts = document.getElementById('pe-poll-opts');
+          if (add && opts && !add.__wired) { add.__wired = true;
+            add.addEventListener('click', function () {
+              if (opts.querySelectorAll('.pe-poll-opt').length >= 8) return;
+              var i = document.createElement('input');
+              i.type = 'text'; i.name = 'poll_option'; i.className = 'pe-poll-opt'; i.maxLength = 100;
+              i.placeholder = add.getAttribute('data-ph') || '';
+              i.setAttribute('style', 'display:block;width:100%;box-sizing:border-box;margin-bottom:5px;font-size:13px;padding:7px 9px;border-radius:7px;border:1px solid var(--rule,rgba(128,128,128,.35));background:transparent;color:inherit');
+              opts.appendChild(i);
+            });
           }
         })();
Index: src/views/pages/post.ejs
===================================================================
--- src/views/pages/post.ejs	(revision 731e431f5dd53b164f13bdf3cad33358f201f785)
+++ src/views/pages/post.ejs	(revision 04031873473c62253ea8961f8fc5cd47159ac270)
@@ -44,4 +44,27 @@
     <%- post.content_html %>
   </div>
+
+  <% if (typeof poll !== 'undefined' && poll) { %>
+    <section class="poll" aria-label="<%= t('poll.aria') %>">
+      <% poll.options.forEach(function(o){ var _lead = poll.total > 0 && o.count === Math.max.apply(null, poll.options.map(function(x){return x.count;})); %>
+        <div class="poll-opt<%= _lead ? ' is-lead' : '' %>">
+          <div class="poll-bar" style="width:<%= o.pct %>%"></div>
+          <span class="poll-name"><%= o.name %></span>
+          <span class="poll-pct"><%= o.pct %>%</span>
+        </div>
+      <% }); %>
+      <div class="poll-meta">
+        <span><%= poll.voters %> <%= poll.voters === 1 ? t('poll.voter_one') : t('poll.voter_many') %></span>
+        <span aria-hidden="true">·</span>
+        <% if (poll.closed) { %>
+          <span><%= t('poll.closed') %></span>
+        <% } else if (poll.endTime) { %>
+          <span><%= t('poll.closes') %> <%= new Date(poll.endTime).toLocaleString() %></span>
+        <% } %>
+        <% if (poll.multiple) { %><span aria-hidden="true">·</span><span><%= t('poll.multiple') %></span><% } %>
+      </div>
+      <p class="poll-note"><%= t('poll.fedi_only') %></p>
+    </section>
+  <% } %>
 
   <% if (post.tags && post.tags.length > 0) { %>
@@ -210,4 +233,14 @@
 .post-content { font-family: var(--font-body, serif); font-size: 1.1rem; line-height: 1.7; color: var(--ink); }
 .post-content p { margin: 1.2em 0; }
+/* Poll (a hosted AS2 Question) — display-only; voting happens from the fediverse. */
+.poll { margin: 1.75rem 0; display: flex; flex-direction: column; gap: .5rem; }
+.poll-opt { position: relative; display: flex; align-items: center; gap: .5rem; padding: .55rem .75rem; border: 1px solid var(--line, rgba(128,128,128,.25)); border-radius: 8px; overflow: hidden; font-size: .98rem; }
+.poll-bar { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--accent) 18%, transparent); z-index: 0; transition: width .3s ease; }
+.poll-opt.is-lead .poll-bar { background: color-mix(in srgb, var(--accent) 30%, transparent); }
+.poll-name { position: relative; z-index: 1; flex: 1; color: var(--ink); }
+.poll-opt.is-lead .poll-name { font-weight: 600; }
+.poll-pct { position: relative; z-index: 1; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
+.poll-meta { display: flex; flex-wrap: wrap; gap: .4rem; font-size: .85rem; color: var(--ink-muted); }
+.poll-note { font-size: .8rem; color: var(--ink-muted); margin: .1rem 0 0; }
 .like-btn {
   display: inline-flex; align-items: center; gap: 0.45rem;
