Index: src/routes/paid.js
===================================================================
--- src/routes/paid.js	(revision dede82e58262792fc792f19d0d44e161753764db)
+++ 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)}` });
 });
 
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision dede82e58262792fc792f19d0d44e161753764db)
+++ src/routes/posts.js	(revision 072a24208b5f98d3ac8b491384e7a63293f98239)
@@ -22,4 +22,5 @@
 import { premiumUnlocked } from '../services/PatreonService.js';
 import { defaultMinCents as paidDefaultMinCents, patreonUrl as paidPatronUrl } from '../services/PaidPatreonService.js';
+import { verifyBlob } from '../services/CryptoBox.js';
 import MusicMeta from '../services/MusicMeta.js';
 
@@ -1238,5 +1239,11 @@
   // never see the unlock button).
   const canEditThis = req.session?.user && PermissionsService.canEditPost(req.session.user, post, site);
-  if (post.paid && !canEditThis) {
+  // A fresh unlock capability (?u=) from /paid/unlock lets a just-verified
+  // supporter render the FULL post through this normal template (correct layout,
+  // scoped styles, working audio). Short-lived signed blob, single post, not a
+  // cookie and not stored.
+  const _u = req.query.u ? verifyBlob(String(req.query.u)) : null;
+  const _unlocked = _u && _u.purpose === 'unlocked' && _u.siteId === site.id && String(_u.post) === String(post.slug);
+  if (post.paid && !canEditThis && !_unlocked) {
     const { newerPost, olderPost } = postNeighbors(site, post, res.locals.tenancy === 'hub');
     return renderPage(req, res, 'pages/paid-gate', {
Index: src/views/pages/paid-gate.ejs
===================================================================
--- src/views/pages/paid-gate.ejs	(revision dede82e58262792fc792f19d0d44e161753764db)
+++ src/views/pages/paid-gate.ejs	(revision 072a24208b5f98d3ac8b491384e7a63293f98239)
@@ -37,11 +37,18 @@
   var base = "<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>";
   var slug = "<%= pgSlug %>";
+  var hasPatron = <%= _hasPatron ? 'true' : 'false' %>;
   var btn = document.getElementById('pg-unlock');
   var status = document.getElementById('pg-status');
-  var page = document.getElementById('pg-page');
   function say(msg, err) { status.hidden = false; status.textContent = msg; status.classList.toggle('is-err', !!err); }
   function toLink() { location.href = base + '/paid/link?post=' + encodeURIComponent(slug); }
 
-  if (!window.SimpleWebAuthnBrowser || !window.PublicKeyCredential) { btn.textContent = 'Word supporter'; btn.addEventListener('click', toLink); return; }
+  // No WebAuthn here: an assertion is impossible. With a Patreon page there is
+  // already a "Word supporter" button, so hide the (dead) unlock button rather
+  // than turn it into a second "Word supporter". Without one, this IS the button.
+  if (!window.SimpleWebAuthnBrowser || !window.PublicKeyCredential) {
+    if (hasPatron) { btn.style.display = 'none'; }
+    else { btn.textContent = 'Word supporter'; btn.addEventListener('click', toLink); }
+    return;
+  }
 
   btn.addEventListener('click', function () {
@@ -61,10 +68,8 @@
       .then(function (r) { return r.json().then(function (j) { return { status: r.status, j: j }; }); })
       .then(function (res) {
-        if (res.j && res.j.ok) {
-          // Swap the gate for the full post, client-side (no cookie kept).
-          var h = document.createElement('div');
-          h.innerHTML = (res.j.title ? '<h1 class="post-title">' + res.j.title + '</h1>' : '') +
-            '<div class="post-content">' + res.j.html + '</div>';
-          page.replaceWith(h);
+        if (res.j && res.j.ok && res.j.redirect) {
+          // Reload the real post page via the one-shot unlock capability, so it
+          // renders through its normal template (layout, styles, audio).
+          location.href = res.j.redirect;
         } else if (res.status === 403) {
           toLink();   // no valid passkey yet (or lapsed tier): link via Patreon
