Index: src/assets/js/mod/download.js
===================================================================
--- src/assets/js/mod/download.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
+++ src/assets/js/mod/download.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -0,0 +1,11 @@
+// De downloadpagina -- verplaatst uit inline script, shaer-bqr.
+//
+// Inline script wordt door de CSP geweigerd zodra deze pagina via een link
+// BINNEN de site binnenkomt (shaer-0i6). Wat de server meegeeft komt uit
+// pageData(); interpolatie kan niet in een statisch bestand.
+
+import { pageData } from './lib.js';
+
+        // Auto-start de download (zelfde-origin attachment-link).
+        setTimeout(function(){ try { window.location.href = (pageData().fileUrl || ''); } catch(e){} }, 600);
+      
Index: src/assets/js/mod/news.js
===================================================================
--- src/assets/js/mod/news.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
+++ src/assets/js/mod/news.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -0,0 +1,129 @@
+// De Krant -- verplaatst uit inline script, shaer-bqr.
+//
+// Inline script wordt door de CSP geweigerd zodra deze pagina via een link
+// BINNEN de site binnenkomt (shaer-0i6). Wat de server meegeeft komt uit
+// pageData(); interpolatie kan niet in een statisch bestand.
+
+import { pageData } from './lib.js';
+
+  (function () {
+    if (window.__tlPasteWired) return; window.__tlPasteWired = true;
+    function modal() { return document.getElementById('tl-paste'); }
+    function openM() {
+      var m = modal(); if (!m) return;
+      var inp = m.querySelector('.tl-paste-input');
+      m.classList.add('is-open');
+      if (inp) {
+        inp.value = ''; inp.focus();
+        if (navigator.clipboard && navigator.clipboard.readText) {
+          navigator.clipboard.readText().then(function (t) {
+            t = (t || '').trim();
+            if (/^https?:\/\//i.test(t) && !inp.value) { inp.value = t; inp.select(); }
+          }).catch(function () {});
+        }
+      }
+    }
+    function closeM() { var m = modal(); if (m) m.classList.remove('is-open'); }
+    function go() {
+      var m = modal(); if (!m) return;
+      var inp = m.querySelector('.tl-paste-input'), v = (inp ? inp.value : '').trim();
+      if (!/^https?:\/\//i.test(v)) { if (inp) inp.focus(); return; }
+      location.href = '/authorize_interaction?uri=' + encodeURIComponent(v);
+    }
+    document.addEventListener('click', function (e) {
+      if (e.target.closest && e.target.closest('#tl-paste-btn')) { e.preventDefault(); openM(); return; }
+      if (e.target.closest && e.target.closest('.tl-paste-go')) { e.preventDefault(); go(); return; }
+      if (e.target.closest && (e.target.closest('.tl-paste-cancel') || e.target.closest('.tl-paste-x'))) { e.preventDefault(); closeM(); return; }
+      var op = document.querySelector('.tl-paste.is-open'); if (op && e.target === op) closeM();
+    });
+    document.addEventListener('keydown', function (e) {
+      var m = document.querySelector('.tl-paste.is-open'); if (!m) return;
+      if (e.key === 'Escape') closeM();
+      else if (e.key === 'Enter' && e.target.closest && e.target.closest('.tl-paste')) { e.preventDefault(); go(); }
+    });
+  })();
+  
+
+// ── volgend blok ──
+
+(function(){
+  if (window.__fediRemoteWired) return; window.__fediRemoteWired = true;
+  var current = null, currentBtn = null;
+  function close(){ if (current) { current.remove(); current = null; currentBtn = null; } }
+  function go(raw, uri){ var d=(raw||'').trim().replace(/^@?[^@\s]*@/,'').replace(/^https?:\/\//i,'').replace(/\/.*$/,'').trim(); if(d){ try{ localStorage.setItem('pcmsFediServer', d); }catch(e){} location.href='https://'+d+'/authorize_interaction?uri='+encodeURIComponent(uri||''); } }
+  function place(f, b){ var r=b.getBoundingClientRect(); f.style.top=(r.bottom+window.scrollY+6)+'px'; f.style.left=Math.max(8, Math.min(r.left+window.scrollX, window.scrollX+window.innerWidth-340))+'px'; }
+  document.addEventListener('click', function(e){
+    if (e.target.closest && e.target.closest('.fedi-remote-cancel')) { close(); return; }
+    if (current && e.target.closest && e.target.closest('.fedi-remote-form')) return;
+    var b = e.target.closest && e.target.closest('.fedi-remote-reply-btn');
+    if (b) {
+      e.preventDefault();
+      if (currentBtn === b) { close(); return; }
+      close();
+      var f = document.createElement('form'); f.className='fedi-remote-form'; f.dataset.uri = b.getAttribute('data-fedi-uri')||'';
+      f.innerHTML = '<input type="text" autocomplete="off" spellcheck="false"><button type="submit" class="btn btn-primary fedi-remote-go" aria-label="ok">&rarr;</button><button type="button" class="fedi-remote-cancel" aria-label="x">&times;</button>';
+      var _inp = f.querySelector('input'); _inp.placeholder = b.getAttribute('data-fedi-ph') || 'mastodon.social';
+      try{ var _sv = localStorage.getItem('pcmsFediServer'); if(_sv) _inp.value = _sv; }catch(e){}
+      document.body.appendChild(f); place(f, b); current=f; currentBtn=b; _inp.focus(); _inp.select();
+      return;
+    }
+    if (current) close();
+  });
+  document.addEventListener('submit', function(e){ var f=e.target.closest && e.target.closest('.fedi-remote-form'); if(!f) return; e.preventDefault(); go(f.querySelector('input').value, f.dataset.uri); });
+  document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
+  window.addEventListener('scroll', close, true);
+})();
+
+/* Short feed videos (gif/cover loops like an animated cover) autoplay + loop muted like a GIF;
+   longer real videos keep their controls. Decided on the actual duration once metadata loads. */
+(function(){
+  document.querySelectorAll('.tl-media-video').forEach(function(v){
+    if (v.dataset.gifWired) return; v.dataset.gifWired = '1';
+    var decide = function(){
+      if (v.duration && v.duration <= 30) {
+        v.removeAttribute('controls'); v.loop = true; v.muted = true; v.play().catch(function(){});
+      }
+    };
+    if (v.readyState >= 1) decide(); else v.addEventListener('loadedmetadata', decide, { once: true });
+  });
+})();
+
+/* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page (no reload, no banner). */
+(function(){
+  if (window.__newsReactWired) return; window.__newsReactWired = true;
+  document.addEventListener('submit', function(e){
+    var f = e.target.closest && e.target.closest('.tl-react-form');
+    if (!f) return;
+    e.preventDefault();
+    var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
+    btn.disabled = true;
+    var body = new URLSearchParams();
+    new FormData(f).forEach(function(v, k){ body.append(k, v); });
+    fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
+      .then(function(r){ return r.ok ? r.json() : null; })
+      .then(function(j){ if (j) btn.classList.toggle('is-on', !!j.on); })
+      .catch(function(){})
+      .then(function(){ btn.disabled = false; });
+  });
+})();
+
+// ── volgend blok ──
+
+// Collapse long post bodies to a max height with a "read more" toggle — only when the content
+// actually overflows. Runs on every /news render (full load + htmx swap); a per-element flag
+// prevents double-wiring.
+(function(){
+  var _d = pageData(); var RM = _d.readMore || 'Lees meer', SL = _d.showLess || 'Toon minder';
+  document.querySelectorAll('.tl-content:not(.nsfw-media):not([data-rm])').forEach(function(c){
+    c.setAttribute('data-rm', '1');
+    if (c.scrollHeight > 360) {
+      c.classList.add('tl-clamp');
+      var b = document.createElement('button');
+      b.type = 'button'; b.className = 'tl-readmore'; b.textContent = RM;
+      b.addEventListener('click', function(){
+        b.textContent = c.classList.toggle('tl-clamp') ? RM : SL;
+      });
+      c.insertAdjacentElement('afterend', b);
+    }
+  });
+})();
Index: src/routes/download.js
===================================================================
--- src/routes/download.js	(revision 54ee51c15ca8fabdc52a492b077cbd642c57fc87)
+++ src/routes/download.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -85,4 +85,5 @@
   const fan = req.session && req.session.user;
   renderPage(req, res, 'pages/download', {
+    pageJs: 'download',
     pageTitle: track.title + ' — download',
     bodyClass: 'on-download',
Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 54ee51c15ca8fabdc52a492b077cbd642c57fc87)
+++ src/routes/posts.js	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -1194,4 +1194,5 @@
   }
   renderPage(req, res, 'pages/news', {
+    pageJs: 'news',
     pageTitle: 'News', bodyClass: 'on-special',
     timeline, hasMore, nextOffset: offset + FEED_PAGE, moreBase,
Index: src/views/pages/download.ejs
===================================================================
--- src/views/pages/download.ejs	(revision 54ee51c15ca8fabdc52a492b077cbd642c57fc87)
+++ src/views/pages/download.ejs	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -26,8 +26,5 @@
       <p class="dl-sub"><%= _ready_sub %></p>
       <p><a class="dl-go" href="<%= fileUrl %>"><%= _manual %></a></p>
-      <script>
-        // Auto-start de download (zelfde-origin attachment-link).
-        setTimeout(function(){ try { window.location.href = <%- JSON.stringify(fileUrl) %>; } catch(e){} }, 600);
-      </script>
+      <%# Het script staat in assets/js/mod/download.js; de servergegevens gaan via partials/page-data.ejs (shaer-bqr). %>
     <% } else { %>
       <h1 class="dl-h1"><%= _download_btn %> <%= tr.title %></h1>
@@ -57,2 +54,3 @@
   .dl-go { padding: 12px 20px; border-radius: 10px; border: none; background: var(--accent,#6b8f71); color: #fff; font-weight: 600; font-size: 15px; cursor: pointer; text-decoration: none; display: inline-block; }
 </style>
+<%- include('../partials/page-data', { pageData: { fileUrl: fileUrl } }) %>
Index: src/views/pages/news.ejs
===================================================================
--- src/views/pages/news.ejs	(revision 54ee51c15ca8fabdc52a492b077cbd642c57fc87)
+++ src/views/pages/news.ejs	(revision 52fc278d3043952817f815db997e8fad5ed17b3b)
@@ -41,42 +41,5 @@
     .tl-paste-go { background: var(--accent); color: var(--paper); }
   </style>
-  <script>
-  (function () {
-    if (window.__tlPasteWired) return; window.__tlPasteWired = true;
-    function modal() { return document.getElementById('tl-paste'); }
-    function openM() {
-      var m = modal(); if (!m) return;
-      var inp = m.querySelector('.tl-paste-input');
-      m.classList.add('is-open');
-      if (inp) {
-        inp.value = ''; inp.focus();
-        if (navigator.clipboard && navigator.clipboard.readText) {
-          navigator.clipboard.readText().then(function (t) {
-            t = (t || '').trim();
-            if (/^https?:\/\//i.test(t) && !inp.value) { inp.value = t; inp.select(); }
-          }).catch(function () {});
-        }
-      }
-    }
-    function closeM() { var m = modal(); if (m) m.classList.remove('is-open'); }
-    function go() {
-      var m = modal(); if (!m) return;
-      var inp = m.querySelector('.tl-paste-input'), v = (inp ? inp.value : '').trim();
-      if (!/^https?:\/\//i.test(v)) { if (inp) inp.focus(); return; }
-      location.href = '/authorize_interaction?uri=' + encodeURIComponent(v);
-    }
-    document.addEventListener('click', function (e) {
-      if (e.target.closest && e.target.closest('#tl-paste-btn')) { e.preventDefault(); openM(); return; }
-      if (e.target.closest && e.target.closest('.tl-paste-go')) { e.preventDefault(); go(); return; }
-      if (e.target.closest && (e.target.closest('.tl-paste-cancel') || e.target.closest('.tl-paste-x'))) { e.preventDefault(); closeM(); return; }
-      var op = document.querySelector('.tl-paste.is-open'); if (op && e.target === op) closeM();
-    });
-    document.addEventListener('keydown', function (e) {
-      var m = document.querySelector('.tl-paste.is-open'); if (!m) return;
-      if (e.key === 'Escape') closeM();
-      else if (e.key === 'Enter' && e.target.closest && e.target.closest('.tl-paste')) { e.preventDefault(); go(); }
-    });
-  })();
-  </script>
+  <%# Het script staat in assets/js/mod/news.js; de servergegevens gaan via partials/page-data.ejs (shaer-bqr). %>
   <% if (typeof success !== 'undefined' && success) { %><div class="alert alert-success"><%= success %></div><% } %>
   <% if (typeof error !== 'undefined' && error) { %><div class="alert alert-error"><%= error %></div><% } %>
@@ -184,86 +147,3 @@
   .tl-act-block:hover { background: color-mix(in srgb, #d9534f 14%, transparent); border-color: color-mix(in srgb, #d9534f 50%, transparent); }
 </style>
-
-<script>
-(function(){
-  if (window.__fediRemoteWired) return; window.__fediRemoteWired = true;
-  var current = null, currentBtn = null;
-  function close(){ if (current) { current.remove(); current = null; currentBtn = null; } }
-  function go(raw, uri){ var d=(raw||'').trim().replace(/^@?[^@\s]*@/,'').replace(/^https?:\/\//i,'').replace(/\/.*$/,'').trim(); if(d){ try{ localStorage.setItem('pcmsFediServer', d); }catch(e){} location.href='https://'+d+'/authorize_interaction?uri='+encodeURIComponent(uri||''); } }
-  function place(f, b){ var r=b.getBoundingClientRect(); f.style.top=(r.bottom+window.scrollY+6)+'px'; f.style.left=Math.max(8, Math.min(r.left+window.scrollX, window.scrollX+window.innerWidth-340))+'px'; }
-  document.addEventListener('click', function(e){
-    if (e.target.closest && e.target.closest('.fedi-remote-cancel')) { close(); return; }
-    if (current && e.target.closest && e.target.closest('.fedi-remote-form')) return;
-    var b = e.target.closest && e.target.closest('.fedi-remote-reply-btn');
-    if (b) {
-      e.preventDefault();
-      if (currentBtn === b) { close(); return; }
-      close();
-      var f = document.createElement('form'); f.className='fedi-remote-form'; f.dataset.uri = b.getAttribute('data-fedi-uri')||'';
-      f.innerHTML = '<input type="text" autocomplete="off" spellcheck="false"><button type="submit" class="btn btn-primary fedi-remote-go" aria-label="ok">&rarr;</button><button type="button" class="fedi-remote-cancel" aria-label="x">&times;</button>';
-      var _inp = f.querySelector('input'); _inp.placeholder = b.getAttribute('data-fedi-ph') || 'mastodon.social';
-      try{ var _sv = localStorage.getItem('pcmsFediServer'); if(_sv) _inp.value = _sv; }catch(e){}
-      document.body.appendChild(f); place(f, b); current=f; currentBtn=b; _inp.focus(); _inp.select();
-      return;
-    }
-    if (current) close();
-  });
-  document.addEventListener('submit', function(e){ var f=e.target.closest && e.target.closest('.fedi-remote-form'); if(!f) return; e.preventDefault(); go(f.querySelector('input').value, f.dataset.uri); });
-  document.addEventListener('keydown', function(e){ if (e.key === 'Escape') close(); });
-  window.addEventListener('scroll', close, true);
-})();
-
-/* Short feed videos (gif/cover loops like an animated cover) autoplay + loop muted like a GIF;
-   longer real videos keep their controls. Decided on the actual duration once metadata loads. */
-(function(){
-  document.querySelectorAll('.tl-media-video').forEach(function(v){
-    if (v.dataset.gifWired) return; v.dataset.gifWired = '1';
-    var decide = function(){
-      if (v.duration && v.duration <= 30) {
-        v.removeAttribute('controls'); v.loop = true; v.muted = true; v.play().catch(function(){});
-      }
-    };
-    if (v.readyState >= 1) decide(); else v.addEventListener('loadedmetadata', decide, { once: true });
-  });
-})();
-
-/* Like/Boost toggle in place — POST via fetch, flip the button, stay on the page (no reload, no banner). */
-(function(){
-  if (window.__newsReactWired) return; window.__newsReactWired = true;
-  document.addEventListener('submit', function(e){
-    var f = e.target.closest && e.target.closest('.tl-react-form');
-    if (!f) return;
-    e.preventDefault();
-    var btn = f.querySelector('button'); if (!btn || btn.disabled) return;
-    btn.disabled = true;
-    var body = new URLSearchParams();
-    new FormData(f).forEach(function(v, k){ body.append(k, v); });
-    fetch(f.action, { method: 'POST', body: body, headers: { 'X-Requested-With': 'fetch' }, credentials: 'same-origin' })
-      .then(function(r){ return r.ok ? r.json() : null; })
-      .then(function(j){ if (j) btn.classList.toggle('is-on', !!j.on); })
-      .catch(function(){})
-      .then(function(){ btn.disabled = false; });
-  });
-})();
-</script>
-
-<script>
-// Collapse long post bodies to a max height with a "read more" toggle — only when the content
-// actually overflows. Runs on every /news render (full load + htmx swap); a per-element flag
-// prevents double-wiring.
-(function(){
-  var RM = '<%= t("tl.read_more") %>', SL = '<%= t("tl.show_less") %>';
-  document.querySelectorAll('.tl-content:not(.nsfw-media):not([data-rm])').forEach(function(c){
-    c.setAttribute('data-rm', '1');
-    if (c.scrollHeight > 360) {
-      c.classList.add('tl-clamp');
-      var b = document.createElement('button');
-      b.type = 'button'; b.className = 'tl-readmore'; b.textContent = RM;
-      b.addEventListener('click', function(){
-        b.textContent = c.classList.toggle('tl-clamp') ? RM : SL;
-      });
-      c.insertAdjacentElement('afterend', b);
-    }
-  });
-})();
-</script>
+<%- include('../partials/page-data', { pageData: { readMore: t('tl.read_more'), showLess: t('tl.show_less') } }) %>
