Index: src/views/pages/messages.ejs
===================================================================
--- src/views/pages/messages.ejs	(revision 3ca7bdf670788b368fa71765ae6b93f739f56b8f)
+++ src/views/pages/messages.ejs	(revision 2bd31d67a4567197583d1b78c6097c9c4b4558bf)
@@ -162,4 +162,20 @@
   .msg-wave button { line-height: 1.2; }
 
+  /* Zwaaien gebeurt ter plekke (mod/messages.js). Twee toestanden na de klik:
+     gelukt, dan vervangt de bevestiging de knoppen -- en mislukt, dan komen de
+     knoppen terug met een schud, zodat een tweede poging voor de hand ligt.
+     De bestaande klasse `nudge` kon hier niet voor dienen: die heeft alleen
+     CSS op authorize-interaction, dus op deze pagina zou hij niets doen. */
+  .msg-wave-done { font-size: .82rem; color: var(--ink-soft, #888); white-space: nowrap; }
+  .msg-wave.is-failed, .msg-quickreply.is-failed { animation: msg-wave-shake .4s ease; }
+  @keyframes msg-wave-shake {
+    0%, 100% { transform: translateX(0); }
+    25% { transform: translateX(-4px); }
+    75% { transform: translateX(4px); }
+  }
+  @media (prefers-reduced-motion: reduce) {
+    .msg-wave.is-failed, .msg-quickreply.is-failed { animation: none; outline: 2px solid var(--accent); }
+  }
+
   @media (max-width: 560px) {
     /* Op een telefoon is 40px avatar per bubbel puur verlies: de naam staat er
Index: src/views/partials/msg-item.ejs
===================================================================
--- src/views/partials/msg-item.ejs	(revision 3ca7bdf670788b368fa71765ae6b93f739f56b8f)
+++ src/views/partials/msg-item.ejs	(revision 2bd31d67a4567197583d1b78c6097c9c4b4558bf)
@@ -70,5 +70,5 @@
                   seintje (FEP-633c), en die twee horen niet op een hoop. %>
               <% if (_rt.actorUri) { %>
-                <form method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" class="msg-wave">
+                <form method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" class="msg-wave" data-sent="<%= t('msg.wave_sent') %>">
                   <input type="hidden" name="to" value="<%= _rt.actorUri %>">
                   <button class="btn small" name="text" value="&#128075;" title="<%= t('msg.wave_back') %>">&#128075;</button>
@@ -167,5 +167,5 @@
             <% } else if ((_t === 'reply' || _t === 'mention' || _t === 'sent') && n.content) { %><div class="msg-content msg-note"><%- include('../partials/note-body', { nb: n }) %></div>
               <% if (_t === 'mention' && n.wave && n.actorUri && canMutate) { %>
-                <form class="msg-quickreply" method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">
+                <form class="msg-quickreply" method="post" action="<%= (typeof siteUrlBase !== 'undefined' ? siteUrlBase : '') %>/messages/quick-reply" data-sent="<%= t('msg.wave_sent') %>" style="display:flex;gap:6px;flex-wrap:wrap;margin-top:6px">
                   <input type="hidden" name="to" value="<%= n.actorUri %>">
                   <button class="btn small" name="text" value="<%= t('msg.wave_r1') %>"><%= t('msg.wave_r1') %></button>
