Index: src/routes/paid.js
===================================================================
--- src/routes/paid.js	(revision ec288dc84c580f311e1c6cd5900dbd67738f7f62)
+++ src/routes/paid.js	(revision 072a24208b5f98d3ac8b491384e7a63293f98239)
@@ -15,5 +15,4 @@
 import PaidPatreon from '../services/PaidPatreonService.js';
 import Passkey from '../services/PasskeyService.js';
-import { renderPostBodyHtml } from './posts.js';
 
 const router = express.Router();
@@ -128,5 +127,10 @@
   const post = db.prepare("SELECT * FROM posts WHERE site_id = ? AND slug = ? AND status = 'published'").get(r.site.id, String(payload.post || ''));
   if (!post || !post.paid) return res.status(404).json({ error: 'gone' });
-  res.json({ ok: true, title: post.title || '', html: renderPostBodyHtml(r.site, post, req) });
+  // Hand back a short-lived, single-post unlock capability. The client reloads
+  // the real post page with it (?u=), so the post renders through its normal
+  // template: correct layout, scoped styles, working audio. Not a cookie and
+  // not stored: a 120s signed blob that lives only in that one URL.
+  const token = signBlob({ purpose: 'unlocked', siteId: r.site.id, post: post.slug }, 120);
+  res.json({ ok: true, redirect: `${res.locals.siteUrlBase || ''}/${encodeURIComponent(post.slug)}?u=${encodeURIComponent(token)}` });
 });
 
