Index: src/assets/css/style.css
===================================================================
--- src/assets/css/style.css	(revision 0ca7e9a4accafc47292602b2067055a4cc416408)
+++ src/assets/css/style.css	(revision bfa6fa1e058aa9b3c2821d56a16842937aeed22a)
@@ -559,4 +559,24 @@
 
 .empty { text-align: center; padding: 4rem 1rem; }
+/* Moved account (FEP-7628 slice 3): the signpost above the feed. Loud on
+   purpose — a visitor must not read a stale timeline thinking it is current. */
+.moved-banner {
+    text-align: center;
+    margin: 1.5rem 0 2rem;
+    padding: 1.75rem 1.25rem;
+    border: 2px solid var(--accent, #e8b04b);
+    border-radius: var(--radius, 10px);
+    background: color-mix(in srgb, var(--accent, #e8b04b) 10%, var(--paper-2, var(--paper)));
+}
+.moved-banner-lead { margin: 0 0 .5rem; font-size: 1rem; opacity: .85; }
+.moved-banner-link {
+    display: inline-block;
+    font-family: var(--font-display);
+    font-size: clamp(1.5rem, 5vw, 2.4rem);
+    font-weight: 700;
+    line-height: 1.15;
+    overflow-wrap: anywhere;
+}
+.moved-banner-hint { margin: .75rem 0 0; font-size: .85rem; color: var(--ink-soft); }
 .empty h1 { font-size: 2rem; }
 .empty p { color: var(--ink-muted); }
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 0ca7e9a4accafc47292602b2067055a4cc416408)
+++ src/routes/posts.js	(revision bfa6fa1e058aa9b3c2821d56a16842937aeed22a)
@@ -232,8 +232,13 @@
   recordPageview(site.id, req);
 
+  // FEP-7628 slice 3: this account moved. A visitor who lands here deserves
+  // the same signpost the fediverse gets — one big link to the new address.
+  const movedTo = site.moved_to && /^https?:\/\//i.test(String(site.moved_to)) ? String(site.moved_to) : null;
   renderPage(req, res, 'pages/home', {
     pinnedPosts,
     posts,
     hasMore, nextOffset: offset + FEED_PAGE, moreBase,
+    movedTo,
+    movedToLabel: movedTo ? (ActivityPubService.actorDisplay(site.slug, movedTo).handle || movedTo) : null,
     pageTitle: site.title,
     socialDescr: site.description || site.tagline || '',
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 0ca7e9a4accafc47292602b2067055a4cc416408)
+++ src/services/ActivityPubService.js	(revision bfa6fa1e058aa9b3c2821d56a16842937aeed22a)
@@ -234,4 +234,10 @@
     }
   } catch { /* skip malformed ap_aliases */ }
+  // FEP-7628 slice 3: this account moved. The old actor stays online AS A
+  // SIGNPOST — that is the whole point of keeping it: whoever missed the Move
+  // activity (offline server, later visitor) still learns where we went by
+  // fetching us. Per the FEP the moved actor "should be considered inactive",
+  // and publishers should stop delivering here.
+  if (site.moved_to && /^https?:\/\//i.test(String(site.moved_to))) actor.movedTo = String(site.moved_to);
   // Profile links → PropertyValue rows: Mastodon/PeerTube/WordPress-ActivityPub render these as
   // profile metadata (rel=me enables link-back verification). Additive; ignored by simpler receivers.
@@ -3892,7 +3898,20 @@
     to: [`${me}/followers`],
   };
+  // FEP-7628: after setting movedTo, notify the followers with an Update of
+  // the actor, so their servers hold the signpost even if the Move itself is
+  // lost. Built from the FRESH row: `site` still carries the pre-move values.
+  const movedSite = db.prepare('SELECT * FROM sites WHERE slug = ?').get(site.slug) || { ...site, moved_to: target.id };
+  const update = {
+    '@context': AP_CONTEXT,
+    id: `${me}#update-${Date.now()}-${rid()}`,
+    type: 'Update', actor: me, to: [PUBLIC], cc: [`${me}/followers`],
+    object: buildActor(base, movedSite),
+    published: new Date().toISOString(),
+  };
   const inboxes = [...new Set(fStmts().list.all(site.slug).map((f) => f.shared_inbox || f.inbox).filter(Boolean))];
+  const send = deliverFn || deliverWithRetry;
   for (const inbox of inboxes) {
-    await (deliverFn || deliverWithRetry)(site.slug, inbox, move, `${me}#main-key`, keys.private_pem);
+    await send(site.slug, inbox, update, `${me}#main-key`, keys.private_pem);
+    await send(site.slug, inbox, move, `${me}#main-key`, keys.private_pem);
   }
   console.log('[AP] MOVE announced:', site.slug, '→', target.id, 'naar', inboxes.length, 'inbox(en)');
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision 0ca7e9a4accafc47292602b2067055a4cc416408)
+++ src/services/i18n.js	(revision bfa6fa1e058aa9b3c2821d56a16842937aeed22a)
@@ -912,4 +912,6 @@
     'vblk.back': 'Terug',
     'vblk.to_home': 'Naar de hoofdpagina',
