Index: src/views/pages/paid-passkey.ejs
===================================================================
--- src/views/pages/paid-passkey.ejs	(revision d43230fb28a7eb53abe716d29d94f498410f4edd)
+++ src/views/pages/paid-passkey.ejs	(revision d43230fb28a7eb53abe716d29d94f498410f4edd)
@@ -0,0 +1,59 @@
+<section class="pk">
+  <div class="pk-card">
+    <div class="pk-ic">🔑</div>
+    <h1 class="pk-h1">Je bent supporter, mooi.</h1>
+    <p class="pk-sub">
+      Maak nu een passkey aan. Die wordt je sleutel voor betaalde posts, zonder
+      account en zonder cookie. We bewaren geen naam of e-mailadres.
+    </p>
+    <button type="button" id="pk-go" class="pk-btn">Maak passkey</button>
+    <p id="pk-status" class="pk-status" hidden></p>
+  </div>
+</section>
+
+<script src="/assets/vendor/simplewebauthn-browser.umd.min.js" nonce="<%= cspNonce %>"></script>
+<script nonce="<%= cspNonce %>">
+(function () {
+  var options = <%- optionsJson %>;
+  var blob = "<%= regBlob %>";
+  var postUrl = "<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>/<%= postSlug %>";
+  var btn = document.getElementById('pk-go');
+  var status = document.getElementById('pk-status');
+  function say(msg, err) { status.hidden = false; status.textContent = msg; status.classList.toggle('is-err', !!err); }
+
+  if (!window.SimpleWebAuthnBrowser || !window.PublicKeyCredential) {
+    btn.disabled = true;
+    say('Passkeys worden niet ondersteund in deze browser.', true);
+    return;
+  }
+  btn.addEventListener('click', function () {
+    btn.disabled = true;
+    say('Volg de vraag van je apparaat…');
+    window.SimpleWebAuthnBrowser.startRegistration({ optionsJSON: options })
+      .then(function (response) {
+        return fetch('/paid/register', {
+          method: 'POST', headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ response: response, blob: blob }),
+        });
+      })
+      .then(function (r) { return r.json(); })
+      .then(function (j) {
+        if (j && j.ok) { say('Gelukt. Je passkey is aangemaakt.'); setTimeout(function () { location.href = postUrl; }, 900); }
+        else { btn.disabled = false; say('Aanmaken mislukt (' + ((j && j.error) || 'onbekend') + '). Probeer opnieuw.', true); }
+      })
+      .catch(function (e) { btn.disabled = false; say(e && e.name === 'NotAllowedError' ? 'Geannuleerd.' : 'Er ging iets mis. Probeer opnieuw.', true); });
+  });
+})();
+</script>
+
+<style>
+  .pk { max-width: 480px; margin: 0 auto; padding: 56px 18px; text-align: center; }
+  .pk-card { border: 1px solid color-mix(in srgb, var(--ink,#000) 16%, transparent); border-radius: 18px; padding: 34px 26px; }
+  .pk-ic { font-size: 40px; margin-bottom: 8px; }
+  .pk-h1 { font-size: 24px; margin: 0 0 6px; }
+  .pk-sub { opacity: .85; line-height: 1.6; margin: 0 0 20px; }
+  .pk-btn { padding: 12px 24px; border: none; border-radius: 10px; background: var(--accent,#6b8f71); color: #fff; font-weight: 600; font: inherit; font-weight: 600; cursor: pointer; }
+  .pk-btn:disabled { opacity: .6; cursor: default; }
+  .pk-status { margin: 14px 0 0; opacity: .9; }
+  .pk-status.is-err { color: #c0392b; }
+</style>
Index: src/views/pages/paid-result.ejs
===================================================================
--- src/views/pages/paid-result.ejs	(revision d43230fb28a7eb53abe716d29d94f498410f4edd)
+++ src/views/pages/paid-result.ejs	(revision d43230fb28a7eb53abe716d29d94f498410f4edd)
@@ -0,0 +1,30 @@
+<section class="pr">
+  <div class="pr-card">
+    <div class="pr-ic">🔒</div>
+    <% if (reason === 'notpatron') { %>
+      <h1 class="pr-h1">Nog geen supporter</h1>
+      <p class="pr-sub">Je bent (nog) geen actieve supporter van deze site op Patreon. Word supporter en probeer het opnieuw.</p>
+    <% } else if (reason === 'tier') { %>
+      <h1 class="pr-h1">Een niveau hoger nodig</h1>
+      <p class="pr-sub">
+        Deze post vraagt vanaf &euro;<%= ((typeof neededCents !== 'undefined' ? neededCents : 0)/100).toFixed(2) %>.
+        Jouw steun is nu &euro;<%= ((typeof haveCents !== 'undefined' ? haveCents : 0)/100).toFixed(2) %>. Verhoog je steun en probeer opnieuw.
+      </p>
+    <% } else { %>
+      <h1 class="pr-h1">Ontgrendelen afgebroken</h1>
+      <p class="pr-sub">Er is niets gekoppeld. Je kunt het opnieuw proberen vanaf de post.</p>
+    <% } %>
+    <% if (typeof postSlug !== 'undefined' && postSlug) { %>
+      <p><a class="pr-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase ? siteUrlBase : '') %>/<%= postSlug %>">Terug naar de post</a></p>
+    <% } %>
+  </div>
+</section>
+
+<style>
+  .pr { max-width: 460px; margin: 0 auto; padding: 56px 18px; text-align: center; }
+  .pr-card { border: 1px solid color-mix(in srgb, var(--ink,#000) 16%, transparent); border-radius: 18px; padding: 34px 26px; }
+  .pr-ic { font-size: 38px; margin-bottom: 6px; }
+  .pr-h1 { font-size: 23px; margin: 0 0 8px; }
+  .pr-sub { opacity: .85; line-height: 1.6; margin: 0 0 18px; }
+  .pr-btn { display: inline-block; padding: 11px 20px; border-radius: 10px; background: var(--accent,#6b8f71); color: #fff; text-decoration: none; font-weight: 600; }
+</style>
