Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 9d3485567cfa5e63e210609b3bb7ec38ec905976)
+++ src/routes/posts.js	(revision 0a7535630de8d7f1d893cbb685aa1a8512184e0d)
@@ -693,43 +693,31 @@
 });
 
+// Like / unlike a feed post — a toggle. Fetch request → JSON {on} (stay on the page,
+// no banner); no-JS → redirect back.
 router.post('/news/like', requireSiteManager, async (req, res) => {
   const site = res.locals.site;
   const note = (req.body.note || '').toString();
+  let on = false;
   if (site && note) {
-    try { await ActivityPubService.sendInteraction(site, 'like', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
-    ActivityPubService.markLiked(site.slug, note);
-  }
-  res.redirect('/news?success=' + encodeURIComponent('Geliket ⭐'));
-});
-
-router.post('/news/unlike', requireSiteManager, async (req, res) => {
+    on = !ActivityPubService.getTimelineReaction(site.slug, note).liked;
+    try { await ActivityPubService.sendInteraction(site, on ? 'like' : 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
+    if (on) ActivityPubService.markLiked(site.slug, note); else ActivityPubService.unmarkLiked(site.slug, note);
+  }
+  if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on });
+  res.redirect('/news');
+});
+
+// Boost / unboost a feed post — a toggle. markBoosted also surfaces it in the Cirkel.
+router.post('/news/boost', requireSiteManager, async (req, res) => {
   const site = res.locals.site;
   const note = (req.body.note || '').toString();
+  let on = false;
   if (site && note) {
-    try { await ActivityPubService.sendInteraction(site, 'unlike', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
-    ActivityPubService.unmarkLiked(site.slug, note);
-  }
-  res.redirect('/news?success=' + encodeURIComponent('Like ingetrokken'));
-});
-
-router.post('/news/boost', requireSiteManager, async (req, res) => {
-  const site = res.locals.site;
-  const note = (req.body.note || '').toString();
-  if (site && note) {
-    try { await ActivityPubService.sendInteraction(site, 'boost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
-    ActivityPubService.markBoosted(site.slug, note); // also surface it in the Cirkel (mixed by date)
-  }
-  res.redirect('/news?success=' + encodeURIComponent('Geboost 🔁'));
-});
-
-// Unboost (retract): send Undo(Announce) + clear the local flag.
-router.post('/news/unboost', requireSiteManager, async (req, res) => {
-  const site = res.locals.site;
-  const note = (req.body.note || '').toString();
-  if (site && note) {
-    try { await ActivityPubService.sendInteraction(site, 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
-    ActivityPubService.unmarkBoosted(site.slug, note);
-  }
-  res.redirect('/news?success=' + encodeURIComponent('Boost ingetrokken'));
+    on = !ActivityPubService.getTimelineReaction(site.slug, note).boosted;
+    try { await ActivityPubService.sendInteraction(site, on ? 'boost' : 'unboost', note, (req.body.author || '').toString()); } catch (e) { /* ignore */ }
+    if (on) ActivityPubService.markBoosted(site.slug, note); else ActivityPubService.unmarkBoosted(site.slug, note);
+  }
+  if (req.get('X-Requested-With') === 'fetch') return res.json({ ok: true, on });
+  res.redirect('/news');
 });
 
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 9d3485567cfa5e63e210609b3bb7ec38ec905976)
+++ src/services/ActivityPubService.js	(revision 0a7535630de8d7f1d893cbb685aa1a8512184e0d)
@@ -1106,4 +1106,7 @@
   try { if (!_unmarkLike) _unmarkLike = db.prepare('UPDATE ap_timeline SET liked = 0 WHERE slug = ? AND id = ?'); _unmarkLike.run(slug, noteId); } catch { /* ignore */ }
 }
+export function getTimelineReaction(slug, noteId) {
+  try { const r = db.prepare('SELECT liked, boosted FROM ap_timeline WHERE slug = ? AND id = ?').get(slug, noteId); return { liked: !!(r && r.liked), boosted: !!(r && r.boosted) }; } catch { return { liked: false, boosted: false }; }
+}
 export function boostedCount(slug) {
   try { if (!_boostedCount) _boostedCount = db.prepare('SELECT COUNT(*) AS n FROM ap_timeline WHERE slug = ? AND boosted = 1'); return _boostedCount.get(slug).n; } catch { return 0; }
@@ -1356,5 +1359,5 @@
   listOutbox, deliverOutboxDelete,
   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, getTimeline, sendInteraction,
-  autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getCirkelPosts, getCirkelMembers, selfHealTimeline,
+  autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, getCirkelPosts, getCirkelMembers, selfHealTimeline,
   getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
   deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
Index: src/views/pages/news.ejs
===================================================================
--- src/views/pages/news.ejs	(revision 9d3485567cfa5e63e210609b3bb7ec38ec905976)
+++ src/views/pages/news.ejs	(revision 0a7535630de8d7f1d893cbb685aa1a8512184e0d)
@@ -47,14 +47,6 @@
 
           <div class="tl-actions">
-            <% if (p.liked) { %>
-            <form method="post" action="/news/unlike" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like is-on" title="<%= t('fedi.unlike_short') %>" aria-label="<%= t('fedi.unlike_short') %>"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.9 5.88 6.49.95-4.7 4.58 1.11 6.46L12 17.96l-5.8 3.06 1.1-6.46-4.69-4.58 6.49-.95z"/></svg></button></form>
-            <% } else { %>
-            <form method="post" action="/news/like" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like" title="<%= t('fedi.likes') %>" aria-label="<%= t('fedi.likes') %>"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.9 5.88 6.49.95-4.7 4.58 1.11 6.46L12 17.96l-5.8 3.06 1.1-6.46-4.69-4.58 6.49-.95z"/></svg></button></form>
-            <% } %>
-            <% if (p.boosted) { %>
-            <form method="post" action="/news/unboost" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost is-on" title="<%= t('tl.unboost') %>" aria-label="<%= t('tl.unboost') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg></button></form>
-            <% } else { %>
-            <form method="post" action="/news/boost" class="tl-act-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost" title="<%= t('fedi.boosts') %>" aria-label="<%= t('fedi.boosts') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg></button></form>
-            <% } %>
+            <form method="post" action="/news/like" class="tl-act-form tl-react-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-like<%= p.liked ? ' is-on' : '' %>" title="<%= p.liked ? t('fedi.unlike_short') : t('fedi.likes') %>" aria-label="<%= p.liked ? t('fedi.unlike_short') : t('fedi.likes') %>"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2.6l2.9 5.88 6.49.95-4.7 4.58 1.11 6.46L12 17.96l-5.8 3.06 1.1-6.46-4.69-4.58 6.49-.95z"/></svg></button></form>
+            <form method="post" action="/news/boost" class="tl-act-form tl-react-form"><input type="hidden" name="note" value="<%= p.id %>"><input type="hidden" name="author" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-boost<%= p.boosted ? ' is-on' : '' %>" title="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>" aria-label="<%= p.boosted ? t('tl.unboost') : t('fedi.boosts') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg></button></form>
             <button type="button" class="tl-act tl-act-reply fedi-remote-reply-btn" data-fedi-uri="<%= p.id %>" data-fedi-ph="<%= t('fedi.remote_ph') %>" title="<%= t('fedi.remote_reply') %>" aria-label="<%= t('fedi.remote_reply') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg></button>
             <form method="post" action="/blocking/add" class="tl-act-form" onsubmit="return confirm('<%= t('tl.block') %>?');"><input type="hidden" name="target" value="<%= p.author_uri %>"><button type="submit" class="tl-act tl-act-block" title="<%= t('tl.block') %>" aria-label="<%= t('tl.block') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="4.9" y1="4.9" x2="19.1" y2="19.1"/></svg></button></form>
@@ -168,3 +160,22 @@
   window.addEventListener('scroll', close, true);
 })();
+
+/* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page (no reload, no banner). */
+(function(){
+  if (window.__newsReactWired) return; window.__newsReactWired = true;
+  document.addEventListener('submit', function(e){
+    var f = e.target.closest && e.target.closest('.tl-react-form');
+    if (!f) return;
+    e.preventDefault();
+    var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
+    btn.disabled = true;
+    var body = new URLSearchParams();
+    new FormData(f).forEach(function(v, k){ body.append(k, v); });
+    fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
+      .then(function(r){ return r.ok ? r.json() : null; })
+      .then(function(j){ if (j) btn.classList.toggle('is-on', !!j.on); })
+      .catch(function(){})
+      .then(function(){ btn.disabled = false; });
+  });
+})();
 </script>