+    'phome.moved_lead': 'Dit account is verhuisd. Je vindt me nu hier:',
+    'phome.moved_hint': 'Volg je me al? Dan verhuist je server je waarschijnlijk vanzelf mee. Zo niet: volg het nieuwe adres.',
     'phome.empty_title': 'Hier is het nog stil.',
     'phome.empty_sub': 'Nog geen posts. Spannend.',
@@ -1854,4 +1856,6 @@
     'vblk.back': 'Back',
     'vblk.to_home': 'Go to the main page',
+    'phome.moved_lead': 'This account has moved. You can find me here now:',
+    'phome.moved_hint': 'Already following me? Your server most likely moves you along by itself. If not: follow the new address.',
     'phome.empty_title': "It's quiet here for now.",
     'phome.empty_sub': 'No posts yet. Exciting.',
@@ -2796,4 +2800,6 @@
     'vblk.back': 'Zurück',
     'vblk.to_home': 'Zur Hauptseite',
+    'phome.moved_lead': 'Dieses Konto ist umgezogen. Du findest mich jetzt hier:',
+    'phome.moved_hint': 'Folgst du mir schon? Dann zieht dein Server dich wahrscheinlich automatisch mit. Falls nicht: folge der neuen Adresse.',
     'phome.empty_title': 'Hier ist es noch still.',
     'phome.empty_sub': 'Noch keine Beiträge. Spannend.',
Index: src/views/pages/home.ejs
===================================================================
--- src/views/pages/home.ejs	(revision 0ca7e9a4accafc47292602b2067055a4cc416408)
+++ src/views/pages/home.ejs	(revision bfa6fa1e058aa9b3c2821d56a16842937aeed22a)
@@ -17,4 +17,18 @@
     #pcms-main) so it survives HTMX swaps and is visible on every
     non-admin page. %>
+
+<%# FEP-7628 slice 3: this account moved. The signpost comes FIRST, before
+    any post: a visitor should not scroll a dead feed to find out we left.
+    typeof-guard because an old route may render this template for one
+    request right after a deploy (view cache fills on first hit). %>
+<% if (typeof movedTo !== 'undefined' && movedTo) { %>
+  <div class="container">
+    <aside class="moved-banner">
+      <p class="moved-banner-lead"><%= t('phome.moved_lead') %></p>
+      <a class="moved-banner-link" href="<%= movedTo %>" rel="me noopener"><%= movedToLabel || movedTo %></a>
+      <p class="moved-banner-hint"><%= t('phome.moved_hint') %></p>
+    </aside>
+  </div>
+<% } %>
 
 <!-- ========== TIMELINE ========== -->
