Index: src/assets/css/style.css
===================================================================
--- src/assets/css/style.css	(revision 3a3a88292d6219f0e396972813875179f0cfba37)
+++ src/assets/css/style.css	(revision 8c91ed365b468e6dccaab02bf7098f1b30d6e31e)
@@ -2453,4 +2453,24 @@
 /* Een lijn tussen twee berichten: zonder grens is een stroom een lange brij. */
 .feed-reader .read-post + .read-post { border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent); }
+
+/* De cover vult de leeskolom. Eigen regel, niet .post-cover hergebruikt: die
+   heeft een marge van 2rem die hier tegen de scroll-snap aan duwt.
+   De hoogtebegrenzing is er met opzet. Een paneel is min-height:100svh en snapt,
+   dus een staande foto op volle breedte zou de titel en de eerste alinea onder
+   de vouw duwen -- dan begint elk bericht met een plaat waar je langs moet
+   scrollen om te zien wat het IS. */
+.feed-reader .read-cover {
+    margin: 0 0 1.2rem;
+    border-radius: 10px;
+    overflow: hidden;
+}
+.feed-reader .read-cover img,
+.feed-reader .read-cover video {
+    display: block;
+    width: 100%;
+    height: auto;
+    max-height: 45svh;
+    object-fit: cover;
+}
 
 .feed-reader .read-head { padding-bottom: .5rem; }
Index: src/views/partials/read-article.ejs
===================================================================
--- src/views/partials/read-article.ejs	(revision 3a3a88292d6219f0e396972813875179f0cfba37)
+++ src/views/partials/read-article.ejs	(revision 8c91ed365b468e6dccaab02bf7098f1b30d6e31e)
@@ -25,4 +25,37 @@
       Een <a> en niet alleen een tik-afhandelaar: dat werkt ook met een
       toetsenbord, met een schermlezer, en met cmd-klik. %>
+  <%# De cover hoort hier net zo goed als op de berichtpagina: het is het beeld
+      van het stuk, en zonder hem begint elk bericht in de stroom met kale tekst.
+      Boven de titel, in dezelfde volgorde als post.ejs.
+
+      Twee dingen komen hier van post-card en NIET van post.ejs:
+
+      - De SLUIER. Op de berichtpagina hangt de waarschuwing als banner boven de
+        hele pagina, want daar staat een bericht. In een stroom kan dat niet: de
+        buren rollen gewoon voorbij, dus het beeld moet zijn eigen sluier dragen
+        of het is onderweg al te zien. De afhandelaar zit gedelegeerd op
+        document (shell.ejs), dus hij werkt ook op wat de client later aanschuift.
+      - thumb(). De bron kan het volle bestand zijn en hier staan er meerdere
+        onder elkaar in een stroom die dooraanvult.
+
+      cover_image_url en cover_video_url komen mee: beide voedingen van
+      readerItems() halen `SELECT p.*` op (routes/posts.js, de pinned- en de
+      gewone feed-query). Nagekeken, want een versmalde lijst zou hier niets
+      opleveren zonder ergens te klagen. %>
+  <% if (post.cover_image_url || post.cover_video_url) { %>
+    <figure class="read-cover<%= post.nsfw ? ' nsfw-media' : '' %>">
+      <% if (post.cover_image_url) { %>
+        <img src="<%= thumb(post.cover_image_url, 1280) %>"
+             srcset="<%= thumb(post.cover_image_url, 640) %> 640w, <%= thumb(post.cover_image_url, 1280) %> 1280w"
+             sizes="(min-width: 768px) 46rem, 100vw"
+             alt="<%= post.cover_alt || '' %>" loading="lazy" decoding="async"<% if (post.cover_video_url) { %> data-ios-mp4="<%= post.cover_video_url %>"<% } %>>
+      <% } else { %>
+        <video src="<%= post.cover_video_url %>" poster="<%= thumb(post.cover_video_url, 1280) %>"
+               controls loop muted playsinline preload="metadata"></video>
+      <% } %>
+      <% if (post.nsfw) { %><span class="nsfw-veil"><%- include('nsfw-veil', { cw: post.content_warning }) %></span><% } %>
+    </figure>
+  <% } %>
+
   <header class="read-head">
     <h1 class="read-title"><a href="<%= _b %>/<%= post.slug %>"><%= post.title || '' %></a></h1>
