Index: src/views/partials/bottom-tab.ejs
===================================================================
--- src/views/partials/bottom-tab.ejs	(revision ea138c0c6a4bc5a508027368c44de1a8cdb6a326)
+++ src/views/partials/bottom-tab.ejs	(revision 4c1e327b9d8a84ba2ac7075f7748a6b4a0a171e8)
@@ -302,17 +302,5 @@
 </style>
 
-<script>
-// Bottom-tab search button → opens #search-overlay. Using event delegation + looking
-// up the overlay on EVERY click: the overlay lives in the chrome that is OOB-replaced
-// during htmx navigation, so a once-captured reference goes stale
-// (button did nothing afterwards). Guard against double-wiring.
-(function() {
-  if (window.__btSearchWired) return;
-  window.__btSearchWired = true;
-  document.addEventListener('click', function(e) {
-    if (!e.target.closest || !e.target.closest('#bottom-tab-search-toggle')) return;
-    var o = document.getElementById('search-overlay');
-    if (o) { o.hidden = false; var i = o.querySelector('input'); if (i) i.focus(); }
-  });
-})();
-</script>
+<%# De zoekknop zit nu in de chrome-module (assets/js/mod/chrome.js).
+    Inline script hier wordt door de CSP geweigerd zodra deze partial via htmx
+    binnenkomt, en dat is bij elke navigatie -- zie shaer-0i6. %>
Index: src/views/shell.ejs
===================================================================
--- src/views/shell.ejs	(revision ea138c0c6a4bc5a508027368c44de1a8cdb6a326)
+++ src/views/shell.ejs	(revision 4c1e327b9d8a84ba2ac7075f7748a6b4a0a171e8)
@@ -339,5 +339,5 @@
 </head>
 
-<body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-feed-view="<%- _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
+<body class="<%= bodyClass || 'on-home' %> has-bottom-tab" data-js="chrome<%= (typeof pageJs !== 'undefined' && pageJs) ? ' ' + pageJs : '' %>" data-feed-view="<%- _e(safeSite.feed_view_default || 'timeline') %>" data-grid-cols="3" data-site-base="<%- _e((typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '') %>">
 
 <% if (typeof isViewer !== 'undefined' && isViewer) { %>
@@ -449,4 +449,10 @@
     }
 
+    // Welke modules deze pagina wil (shaer-bqr). De bootstrap hieronder leest
+    // dit zodra deze handler klaar is.
+    if (typeof d.js === 'string' && /^[a-z0-9 -]*$/.test(d.js)) {
+      document.body.setAttribute('data-js', d.js);
+    }
+
     var next = d.bodyClass;
     if (!next) return;
@@ -467,4 +473,41 @@
   //  de pcmsNav-trigger (accent/palette/bodyClass) correct toepast. De vroegere
   //  htmx:historyRestore-handler is vervallen nu htmx-history uitstaat.)
+})();
+</script>
+
+<!-- Modules laden (shaer-bqr). Inline script in gewisselde inhoud wordt door de
+     CSP geweigerd: de nonce rouleert per verzoek, dus een script dat via htmx
+     binnenkomt draagt er een die dit document niet kent (shaer-0i6). Alles wat
+     bij een pagina hoort komt daarom uit een module, en die wordt HIER geladen —
+     vanuit de shell, die alleen bij een volledige laadbeurt binnenkomt en dus
+     wél de goede nonce heeft.
+
+     Een dynamische import vanuit een vertrouwd (genonced) script is precies waar
+     'strict-dynamic' voor bedoeld is, dus de module zelf heeft geen nonce nodig. -->
+<script>
+(function () {
+  if (window.__modBoot) return;
+  window.__modBoot = true;
+
+  var loaded = {};
+  function load() {
+    var names = (document.body.getAttribute('data-js') || '').trim().split(/\s+/);
+    names.forEach(function (name) {
+      // Streng: deze waarde komt uit een template en wordt een PAD. Alleen
+      // kleine letters, cijfers en streepjes; nooit een punt of een schuine
+      // streep.
+      if (!name || loaded[name] || !/^[a-z0-9-]+$/.test(name)) return;
+      loaded[name] = 1;
+      import('/assets/js/mod/' + name + '.js').catch(function (e) {
+        console.warn('[mod] ' + name + ' laadde niet:', e && e.message);
+      });
+    });
+  }
+  load();
+  // Bij een htmx-navigatie wisselt de INHOUD, niet de body. De nav-trigger
+  // hieronder zet data-js opnieuw; daarna halen we op wat er nieuw bij staat.
+  // Een module die er al is wordt niet opnieuw geimporteerd -- vandaar dat elke
+  // module gedelegeerd moet werken en tegen een tweede aanroep moet kunnen.
+  document.body.addEventListener('pcmsNav', load);
 })();
 </script>
