Index: src/assets/css/style.css
===================================================================
--- src/assets/css/style.css	(revision 2165b6d3d57ceaa8b5d1f70491868482149470cf)
+++ src/assets/css/style.css	(revision 7bc636b391c66ac399c33e54f7173a022c6a3cbd)
@@ -54,5 +54,5 @@
 }
 
-/* Klonkt publieke stijl — editorial-ish, geen emoji-UI */
+/* PrutCMS publieke stijl — editorial-ish, geen emoji-UI */
 
 :root {
@@ -63,23 +63,6 @@
     --radius: 8px; --transition: 180ms cubic-bezier(.4,0,.2,1);
     --safe-top: env(safe-area-inset-top, 0); --safe-bot: env(safe-area-inset-bottom, 0);
-    /* iOS top inset: starts from env(), but a tiny boot script overrides it with a
-       real/measured value (env() resolves to 0 in some iOS standalone states). */
-    --ios-safe-top: env(safe-area-inset-top, 0px);
     color-scheme: light dark;
 }
-
-/* Native controls (datum/tijd-picker, scrollbars) het SITE-thema laten volgen i.p.v.
-   de OS-voorkeur, en de datum/tijd-velden de site-accentkleur geven. Zo staat de
-   ingebouwde kalender-popup in dark + accent (de popup zelf is een browser-widget,
-   dus fonts/randen blijven van de browser). */
-[data-theme="dark"] { color-scheme: dark; }
-[data-theme="light"] { color-scheme: light; }
-input[type="date"], input[type="time"], input[type="datetime-local"] { accent-color: var(--accent, #6b8f71); }
-input[type="date"]::-webkit-calendar-picker-indicator,
-input[type="time"]::-webkit-calendar-picker-indicator,
-input[type="datetime-local"]::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .7; }
-input[type="date"]:hover::-webkit-calendar-picker-indicator,
-input[type="time"]:hover::-webkit-calendar-picker-indicator,
-input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator { opacity: 1; }
 
 /* ============================================================
@@ -112,23 +95,4 @@
 }
 
-/* ── KLONKT ── merk-standaard: clean wit → bijna-zwart (neutraal, gouden accent) ── */
-[data-palette="klonkt"] {
-    --paper: #ffffff; --paper-2: #f4f4f5;
-    --ink: #09090b; --ink-soft: #27272a; --ink-muted: #52525b; --ink-faint: #a1a1aa;
-    --rule: #e4e4e7; --rule-2: #d4d4d8;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="klonkt"]:not([data-theme="light"]) {
-        --paper: #0a0a0a; --paper-2: #18181b;
-        --ink: #fafafa; --ink-soft: #d4d4d8; --ink-muted: #a1a1aa; --ink-faint: #71717a;
-        --rule: #27272a; --rule-2: #3f3f46;
-    }
-}
-[data-theme="dark"][data-palette="klonkt"] {
-    --paper: #0a0a0a; --paper-2: #18181b;
-    --ink: #fafafa; --ink-soft: #d4d4d8; --ink-muted: #a1a1aa; --ink-faint: #71717a;
-    --rule: #27272a; --rule-2: #3f3f46;
-}
-
 /* ── PAPER ── minimalistisch wit → zwart ── */
 [data-palette="paper"] {
@@ -150,4 +114,22 @@
 }
 
+/* ── OCEAN ── koel blauw-grijs → diep navy ── */
+[data-palette="ocean"] {
+    --paper: #f0f6fb; --paper-2: #dce9f4;
+    --ink: #0f1e2e; --ink-soft: #1e3a52; --ink-muted: #475569; --ink-faint: #94a3b8;
+    --rule: #c7d9ea; --rule-2: #a8c0d6;
+}
+@media (prefers-color-scheme: dark) {
+    [data-palette="ocean"]:not([data-theme="light"]) {
+        --paper: #0a1929; --paper-2: #152a3f;
+        --ink: #e0f2fe; --ink-soft: #bfdbfe; --ink-muted: #93c5fd; --ink-faint: #64748b;
+        --rule: #1e3a52; --rule-2: #2a4a68;
+    }
+}
+[data-theme="dark"][data-palette="ocean"] {
+    --paper: #0a1929; --paper-2: #152a3f;
+    --ink: #e0f2fe; --ink-soft: #bfdbfe; --ink-muted: #93c5fd; --ink-faint: #64748b;
+    --rule: #1e3a52; --rule-2: #2a4a68;
+}
 
 /* ── FOREST ── olijfgroen → donker bos ── */
@@ -227,155 +209,21 @@
 }
 
-/* ── SAND ── warm papier (subtiel, niet fel geel) → warm donker ── (key blijft 'cream') */
+/* ── CREAM ── warm papier → caramel-donker ── */
 [data-palette="cream"] {
-    --paper: #f7f3ec; --paper-2: #ece5d8;
-    --ink: #232019; --ink-soft: #43392c; --ink-muted: #6f6149; --ink-faint: #a3957c;
-    --rule: #ddd3c0; --rule-2: #c9bca3;
+    --paper: #fefaf0; --paper-2: #f5ebd5;
+    --ink: #2a1f0f; --ink-soft: #4a3820; --ink-muted: #786140; --ink-faint: #ad9068;
+    --rule: #e8d9b0; --rule-2: #d1bc8a;
 }
 @media (prefers-color-scheme: dark) {
     [data-palette="cream"]:not([data-theme="light"]) {
-        --paper: #14120d; --paper-2: #221e15;
-        --ink: #efe9dd; --ink-soft: #d0c6b2; --ink-muted: #a3957c; --ink-faint: #6f6149;
-        --rule: #322c20; --rule-2: #463d2c;
+        --paper: #1a1208; --paper-2: #2e2012;
+        --ink: #fef3d6; --ink-soft: #e0cfa0; --ink-muted: #b0996b; --ink-faint: #7a6848;
+        --rule: #3d2c16; --rule-2: #523d1f;
     }
 }
 [data-theme="dark"][data-palette="cream"] {
-    --paper: #14120d; --paper-2: #221e15;
-    --ink: #efe9dd; --ink-soft: #d0c6b2; --ink-muted: #a3957c; --ink-faint: #6f6149;
-    --rule: #322c20; --rule-2: #463d2c;
-}
-
-/* ── OCEAN ── helder blauw → diep oceaanblauw ── */
-[data-palette="ocean"] {
-    --paper: #eef4fb; --paper-2: #d8e6f7;
-    --ink: #0f2942; --ink-soft: #1d4368; --ink-muted: #4a6e90; --ink-faint: #87a3c0;
-    --rule: #c2d8ee; --rule-2: #a3c2e0;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="ocean"]:not([data-theme="light"]) {
-        --paper: #081726; --paper-2: #102a42;
-        --ink: #d6e8fb; --ink-soft: #aecbe8; --ink-muted: #7e9fc0; --ink-faint: #547390;
-        --rule: #1d3a56; --rule-2: #2a4d6e;
-    }
-}
-[data-theme="dark"][data-palette="ocean"] {
-    --paper: #081726; --paper-2: #102a42;
-    --ink: #d6e8fb; --ink-soft: #aecbe8; --ink-muted: #7e9fc0; --ink-faint: #547390;
-    --rule: #1d3a56; --rule-2: #2a4d6e;
-}
-
-/* ── TEAL ── mint-turquoise → diep teal ── */
-[data-palette="teal"] {
-    --paper: #ecf7f5; --paper-2: #cfe9e4;
-    --ink: #0c2e2a; --ink-soft: #194a44; --ink-muted: #467168; --ink-faint: #84aaa2;
-    --rule: #bce0d9; --rule-2: #9bcbc1;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="teal"]:not([data-theme="light"]) {
-        --paper: #06201d; --paper-2: #0e332e;
-        --ink: #d4f2ec; --ink-soft: #aad9d0; --ink-muted: #7aaaa0; --ink-faint: #517a72;
-        --rule: #1a4a42; --rule-2: #275e54;
-    }
-}
-[data-theme="dark"][data-palette="teal"] {
-    --paper: #06201d; --paper-2: #0e332e;
-    --ink: #d4f2ec; --ink-soft: #aad9d0; --ink-muted: #7aaaa0; --ink-faint: #517a72;
-    --rule: #1a4a42; --rule-2: #275e54;
-}
-
-/* ── CANDY ── zacht roze-rood → diep kersenrood ── */
-[data-palette="candy"] {
-    --paper: #fdf1f3; --paper-2: #f8d8de;
-    --ink: #3a1018; --ink-soft: #5e1f2a; --ink-muted: #8e4450; --ink-faint: #c2868f;
-    --rule: #f0c5cd; --rule-2: #e3a0ab;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="candy"]:not([data-theme="light"]) {
-        --paper: #220810; --paper-2: #3a121d;
-        --ink: #fde0e6; --ink-soft: #f5b8c4; --ink-muted: #cd8b98; --ink-faint: #8e5f6a;
-        --rule: #4d1a28; --rule-2: #632536;
-    }
-}
-[data-theme="dark"][data-palette="candy"] {
-    --paper: #220810; --paper-2: #3a121d;
-    --ink: #fde0e6; --ink-soft: #f5b8c4; --ink-muted: #cd8b98; --ink-faint: #8e5f6a;
-    --rule: #4d1a28; --rule-2: #632536;
-}
-
-/* ── AMBER ── warm honing → diep amber ── */
-[data-palette="amber"] {
-    --paper: #fdf6e9; --paper-2: #f7e6c2;
-    --ink: #3a2a0c; --ink-soft: #5e451a; --ink-muted: #8e7340; --ink-faint: #c2a877;
-    --rule: #f0dca8; --rule-2: #e3c581;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="amber"]:not([data-theme="light"]) {
-        --paper: #221a08; --paper-2: #3a2c12;
-        --ink: #fdeecb; --ink-soft: #f0d49a; --ink-muted: #c2a877; --ink-faint: #8e7340;
-        --rule: #4d3a16; --rule-2: #634d20;
-    }
-}
-[data-theme="dark"][data-palette="amber"] {
-    --paper: #221a08; --paper-2: #3a2c12;
-    --ink: #fdeecb; --ink-soft: #f0d49a; --ink-muted: #c2a877; --ink-faint: #8e7340;
-    --rule: #4d3a16; --rule-2: #634d20;
-}
-
-/* ── ROSE ── zacht roze → wijnrood ── */
-[data-palette="rose"] {
-    --paper: #fdf2f4; --paper-2: #f8dde3;
-    --ink: #2e1419; --ink-soft: #4d242c; --ink-muted: #7a4651; --ink-faint: #b3838d;
-    --rule: #f0c9d1; --rule-2: #e0a3af;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="rose"]:not([data-theme="light"]) {
-        --paper: #1f0a0f; --paper-2: #33141c;
-        --ink: #fce4ea; --ink-soft: #f0bcc8; --ink-muted: #c98f9d; --ink-faint: #8a6470;
-        --rule: #4a1f2c; --rule-2: #5e2d3a;
-    }
-}
-[data-theme="dark"][data-palette="rose"] {
-    --paper: #1f0a0f; --paper-2: #33141c;
-    --ink: #fce4ea; --ink-soft: #f0bcc8; --ink-muted: #c98f9d; --ink-faint: #8a6470;
-    --rule: #4a1f2c; --rule-2: #5e2d3a;
-}
-
-
-/* ── TERRACOTTA ── warme klei → diep brick ── (key blijft 'mint' voor DB-compat) */
-[data-palette="mint"] {
-    --paper: #faf2ee; --paper-2: #f2ddd0;
-    --ink: #2e1a12; --ink-soft: #4a2c1e; --ink-muted: #7a5544; --ink-faint: #b3917e;
-    --rule: #eccdbb; --rule-2: #ddb49d;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="mint"]:not([data-theme="light"]) {
-        --paper: #1f120c; --paper-2: #2e1c13;
-        --ink: #f7e6da; --ink-soft: #e0c3b0; --ink-muted: #b3917c; --ink-faint: #7a5544;
-        --rule: #4a2c1e; --rule-2: #5e3a28;
-    }
-}
-[data-theme="dark"][data-palette="mint"] {
-    --paper: #1f120c; --paper-2: #2e1c13;
-    --ink: #f7e6da; --ink-soft: #e0c3b0; --ink-muted: #b3917c; --ink-faint: #7a5544;
-    --rule: #4a2c1e; --rule-2: #5e3a28;
-}
-
-/* ── LILAC ── zacht lila → donker violet ── */
-[data-palette="lilac"] {
-    --paper: #faf4fb; --paper-2: #f0e0f3;
-    --ink: #2a1830; --ink-soft: #46294f; --ink-muted: #7a5685; --ink-faint: #b393bd;
-    --rule: #e6cdec; --rule-2: #d4abdd;
-}
-@media (prefers-color-scheme: dark) {
-    [data-palette="lilac"]:not([data-theme="light"]) {
-        --paper: #170a1c; --paper-2: #281433;
-        --ink: #f3e2f7; --ink-soft: #dabce4; --ink-muted: #b088c0; --ink-faint: #7a5a88;
-        --rule: #3d1f4a; --rule-2: #4f2b5e;
-    }
-}
-[data-theme="dark"][data-palette="lilac"] {
-    --paper: #170a1c; --paper-2: #281433;
-    --ink: #f3e2f7; --ink-soft: #dabce4; --ink-muted: #b088c0; --ink-faint: #7a5a88;
-    --rule: #3d1f4a; --rule-2: #4f2b5e;
+    --paper: #1a1208; --paper-2: #2e2012;
+    --ink: #fef3d6; --ink-soft: #e0cfa0; --ink-muted: #b0996b; --ink-faint: #7a6848;
+    --rule: #3d2c16; --rule-2: #523d1f;
 }
 
@@ -385,41 +233,9 @@
 
 * { box-sizing: border-box; }
-html {
-    -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent;
-    overscroll-behavior-y: none;
-    /* Horizontaal clippen op ROOT-niveau: dit propageert naar de viewport (geen
-       horizontale scroll) ZONDER position:fixed te breken — html is sowieso de
-       document-scroller. De body mag GEEN overflow-x krijgen, want dan zou die een
-       tweede scroll-container worden en zou de fixed bottom-tab meeschuiven. */
-    overflow-x: hidden;
-}
+html { -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; overscroll-behavior-y: none; }
 body {
     margin: 0;
-}
-
-/* ── Thema-scrollbar: slank, afgerond, subtiel — vervangt de standaard chunky
-   browser-scrollbar (met pijltjes) die niet bij het donkere thema past.
-   Werkt op de pagina én op interne scrollers (queue, chat-thread, dropdowns). ── */
-* {
-    scrollbar-width: thin;
-    scrollbar-color: color-mix(in srgb, var(--ink) 28%, transparent) transparent;
-}
-::-webkit-scrollbar { width: 10px; height: 10px; }
-::-webkit-scrollbar-track { background: transparent; }
-::-webkit-scrollbar-thumb {
-    background-color: color-mix(in srgb, var(--ink) 24%, transparent);
-    border-radius: 999px;
-    border: 2px solid transparent;   /* transparante rand → optisch slanker (≈6px) */
-    background-clip: content-box;
-}
-::-webkit-scrollbar-thumb:hover {
-    background-color: color-mix(in srgb, var(--accent) 60%, var(--ink));
-}
-::-webkit-scrollbar-corner { background: transparent; }
-
-/* Login/auth = focus-scherm. Bij htmx-navigatie naar login blijft de bottom-tab
-   uit de vorige pagina in de DOM staan → hier verbergen zodat het login-scherm
-   net zo "kaal" is als bij een full-load (waar de tab niet eens gerenderd wordt). */
-body.on-auth .bottom-tab { display: none; }
+    overflow-x: hidden;
+}
 body:not(.admin) {
     min-height: 100dvh;
@@ -436,13 +252,7 @@
 body:not(.admin)::before {
     content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
-    /* Per-palette achtergrond-gradient: opgebouwd uit de palette's EIGEN --paper/--paper-2
-       (diagonale verloop) + een zachte accent-glow erbovenop. Werkt automatisch voor elk
-       palet, light én dark — geen per-palette regels nodig. */
-    background: radial-gradient(ellipse at top, color-mix(in srgb, var(--accent) 6%, transparent), transparent 60%),
-                radial-gradient(ellipse at bottom right, color-mix(in srgb, var(--accent) 7%, transparent), transparent 55%),
-                linear-gradient(160deg, var(--paper) 0%, var(--paper-2) 100%);
-}
-/* Klonkt-palet gebruikt nu (net als de andere paletten) de generieke accent-glow
-   hierboven → de achtergrond-gradient volgt de gekozen accentkleur. */
+    background: radial-gradient(ellipse at top, color-mix(in srgb, var(--accent) 3%, transparent), transparent 60%),
+                radial-gradient(ellipse at bottom right, color-mix(in srgb, var(--accent) 4%, transparent), transparent 55%);
+}
 ::selection { background: var(--accent-tint); color: var(--accent); }
 
@@ -454,10 +264,4 @@
 a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--rule-2); text-underline-offset: 3px; transition: color var(--transition), text-decoration-color var(--transition); }
 a:hover { color: var(--accent); text-decoration-color: var(--accent); }
-/* Knop-ankers houden hun eigen tekstkleur bij hover. Zonder dit overschrijft de
-   globale a:hover{color:accent} de knop-tekst → die wordt dezelfde kleur als de
-   accent-achtergrond en valt weg (vooral op sites met een felle accent). */
-a.btn:hover, a.epk-btn:hover { color: var(--ink); }
-a.btn-primary:hover, a.btn-danger:hover, a.btn-success:hover, a.button:hover,
-a.epk-btn-primary:hover, a.dls-btn:hover, a.sh-tix:hover, a.dl-go:hover { color: #fff; text-decoration-color: currentColor; }
 a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; text-decoration: none; }
 blockquote { margin: 2em 0; padding: .25em 0 .25em 1.25em; border-left: 2px solid var(--accent); font-family: var(--font-display); font-style: italic; font-size: 1.15em; color: var(--ink-soft); }
@@ -476,9 +280,4 @@
 .container { max-width: 42rem; margin-inline: auto; padding-inline: clamp(1.25rem, 4vw, 2rem); }
 
-/* Post-/leesbreedte. Globaal + hogere specificity (.container.post-page) zodat 'ie
-   de responsive .container-breakpoints (56/72rem) overal overrult — posts én de
-   downloads-pagina krijgen zo exact dezelfde 720px-breedte. */
-.container.post-page { max-width: 720px; margin: 0.5rem auto 2rem; padding: 0 1rem; }
-
 /* Laptop / Full HD — 1440px viewport of groter */
 @media (min-width: 1440px) {
@@ -510,9 +309,7 @@
 .masthead {
     border-bottom: 1px solid var(--rule);
-    padding: calc(1rem + var(--ios-safe-top, 0px)) 0 1rem;
-    /* GEEN backdrop-filter hier: een voorouder met backdrop-filter blokkeert de
-       frosted blur van het user-menu-dropdown eronder. Masthead is toch bijna
-       dekkend, dus solide bg i.p.v. blur. */
-    background: color-mix(in srgb, var(--paper) 94%, transparent);
+    padding: calc(1rem + var(--safe-top)) 0 1rem;
+    background: color-mix(in srgb, var(--paper) 88%, transparent);
+    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
     position: sticky; top: 0; z-index: 20;
 }
@@ -559,24 +356,4 @@
 
 .empty { text-align: center; padding: 4rem 1rem; }
-/* Moved account (FEP-7628 slice 3): the signpost above the feed. Loud on
-   purpose — a visitor must not read a stale timeline thinking it is current. */
-.moved-banner {
-    text-align: center;
-    margin: 1.5rem 0 2rem;
-    padding: 1.75rem 1.25rem;
-    border: 2px solid var(--accent, #e8b04b);
-    border-radius: var(--radius, 10px);
-    background: color-mix(in srgb, var(--accent, #e8b04b) 10%, var(--paper-2, var(--paper)));
-}
-.moved-banner-lead { margin: 0 0 .5rem; font-size: 1rem; opacity: .85; }
-.moved-banner-link {
-    display: inline-block;
-    font-family: var(--font-display);
-    font-size: clamp(1.5rem, 5vw, 2.4rem);
-    font-weight: 700;
-    line-height: 1.15;
-    overflow-wrap: anywhere;
-}
-.moved-banner-hint { margin: .75rem 0 0; font-size: .85rem; color: var(--ink-soft); }
 .empty h1 { font-size: 2rem; }
 .empty p { color: var(--ink-muted); }
@@ -584,6 +361,5 @@
 /* Embeds */
 .pcms-embed, .folio-embed { margin: 2rem 0; }
-.pcms-embed iframe, .pcms-embed video, .pcms-embed audio,
-.folio-embed iframe, .folio-embed video, .folio-embed audio { width: 100%; display: block; border: 0; border-radius: var(--radius); }
+.pcms-embed iframe, .folio-embed iframe { width: 100%; display: block; border: 0; border-radius: var(--radius); }
 
 /* Article footer */
@@ -692,5 +468,5 @@
 
 /* Footer */
-.site-footer { padding: 2rem 0 calc(2rem + env(safe-area-inset-bottom, 0px)); border-top: 1px solid var(--rule); margin-top: 4rem; font-family: var(--font-ui); font-size: .85rem; color: var(--ink-muted); }
+.site-footer { padding: 2rem 0 calc(2rem + var(--safe-bot)); border-top: 1px solid var(--rule); margin-top: 4rem; font-family: var(--font-ui); font-size: .85rem; color: var(--ink-muted); }
 .site-footer-inner { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; align-items: center; }
 .site-footer a { color: var(--ink-muted); text-decoration-color: var(--rule); }
@@ -721,7 +497,7 @@
     display: block;
     width: 100%;
-    height: auto;        /* volledige (niet-bijgesneden) hoogte */
-    /* geen max-height/object-fit: cover meer → de hele afbeelding toont,
-       volle containerbreedte, zijkanten gerespecteerd. */
+    height: auto;
+    max-height: 50vh;
+    object-fit: cover;
 }
 .post-title { font-size: clamp(1.6rem, 4vw, 2.6rem); line-height: 1.15; margin: 0 0 .5rem; letter-spacing: -.02em; }
@@ -783,33 +559,4 @@
 .post-image--right img { margin-left: auto; margin-right: 0; }
 
-/* Afbeeldingen in de post-tekst vullen ALTIJD de containerbreedte en houden hun
-   natuurlijke (niet-bijgesneden) hoogte — geen vaste hoogte / geen crop. */
-.post-content img {
-    display: block;
-    width: 100%;
-    max-width: 100%;
-    height: auto;
-    border-radius: 6px;
-    margin: 1.4em auto;
-}
-
-/* Video in de post-tekst (C2S-uploads uit Shaer): dezelfde maatvoering als
-   afbeeldingen, plus een plafond. Zonder deze regel rendert een <video> op
-   zijn natuurlijke maat en duwt een portret-video de hele kolom vol; met
-   object-fit blijft het beeld heel binnen het kader. Geldt ook voor de
-   Krant/Berichten (tl-content), waar dezelfde content langskomt. */
-.post-content video, .tl-content video {
-    display: block;
-    width: 100%;
-    max-width: 100%;
-    max-height: min(70vh, 540px);
-    height: auto;
-    object-fit: contain;
-    background: #000;
-    border-radius: 6px;
-    margin: 1.4em auto;
-}
-.post-content audio, .tl-content audio { width: 100%; display: block; margin: 1em auto; }
-
 /* Mini-player + bottom-tab clearance: applied to .pcms-main so the footer
    is NOT pushed below the viewport bottom. The bars are position:fixed so
@@ -819,25 +566,4 @@
 .pcms-main { padding-bottom: 5.5rem; }
 body.has-audio-player .pcms-main { padding-bottom: 5.5rem; }
-
-/* Mobiel toetsenbord open (body.kb-open, gezet via visualViewport in shell.ejs):
-   verberg de vaste onderbalken — anders zweven de bottom-tab + mini-speler boven
-   het toetsenbord of overlappen ze het invoerveld. Ze komen terug zodra het
-   toetsenbord sluit. De extra clearance onderaan is dan ook niet meer nodig. */
-body.kb-open .bottom-tab,
-body.kb-open .audio-player { display: none !important; }
-body.kb-open .pcms-main { padding-bottom: 1rem; }
-
-/* Mobiel & tablet (touch): scrollbalken verbergen — scrollen blijft werken, alleen
-   de balk-UI is weg (geen verwarrende/dubbele balken). */
-@media (pointer: coarse) {
-  * { scrollbar-width: none; -ms-overflow-style: none; }
-  *::-webkit-scrollbar { width: 0; height: 0; display: none; }
-}
-
-/* Afbeeldingen lastiger op te slaan (frictie, geen echte beveiliging): geen
-   sleep-naar-bureaublad, geen iOS long-press-bewaarmenu. Rechtsklik wordt in JS
-   geblokkeerd (zie shell.ejs). Covers/hero zijn al CSS-achtergronden = niet via
-   "afbeelding opslaan" te pakken. */
-img { -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; user-drag: none; -webkit-touch-callout: none; }
 
 /* ============================================================
@@ -1172,7 +898,8 @@
     }
 
-    /* v9.3 — horizontaal clippen gebeurt nu globaal op html (zie de html-regel
-       bovenaan). Hier bewust NIETS op body zetten: body als scroll-container zou
-       de fixed bottom-tab op mobiel laten meeschuiven. */
+    /* v9.3 — Forceer niets schuift horizontaal weg */
+    body, html {
+        overflow-x: hidden;
+    }
 }
 
@@ -1662,5 +1389,5 @@
         border-top-right-radius: 0;
         box-shadow: none;
-        padding-top: calc(.75rem + var(--ios-safe-top, 0px));
+        padding-top: calc(.75rem + env(safe-area-inset-top, 0px));
         padding-bottom: calc(2rem + env(safe-area-inset-bottom, 0px));
     }
@@ -1686,39 +1413,6 @@
     .pagination-item { width: 100%; }
 }
-
-/* Taal-keuze in de header-nav (NL/EN/DE) — eigen gestylede dropdown. */
-.lang-menu { position: relative; }
-.lang-menu > summary {
-    list-style: none; cursor: pointer;
-    display: inline-flex; align-items: center; gap: .3rem;
-    padding: .35rem .55rem; border-radius: 999px;
-    border: 1px solid var(--rule); background: transparent; color: var(--ink);
-}
-.lang-menu > summary::-webkit-details-marker { display: none; }
-.lang-menu > summary:hover { border-color: var(--accent); }
-.lang-toggle .lang-code { font-size: .8rem; font-weight: 700; letter-spacing: .02em; }
-.lang-toggle .icon { width: 16px; height: 16px; opacity: .8; }
-.lang-caret { width: 12px; height: 12px; opacity: .7; transition: transform .15s; }
-.lang-menu[open] .lang-caret { transform: rotate(180deg); }
-.lang-dropdown {
-    position: absolute; top: calc(100% + 8px); right: 0;
-    min-width: 170px; z-index: 60;
-    background: var(--paper); border: 1px solid var(--rule);
-    border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.22);
-    padding: .35rem; display: flex; flex-direction: column;
-}
-.lang-item {
-    display: flex; align-items: center; gap: .6rem;
-    padding: .5rem .65rem; border-radius: 8px;
-    color: var(--ink); text-decoration: none; font-size: .9rem;
-}
-.lang-item:hover { background: var(--paper-2); }
-.lang-item.is-active { color: var(--accent); font-weight: 600; }
-.lang-item-code {
-    font-size: .7rem; font-weight: 700; opacity: .6;
-    min-width: 1.6em;
-}
 @media (max-width: 400px) {
-    .masthead { padding: calc(.5rem + var(--ios-safe-top, 0px)) 0 .5rem; }
+    .masthead { padding: calc(.5rem + var(--safe-top)) 0 .5rem; }
     .masthead-inner { gap: .25rem; }
     .site-title { font-size: 1.05rem; }
@@ -1971,18 +1665,10 @@
     overflow: hidden;
     border-radius: var(--radius);
-    /* Accent-gradient placeholder while the cover image loads (image fades in over it). */
-    background-image: linear-gradient(135deg,
-        color-mix(in srgb, var(--accent, #888) 22%, var(--paper-2, var(--paper))) 0%,
-        color-mix(in srgb, var(--accent, #888) 6%, var(--paper-2, var(--paper))) 100%);
-}
-.post-list-cover img, .post-list-cover video {
+}
+.post-list-cover img {
     width: 100%; height: 100%;
-    object-fit: cover;                 /* fill the square, keep aspect, crop the overflow, centered */
-    object-position: center;
+    object-fit: cover;
     display: block;
-    background-color: #fff;            /* transparent logos sit on white once loaded */
-    transition: opacity .4s ease;
-}
-.post-list-cover img.is-loading { opacity: 0; }
+}
 .post-list-body { display: flex; flex-direction: column; gap: .35rem; }
 .post-list-meta {
@@ -2254,6 +1940,5 @@
 }
 .profile-links li { margin: 0; }
-.profile-links a,
-.profile-links .profile-fedi-link {
+.profile-links a {
     display: inline-flex;
     align-items: center;
@@ -2267,6 +1952,5 @@
     transition: background .15s, color .15s;
 }
-.profile-links a:hover,
-.profile-links .profile-fedi-link:hover {
+.profile-links a:hover {
     background: color-mix(in srgb, var(--accent) 15%, transparent);
     color: var(--accent);
@@ -2341,6 +2025,5 @@
     background: var(--paper);
     /* Bottom margin gives breathing room between the switcher bar and the
-       first post. margin-top staat in shared-styles.ejs (de winnende inline
-       <style>) — symmetrisch op 1.5rem. */
+       first post — without it the feed sits flush against the bar. */
     margin-bottom: 1.5rem;
 }
@@ -2372,27 +2055,18 @@
     border-color: var(--ink-muted);
 }
-/* Uitgeschakeld (Lezen in de cirkel) wint van :hover -- anders licht een knop
-   op die je niet kunt indrukken, en dat is precies de verkeerde belofte. */
-.view-switch-btn[disabled],
-.view-switch-btn[disabled]:hover {
-    opacity: .38;
-    cursor: not-allowed;
-    color: var(--ink-muted);
-    border-color: var(--rule);
-}
-/* Actieve state: puur afgeleid van body[data-feed-view]. Geen .is-active class meer.
-   Niet op de Archive-pagina: daar is geen "huidige" timeline/grid-keuze, dus beide
-   knoppen blijven neutraal (grijs). */
-/* Actieve Tijdlijn/Grid-state wordt nu gestuurd via [aria-selected="true"] (gezet
-   door syncAria, die óók op htmx:afterSettle draait) i.p.v. body[data-feed-view].
-   Reden: body-class/attribuut-CSS pakte niet betrouwbaar na een htmx-OOB-swap;
-   een element-attribuut wel. De witte active-regel staat in shared-styles. */
+/* Actieve state: puur afgeleid van body[data-feed-view]. Geen .is-active class meer. */
+body[data-feed-view="timeline"] .view-switch-btn[data-view="timeline"],
+body[data-feed-view="grid"] .view-switch-btn[data-view="grid"] {
+    background: var(--ink);
+    color: var(--paper);
+    border-color: var(--ink);
+}
 .view-switch-btn .icon { width: 1rem; height: 1rem; }
 
 /* -------- FEED TOGGLE VIA BODY --------
-   Grid-modus werkt op de feed (home, cirkel). Het ARCHIEF negeert de grid-keuze
-   bewust: dat is altijd de chronologische jaar/maand-lijst (de switcher is daar
-   grijs en springt bij klik terug naar de feed). Post-pagina's tonen de
-   feed-timeline article sowieso altijd. Alle andere body-classes negeren
+   Grid-modus werkt op elke feed-pagina (home, archive). Op een post-pagina
+   is de feed-timeline article juist het hoofdonderwerp — daar moet 'ie
+   altijd zichtbaar zijn, dus de grid-toggle slaat alleen aan op
+   feed-pagina's (.on-home, .on-archive). Andere body-classes negeren de
    data-feed-view en houden timeline visible. */
 body.on-home[data-feed-view="grid"]    .feed-timeline,
@@ -2400,409 +2074,12 @@
 body.on-home[data-feed-view="grid"]    .related-posts,
 body.on-home[data-feed-view="grid"]    .article-footer,
-body.on-cirkel[data-feed-view="grid"]  .feed-timeline {
+body.on-archive[data-feed-view="grid"] .feed-timeline,
+body.on-archive[data-feed-view="grid"] .pagination {
     display: none !important;
 }
-/* Grid-sectie alleen zichtbaar op de feed in grid-mode (NIET op archief) */
+/* Grid-sectie alleen zichtbaar op feed-pagina's in grid-mode */
 .feed-grid { display: none; }
 body.on-home[data-feed-view="grid"]    .feed-grid,
-body.on-cirkel[data-feed-view="grid"]  .feed-grid { display: block; }
-
-/* -------- LEESWEERGAVE (data-feed-view="reader") --------
-   Dezelfde berichten als de tijdlijn, maar heel: één per scherm, grenzen door
-   scroll-snapping. GEEN eigen route en geen eigen shell -- de balken blijven
-   staan, want je bent nog op de feed en moet terug kunnen schakelen.
-   Pagina's zonder .feed-reader (cirkel, archief, post) negeren de keuze en
-   houden hun tijdlijn: er is hieronder simpelweg geen regel die die daar
-   verbergt. Zo is Lezen nooit een leeg scherm. */
-.feed-reader { display: none; }
-body.on-home[data-feed-view="reader"] .feed-reader { display: block; }
-/* :has(.feed-reader) is hier de LEGE STAAT, niet een truc. Zonder berichten
-   rendert de leessectie niet, en dan mag de tijdlijn niet verborgen worden --
-   want dáár staat "nog niets geschreven" met de knop om te beginnen. Zonder deze
-   voorwaarde is Lezen op een verse site een wit scherm. Hetzelfde geldt voor
-   feeds die (nog) geen leessectie hebben. */
-body.on-home[data-feed-view="reader"]:has(.feed-reader) .feed-timeline,
-body.on-home[data-feed-view="reader"]:has(.feed-reader) .feed-grid,
-body.on-home[data-feed-view="reader"]:has(.feed-reader) .pagination,
-body.on-home[data-feed-view="reader"]:has(.feed-reader) .related-posts,
-body.on-home[data-feed-view="reader"]:has(.feed-reader) .article-footer {
-    display: none !important;
-}
-
-/* -------- AUTO (data-feed-view="auto") --------
-   Lezen op mobiel, Tijdlijn op desktop. GEEN JavaScript: de CSS kiest, dus geen
-   resize-afhandeling en geen flikkering waarin je eerst de verkeerde weergave
-   ziet. De tijdlijn is de terugval, dus boven de grens hoeft hier niets te
-   staan -- die is vanzelf al zichtbaar.
-   De :has(.feed-reader)-voorwaarde staat er om dezelfde reden als hierboven:
-   zonder berichten is er geen leessectie, en dan moet de tijdlijn blijven staan
-   met "nog niets geschreven" erin. */
-@media (max-width: 767px) {
-    body.on-home[data-feed-view="auto"] .feed-reader { display: block; }
-    body.on-home[data-feed-view="auto"]:has(.feed-reader) .feed-timeline,
-    body.on-home[data-feed-view="auto"]:has(.feed-reader) .feed-grid,
-    body.on-home[data-feed-view="auto"]:has(.feed-reader) .pagination,
-    body.on-home[data-feed-view="auto"]:has(.feed-reader) .related-posts,
-    body.on-home[data-feed-view="auto"]:has(.feed-reader) .article-footer {
-        display: none !important;
-    }
-}
-/* In de auto-stand staan BEIDE knoppen in de HTML en laat de mediaquery er een
-   zien. Zo verspringt de naam van de knop niet terwijl je ernaar kijkt, en is er
-   geen JavaScript nodig dat op resize moet luisteren. */
-.view-switch-btn.vs-alleen-mobiel { display: none; }
-@media (max-width: 767px) {
-    .view-switch-btn.vs-alleen-mobiel { display: inline-flex; }
-    .view-switch-btn.vs-alleen-desktop { display: none; }
-}
-
-/* Snappen doet de SCROLLCONTAINER, en dat is het document -- niet .read-stream,
-   die heeft overflow: visible en scrollt zelf niet. Proximity en geen mandatory:
-   in een bericht dat langer is dan het scherm moet je rustig door kunnen lezen. */
-html:has(body.on-home[data-feed-view="reader"]) {
-    scroll-snap-type: y proximity;
-    /* GEEN scroll-behavior:smooth hier. Dat maakte elke beweging op deze
-       scroller traag, ook het snappen zelf -- en een snap moet vlot zijn
-       (Robin, 20-8: "vlot maar geanimeerd, mag niet te lang duren"). De browser
-       animeert een snap uit zichzelf al, en korter dan zijn smooth-scroll.
-       De terug-naar-boven-knop vraagt zijn animatie expliciet aan in
-       mod/read.js, dus die blijft vloeiend. */
-}
-/* En de browser niet óók laten compenseren als Load more er berichten bij zet:
-   Chrome en Firefox schuiven bij invoegen zelf mee (scroll anchoring), en dat
-   vecht met het snappen. */
-html:has(body.on-home[data-feed-view="reader"]),
-.feed-reader .read-stream { overflow-anchor: none; }
-
-.feed-reader .read-post {
-    /* Snap naar de BOVENkant van een bericht, zacht (proximity op html) en nooit
-       dwingend (stop: normal). Een korte post vult het scherm, zodat hij niet als
-       snipper tussen twee lijnen hangt.
-       De uitzondering staat NIET hier maar in mod/read.js: een bericht dat langer
-       is dan het scherm krijgt scroll-snap-align:none, anders trekt de browser je
-       tijdens het lezen terug naar zijn bovenrand en lijkt het of het scrollen
-       stopt. "Past het op het scherm?" is een vraag over gemeten hoogte, en die
-       kan CSS niet stellen. */
-    /* Je LANDT op de bovenkant van een bericht -- dat is wat "snap to post"
-       hoort te betekenen. (align:end probeerde ik ook; dan land je op de
-       ONDERKANT van het vorige bericht, en bij een lang bericht zie je dus zijn
-       laatste schermvol in plaats van de kop van het volgende.)
-       Wanneer die bovenkant een snappunt IS, staat in mod/read.js: alleen zolang
-       je er nog niet voorbij bent. Eenmaal in een bericht snapt er niets meer,
-       ook niet naar zijn eigen bovenrand -- anders trekt proximity je terug
-       zodra je een paar regels verder leest. */
-    /* Het snappunt zit op .read-anker hierna, niet hier. Zie de toelichting
-       daar: een snapgebied groter dan het scherm heeft een BEREIK aan geldige
-       posities, en de bovenste daarvan zet het volgende bericht op de onderrand. */
-    scroll-snap-align: none;
-    scroll-snap-stop: normal;
-    /* GEEN min-height meer. Die rekte elk bericht tot een vol scherm, en bij een
-       kort bericht leverde dat een halve pagina leegte op onder "Replies and
-       reactions" -- je zat naar niets te kijken en moest verder scrollen om te
-       ontdekken dat er nog iets kwam. Robins vondst (20-8), met een
-       schermafbeelding erbij.
-       Zonder die hoogte is de stroom gewoon bericht na bericht: je ziet het
-       volgende meteen onder het vorige beginnen. Het snappen doet nog steeds
-       zijn werk aan de grens -- dat hangt aan scroll-snap-align en niet aan de
-       hoogte -- dus de streep lijnt nog altijd uit met de bovenkant van het
-       volgende bericht. */
-    max-width: 46rem;
-    margin: 0 auto;
-    /* De onderrand loopt achter de vaste tabbalk door; deze padding houdt de
-       TEKST erboven. De BOVENmarge is verdubbeld (Robin, 20-8: 2rem -> 4rem):
-       na een snap staat de scheidingslijn op de bovenrand, en de tekst
-       daaronder krijgt meer lucht. MOET GELIJK BLIJVEN aan scroll-margin-top
-       op .read-anker hieronder -- lopen die uit elkaar, dan landt de lijn niet
-       meer precies op de bovenrand. */
-    padding: 4rem 1.2rem 4rem;
-    box-sizing: border-box;
-}
-/* ONDERIN GEBEURT ER NIETS (Robin, 20-8: "enkel de bovenkant mag snappen, niet
-   de onderkant; onderin mag er niks met de zone gebeuren").
-   Het laatste bericht heeft een snappunt dat de browser NIET KAN BEREIKEN: er
-   zit niet genoeg pagina onder om zijn bovenkant naar de bovenrand te brengen.
-   Gemeten op dev: snappunt 2548, maar de scroll gaat maar tot 2356. De spec
-   laat de browser dan naar de dichtstbijzijnde BEREIKBARE positie snappen, en
-   dat is de bodem -- waardoor het laatste bericht ergens middenin het scherm
-   blijft hangen en het lijkt of er aan de onderkant wordt uitgelijnd.
-   Zonder snappunt scrolt dat laatste stuk gewoon vrij uit tot het einde. */
-/* HET SNAPPUNT. Nul hoog, dus precies EEN geldige positie: deze bovenrand aan de
-   bovenrand van het scherm. Zat de uitlijning op het artikel zelf, dan gold voor
-   elk bericht dat hoger is dan het scherm een heel BEREIK aan geldige posities
-   (spec: een snapgebied groter dan de snapport mag overal staan waar het de
-   snapport nog vult). Gemeten op dev: bericht van 1009px op een scherm van 812
-   -> geldig van 700 tot 897, en op 867 bleef hij dus gewoon staan. Die bovenste
-   stand, 897, is de onderkant van dat bericht op de onderrand -- oftewel de
-   bovenkant van het VOLGENDE bericht op de onderrand. Dat is wat er onderin
-   leek te snappen, en het kwam van de browser en niet uit onze code. */
-.feed-reader .read-anker {
-    display: block;
-    height: 0;
-    scroll-snap-align: start;
-    /* Het anker staat BINNEN de padding van het artikel, dus een padding-top
-       onder de streep. Zonder deze correctie landt de snap net zoveel te laag
-       en staat de scheidingslijn boven beeld -- gemeten: 733 waar 700 hoort.
-       scroll-margin verschuift alleen het SNAPPUNT en niet de opmaak, dus het
-       anker blijft waar het staat en de uitlijning klopt met de lijn.
-       GELIJK aan de padding-top van .read-post hierboven; die ging 20-8 van
-       2rem naar 4rem, dus deze ook. */
-    scroll-margin-top: 4rem;
-}
-/* HET LAATSTE BERICHT SNAPT OOK (Robin, 20-8). Dat kon eerst niet: zijn
-   bovenkant was onbereikbaar -- er zat te weinig pagina onder om hem naar de
-   bovenrand te brengen (gemeten: snappunt 2548, maxScroll 2356), en de browser
-   viel dan terug op de dichtstbijzijnde bereikbare positie: de bodem. Het
-   snappunt weghalen was de vorige oplossing; dit is de echte.
-   Een volle schermhoogte op het LAATSTE bericht maakt die bovenkant precies
-   bereikbaar. Korter dan het scherm: het bericht wordt even hoog en het snappunt
-   valt exact op maxScroll. Langer dan het scherm: dan was het al haalbaar en
-   doet min-height niets.
-   100lvh en niet 100svh: op een telefoon met een verdwijnende adresbalk is het
-   zichtbare scherm soms de GROTE viewport, en dan komt svh net tekort.
-   Zichtbaar kost dit niets: berichten hebben geen kader, alleen een scheidings-
-   lijn erboven, dus het laatste bericht krijgt hooguit witruimte eronder. */
-.feed-reader .read-post:last-child { min-height: 100lvh; }
-
-/* ELK BERICHT EEN EIGEN SCHERM, als de site dat wil (Instellingen -> Weergave).
-   Aan: een kort bericht vult het scherm en je ziet het volgende pas na een snap.
-   Uit (standaard): de stroom is bericht na bericht, zonder lege ruimte eronder.
-   Dit stond eerst vast op AAN, is toen vast op UIT gezet omdat een kort bericht
-   een halve pagina leegte gaf onder "Replies and reactions", en is nu een keuze
-   -- want welke van de twee prettiger leest hangt af van wat je schrijft. */
-body[data-reader-pages="1"] .feed-reader .read-post { min-height: 100svh; }
-/* OP MOBIEL ALTIJD, wat de instelling ook zegt (Robin, 20-8). Op een telefoon is
-   een bericht per scherm de vorm die werkt -- je veegt van bericht naar bericht.
-   Op desktop blijft het een keuze, want daar lees je in een kolom naast veel
-   witruimte en is een doorlopende stroom vaak prettiger. */
-@media (hover: none) and (pointer: coarse) {
-    /* Op mobiel draait Lezen in paginamodus (body.is-paged), en die zet hieronder
-       een vaste hoogte. Deze regel is de terugval voor het moment vóór de module
-       geladen is, zodat de berichten niet eerst als losse stroom in beeld
-       springen. */
-    .feed-reader .read-post { min-height: 100svh; }
-/* Geen native snap op mobiel: dat doet lenis/snap, met een instelbare vangzone
-       en duur. Twee snapsystemen op dezelfde scroller vechten. */
-    html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: none; }
-}
-
-/* -------- PAGINAMODUS --------
-   Elk bericht is een paneel van precies een scherm dat ZELF scrollt; tussen
-   berichten ga je met knoppen. Daarmee doet het scrollgevoel er niet meer toe:
-   geen momentum om na te bouwen en geen snap die vangt.
-   De stroom zelf vangt geen vingers meer -- dat regelt lenis.stop() in
-   mod/read.js -- en data-lenis-prevent op het paneel houdt de scroll BINNEN een
-   bericht ongemoeid (die controle staat in Lenis vóór de gestopt-controle).
-   Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop
-   als de site dat zo instelt. */
-body.is-paged .feed-reader .read-post {
-    height: 100svh;
-    min-height: 0;
-    overflow-y: auto;
-    overscroll-behavior: contain;   /* raakt het einde, dan niet doorschuiven */
-    scroll-snap-align: none;
-    /* Ruimte voor de twee balken, zodat er nooit tekst onder een knop verdwijnt.
-       Het verloop laat een regel wel zacht wegvagen, maar wat je moet KUNNEN
-       lezen hoort er niet achter te komen -- vandaar padding en niet alleen die
-       fade. --read-balk staat hieronder bij .read-nav, zodat de twee niet uit
-       elkaar kunnen lopen. */
-    padding-top: calc(var(--read-balk) + .5rem);
-    padding-bottom: calc(var(--read-balk) + .5rem);
-    /* BOVENAAN beginnen, niet gecentreerd. Ik had de inhoud een ronde lang
-       verticaal gecentreerd om het gat onder een kort bericht te verdelen, maar
-       dan hangt zo'n bericht halverwege het scherm en begint niet elk paneel op
-       dezelfde plek -- dat leest onrustig. Robin ving het (20-8). Elk bericht
-       begint nu bovenaan, en de ruimte die overblijft valt onderaan. */
-}
-body.is-paged .feed-reader .read-anker { scroll-snap-align: none; }
-
-/* DE DYNAMIC ISLAND, en met opzet alleen op iOS (Robin, 20-8: "op android is het
-   een ander verhaal").
-   In paginamodus vult een bericht precies het scherm, dus de eerste regel komt
-   bovenaan -- en op een iPhone ligt daar de island of de notch overheen.
-   env(safe-area-inset-top) geeft daar de vrije ruimte; viewport-fit=cover staat
-   al in de viewport-meta, dus die waarde is echt.
-   De @supports-regel is de iOS-Safari-toets: -webkit-touch-callout bestaat alleen
-   daar. Op Android is env() vaak 0 of betekent het iets anders (camera-uitsparing,
-   statusbalk die al meegerekend is), en dan zou dit een gat bovenin maken dat er
-   niet hoort. Vandaar afgeschermd in plaats van env() overal toepassen.
-   De hoogte krimpt mee, anders duwt de padding het paneel groter dan het scherm
-   en krijg je alsnog scroll tussen de berichten. */
-@supports (-webkit-touch-callout: none) {
-    body.is-paged .feed-reader .read-post {
-        padding-top: calc(var(--read-balk) + .5rem + env(safe-area-inset-top, 0px));
-        height: calc(100svh - env(safe-area-inset-top, 0px));
-    }
-    /* De bovenbalk houdt zijn knop al vrij van de island via padding-top
-       hierboven; hier hoeft niets extra's meer. */
-}
-
-/* De knop onderaan een bericht: naar het volgende. */
-
-
-/* Omhoog en omlaag: twee balken, boven en onder, die BLIJVEN staan terwijl je in
-   een bericht scrolt (Robin, 20-8). Het verloop van paginakleur naar doorzichtig
-   laat de tekst eronderdoor wegvagen -- zonder dat zou een regel halverwege hard
-   achter de knop verdwijnen en oogt het als een afgeknipte pagina.
-   De BALK vangt geen aanrakingen (pointer-events: none), alleen de knop zelf --
-   anders zou een strook van 5rem boven- en onderaan je scroll opeten. */
-/* TWEE BALKEN over de volle breedte, boven en onder. Eerder waren dit losse
-   cirkels (te rommelig naast de tabbalk en de speler) en daarna onzichtbare
-   tikzones -- maar die werkten niet, omdat de onderste strook onder de
-   mini-speler en de tabbalk ligt en die de tik opvangen. Een balk met een eigen
-   z-index kan daar wel overheen.
-   Slank gehouden: ze eten hooguit 2.6rem aan elke kant, en het paneel houdt daar
-   al ruimte voor vrij. */
-:root { --read-balk: 2.6rem; }
-.read-bar {
-    position: fixed; left: 0; right: 0; z-index: 1060;   /* boven tabbalk (1050) */
-    display: flex; align-items: center;
-    /* Pijlen LINKS in de balk, niet gecentreerd (Robin, 20-8). */
-    justify-content: flex-start;
-    padding-left: max(1.1rem, env(safe-area-inset-left, 0px));
-    box-sizing: border-box;
-    height: var(--read-balk);
-    border: 0;
-    border-top: 1px solid transparent;
-    background: color-mix(in srgb, var(--paper) 90%, var(--ink));
-    backdrop-filter: blur(14px) saturate(140%);
-    -webkit-backdrop-filter: blur(14px) saturate(140%);
-    color: var(--ink);
-    cursor: pointer;
-    transition: opacity .15s ease;
-}
-.read-bar--top {
-    top: 0;
-    height: calc(var(--read-balk) + env(safe-area-inset-top, 0px));
-    padding-top: env(safe-area-inset-top, 0px);
-    border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
-}
-.read-bar--bottom {
-    /* De waarde komt uit mod/read.js: die meet hoe hoog de tabbalk en de
-       mini-speler werkelijk staan. Geraden rem-waarden lieten hem zweven -- een
-       kier boven de speler, of er half achter. Dit is de terugval voor de eerste
-       frame en voor als de module niet laadt. */
-    bottom: 0;
-    border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
-}
-.read-bar:active { background: color-mix(in srgb, var(--paper) 78%, var(--ink)); }
-.read-bar[hidden], body:not(.is-paged) .read-bar { display: none; }
-
-/* 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 {
-    /* Volle kolombreedte: de afbeelding erin neemt die breedte, dus de figure
-       hoeft niet meer om het beeld te krimpen. */
-    width: 100%;
-    margin: 0 0 1.2rem;
-    border-radius: 10px;
-    overflow: hidden;
-}
-.feed-reader .read-cover img,
-.feed-reader .read-cover video {
-    display: block;
-    /* Neemt de VOLLE kolombreedte, met de verhouding intact (object-fit
-       weggelaten, dus geen bijsnijden). Een cover die van zichzelf kleiner is
-       wordt netjes tot de kolom opgeschaald in plaats van als postzegeltje te
-       blijven staan -- dat was te klein. De hoogtegrens houdt een staande foto
-       nog binnen de perken zodat titel + eerste alinea boven de vouw blijven;
-       hij krimpt dan mee in plaats van bijgesneden te worden. */
-    display: block;
-    width: 100%;
-    height: auto;
-    max-height: 70svh;
-    object-fit: contain;
-    /* Een logo met doorzichtige achtergrond zweeft anders op de paginakleur;
-       dezelfde keuze als bij .post-list-cover in de tijdlijn. */
-    background-color: #fff;
-}
-
-.feed-reader .read-head { padding-bottom: .5rem; }
-.feed-reader .read-title { font-size: clamp(1.6rem, 4vw, 2.4rem); line-height: 1.15; margin: 0 0 .3rem; }
-/* Het pin-icoon voor de titel: meeschalend met de kop, maar duidelijk kleiner --
-   het is een markering en geen deel van de titel. */
-.feed-reader .read-pin {
-    display: inline-block;
-    vertical-align: baseline;
-    width: .62em; height: .62em;
-    margin-right: .35em;
-    color: var(--ink-muted);
-}
-.feed-reader .read-pin svg { width: 100%; height: 100%; display: block; }
-/* De titel is een link, maar mag er niet als een link uitzien: dit is een
-   leesscherm, geen index. */
-.feed-reader .read-title a { color: inherit; text-decoration: none; }
-.feed-reader .read-title a:hover,
-.feed-reader .read-title a:focus-visible { text-decoration: underline; }
-.feed-reader .read-when { font-size: .85rem; opacity: .6; margin: 0; }
-
-.feed-reader .read-foot { margin-top: 2rem; }
-.feed-reader .read-open {
-    display: inline-block; font-size: .9rem; opacity: .75;
-    text-decoration: none; border-bottom: 1px solid currentColor; padding-bottom: 1px;
-}
-.feed-reader .read-open:hover,
-.feed-reader .read-open:focus-visible { opacity: 1; }
-
-/* Terug naar boven. Rechtsonder, want daar zit de duim -- en boven de tabbalk,
-   die op mobiel 1050 is en anders overheen zou vallen. Onzichtbaar tot je ver
-   genoeg bent, zodat hij niet meteen in de weg staat bij het eerste bericht. */
-.read-top {
-    position: fixed;
-    right: max(1rem, env(safe-area-inset-right, 0));
-    bottom: calc(env(safe-area-inset-bottom, 0) + 4.75rem);
-    z-index: 1040;
-    display: grid;
-    place-items: center;
-    width: 44px;
-    height: 44px;                 /* 44px = de kleinste maat die een duim betrouwbaar raakt */
-    border-radius: 999px;
-    border: 1px solid var(--rule);
-    background: color-mix(in srgb, var(--paper) 88%, transparent);
-    backdrop-filter: blur(14px) saturate(140%);
-    -webkit-backdrop-filter: blur(14px) saturate(140%);
-    color: var(--ink);
-    box-shadow: 0 2px 12px color-mix(in srgb, var(--ink) 12%, transparent);
-    cursor: pointer;
-    opacity: 0;
-    pointer-events: none;
-    transition: opacity .18s ease, transform .18s ease;
-    transform: translateY(6px);
-}
-.read-top.is-zichtbaar { opacity: 1; pointer-events: auto; transform: none; }
-/* Buiten de leesweergave bestaat hij niet -- home.ejs rendert alle drie de
-   secties, dus zonder deze regel zweeft hij ook boven Tijdlijn en Grid. */
-body:not([data-feed-view="reader"]) .read-top,
-body:not(.on-home) .read-top { display: none; }
-/* Op desktop is er geen tabbalk, dus mag hij lager staan. */
-@media (min-width: 768px) {
-    .read-top { bottom: 1.5rem; right: 1.5rem; }
-}
-/* Staat de mini-speler onderin, dan wijkt de knop daarvoor -- anders komt hij er
-   bovenop te liggen (Robins schermafbeelding, 20-8). body.has-audio-player wordt
-   door audio-player.js gezet zodra de speler zichtbaar is, en weer weggehaald
-   als hij verdwijnt, dus dit volgt vanzelf.
-   Op mobiel staat de speler BOVEN de tabbalk gestapeld, dus daar komt zijn
-   hoogte bovenop de ruimte die er al gereserveerd was. */
-body.has-audio-player .read-top { bottom: calc(env(safe-area-inset-bottom, 0) + 4.75rem + 4.25rem); }
-@media (min-width: 768px) {
-    body.has-audio-player .read-top { bottom: calc(1.5rem + 4.25rem); }
-}
-@media (prefers-reduced-motion: reduce) {
-    .read-top { transition: none; transform: none; }
-}
-
-@media (prefers-reduced-motion: reduce) {
-    html:has(body.on-home[data-feed-view="reader"]) {
-        scroll-snap-type: none;
-        scroll-behavior: auto;
-    }
-}
+body.on-archive[data-feed-view="grid"] .feed-grid { display: block; }
 
 /* -------- GRID VIEW TILES (Instagram-stijl) -------- */
@@ -2829,45 +2106,4 @@
     -webkit-tap-highlight-color: rgba(0,0,0,.1);
 }
-
-/* Transparent covers (PNG logos etc.) sit on white so they don't blend into a dark
-   theme. The white is on the IMG, so the tile's accent-gradient placeholder shows
-   while the image is still loading. */
-.post-cover img,
-.post-list-cover img,
-.grid-tile-img { background-color: #fff; }
-/* Accent-gradient placeholder shown while the cover image loads (the image fades in
-   over it), instead of a white flash on cover tiles. */
-.grid-tile:not(.grid-tile-gradient) {
-    background-image: linear-gradient(135deg,
-        color-mix(in srgb, var(--accent, #888) 22%, var(--paper-2, var(--paper))) 0%,
-        color-mix(in srgb, var(--accent, #888) 6%, var(--paper-2, var(--paper))) 100%);
-}
-/* Cover as a real <img> (not background-image): browsers downsample <img> with the
-   high-quality image pipeline, so high-res line-art covers don't go jagged when shrunk.
-   Fades in on load (.is-loading removed) so the placeholder shows meanwhile. */
-.grid-tile-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; transition: opacity .4s ease; }
-.grid-tile-img.is-loading { opacity: 0; }
-/* Audio tile (Shaer, Robins vraag 30-7): the waveform PNG is white on
-   transparent and floats OVER the tile's own hue gradient, so every audio
-   post keeps its color and gets its shape. Wave and title each get their
-   own band (wave upper, title lower): centering both stacked them right
-   through each other (Robins schermafdruk). */
-.grid-tile-img.grid-tile-wave {
-    background-color: transparent;
-    object-fit: contain;
-    /* explicit box, not auto: an absolutely positioned img falls back to its
-       intrinsic size when width/height are auto, collapsing the band */
-    top: 8%; left: 8%; right: auto; bottom: auto;
-    width: 84%; height: 46%;
-    opacity: .85;
-}
-.grid-tile-audio .grid-tile-title { position: absolute; left: 10%; right: 10%; top: 60%; }
-.grid-tile-audio .grid-tile-gradient-src { position: absolute; left: 10%; right: 10%; top: 76%; }
-/* Cover videos are decorative, not players: let clicks + right-click pass through to the
-   tile link so you get the normal link menu (not the browser's <video> menu/controls).
-   Autoplay is independent of pointer-events, so the video still plays. */
-video.grid-tile-img,
-.post-list-cover video,
-.post-cover video { pointer-events: none; }
 .grid-tile:hover { transform: scale(0.98); }
 .grid-tile-overlay {
@@ -2876,7 +2112,5 @@
     padding: .6rem;
     display: flex;
-    flex-direction: column;
-    justify-content: flex-end;
-    align-items: flex-start;
+    align-items: flex-end;
     background: linear-gradient(to top, rgba(0,0,0,.65) 0%, rgba(0,0,0,0) 50%);
     opacity: 0;
@@ -2884,19 +2118,4 @@
     pointer-events: none; /* klik moet naar de <a> eronder gaan, niet naar de overlay */
 }
-/* Bron-site (cirkel-feed): toont bij hover "van <site>" onder de titel. */
-/* Source label: small text that wraps to 2 lines if needed, with a big chain icon
-   beside it (the text was 1.44rem — bigger than the title — and overflowed on mobile). */
-.grid-tile-overlay-src {
-    color: #fff; opacity: .85;
-    font-size: .72rem; line-height: 1.2; margin-top: .25rem;
-    display: flex; align-items: center; gap: .3rem; width: 100%;
-}
-.grid-tile-overlay-src::before { content: "⛓"; font-size: 1.3rem; line-height: 1; flex: 0 0 auto; }
-.grid-tile-gradient-src {
-    margin-top: .35rem; font-size: .72rem; line-height: 1.2; opacity: .85;
-    color: var(--ink-soft);
-    display: flex; align-items: center; gap: .3rem; width: 100%;
-}
-.grid-tile-gradient-src::before { content: "⛓"; font-size: 1.3rem; line-height: 1; flex: 0 0 auto; }
 .grid-tile-overlay strong {
     color: #fff;
@@ -2923,5 +2142,4 @@
     background-image: linear-gradient(135deg, hsl(var(--tile-hue, 30), 45%, 88%), hsl(calc(var(--tile-hue, 30) + 30), 35%, 75%));
     display: flex;
-    flex-direction: column;
     align-items: center;
     justify-content: center;
@@ -2944,10 +2162,4 @@
     overflow: hidden;
     z-index: 1;
-    /* Federated link posts can have a long bare URL as their title — break it so it wraps
-       inside the tile and clamps cleanly instead of overflowing past both edges. */
-    max-width: 100%;
-    overflow-wrap: anywhere;
-    word-break: break-word;
-    hyphens: auto;
 }
 
@@ -2972,21 +2184,17 @@
     pointer-events: none;
 }
-.grid-tile-pin svg {
-    width: .95em;
-    height: .95em;
-    margin-top: -1px;
-}
-/* Pinned/boosted tile: accent ring. An outline on the tile is painted UNDER the absolute
-   cover <img>, so draw the ring as a pseudo-element overlay ABOVE the image (z-index above
-   the img, below the corner badges + nsfw veil). */
-.grid-tile.is-pinned { position: relative; z-index: 1; }
-.grid-tile.is-pinned::after {
-    content: '';
-    position: absolute;
-    inset: 0;
-    border: 3px solid var(--accent);
-    border-radius: inherit;
-    pointer-events: none;
+.grid-tile-pin::before {
+    content: "📌";
+    font-size: .9em;
+    line-height: 1;
+}
+/* Pinned-tile overall highlight: accent border + subtle inner glow */
+.grid-tile.is-pinned {
+    outline: 3px solid var(--accent);
+    outline-offset: -3px;
     z-index: 1;
+}
+.grid-tile.is-pinned:hover {
+    outline-offset: -2px;
 }
 .grid-tile-type {
@@ -3156,35 +2364,4 @@
 }
 
-/* Solo / Cirkel scope-switcher (alleen in circle-modus) — segmented toggle, centraal.
-   Hoge specificiteit (+ !important op tekst) zodat 'ie globale link-stijlen wint. */
-.view-switcher-wrap .feed-scope {
-    display: inline-flex;
-    background: var(--paper-2);
-    border: 1px solid var(--rule);
-    border-radius: 999px;
-    padding: 3px;
-}
-.view-switcher-wrap .feed-scope-btn {
-    display: inline-flex;
-    align-items: center;
-    padding: .32rem .95rem;
-    min-height: 30px;
-    border: none !important;
-    border-radius: 999px !important;
-    background: transparent !important;
-    color: var(--ink-muted) !important;
-    font-family: var(--font-ui);
-    font-size: .82rem;
-    font-weight: 500 !important;
-    text-decoration: none !important;
-    cursor: pointer;
-    transition: all .15s;
-}
-.view-switcher-wrap .feed-scope-btn:hover { color: var(--ink) !important; }
-.view-switcher-wrap .feed-scope-btn.is-active {
-    background: var(--ink) !important;
-    color: var(--paper) !important;
-}
-
 .grid-cols-picker {
     display: none; /* default hidden — desktop + grid-active maakt 'm zichtbaar */
@@ -3221,17 +2398,17 @@
 }
 
-/* Desktop + grid-view → toon cols-picker (op de home-feed; niet op archief) */
+/* Desktop + grid-view → toon cols-picker (op home + archive) */
 @media (min-width: 600px) {
-    body.on-home[data-feed-view="grid"] .grid-cols-picker {
+    body.on-home[data-feed-view="grid"] .grid-cols-picker,
+    body.on-archive[data-feed-view="grid"] .grid-cols-picker {
         display: inline-flex;
     }
 }
 
-/* Kolom-overrides op desktop. Mobiel negeert dit (staat vast op 2). Geldt op ELKE
-   feed met grid (home én cirkel/tag/type/user) — scope op .feed-grid .grid-tiles. */
+/* Kolom-overrides op desktop. Mobiel negeert dit (staat vast op 2). */
 @media (min-width: 600px) {
-    body[data-grid-cols="2"] .feed-grid .grid-tiles { grid-template-columns: repeat(2, 1fr); }
-    body[data-grid-cols="3"] .feed-grid .grid-tiles { grid-template-columns: repeat(3, 1fr); }
-    body[data-grid-cols="4"] .feed-grid .grid-tiles { grid-template-columns: repeat(4, 1fr); }
+    body.on-home[data-grid-cols="2"] .grid-tiles { grid-template-columns: repeat(2, 1fr); }
+    body.on-home[data-grid-cols="3"] .grid-tiles { grid-template-columns: repeat(3, 1fr); }
+    body.on-home[data-grid-cols="4"] .grid-tiles { grid-template-columns: repeat(4, 1fr); }
 }
 
@@ -3263,5 +2440,5 @@
 
 /* Masthead: compacter */
-.masthead { padding: calc(.55rem + var(--ios-safe-top, 0px)) 0 .55rem; }
+.masthead { padding: calc(.55rem + var(--safe-top)) 0 .55rem; }
 .masthead-inner { gap: 1rem; }
 .site-title { font-size: 1.25rem; }
@@ -3291,5 +2468,5 @@
 
 /* Site footer compacter */
-.site-footer { padding: 1.25rem 0 calc(1.25rem + env(safe-area-inset-bottom, 0px)); margin-top: 2rem; font-size: .78rem; }
+.site-footer { padding: 1.25rem 0 calc(1.25rem + var(--safe-bot)); margin-top: 2rem; font-size: .78rem; }
 .site-footer-inner { gap: .75rem; }
 
@@ -3301,6 +2478,5 @@
 .profile-bio { font-size: .85rem; margin-top: .15rem; }
 .profile-links { margin-top: .5rem; gap: .4rem; }
-.profile-links a,
-.profile-links .profile-fedi-link { width: 32px; height: 32px; }
+.profile-links a { width: 32px; height: 32px; }
 
 @media (min-width: 768px) {
@@ -3525,21 +2701,4 @@
     border-color: var(--accent, #c2410c);
     background: color-mix(in srgb, var(--accent, #c2410c) 5%, var(--paper-2, transparent));
-}
-/* Korte highlight wanneer je via de mini-speler naar deze track springt. */
-.post-audio-track.pat-flash,
-.post-album-track-compact.pat-flash {
-    animation: patFlash 1.6s ease-out;
-    border-radius: 8px;
-}
-/* Blijvende highlight op de track die NU actief is in de speler. */
-.post-audio-track.pat-playing,
-.post-album-track-compact.pat-playing {
-    background: color-mix(in srgb, var(--accent, #c2410c) 12%, var(--paper-2, transparent));
-    box-shadow: inset 3px 0 0 0 var(--accent, #c2410c);
-    border-radius: 8px;
-}
-@keyframes patFlash {
-    0%, 30% { border-color: var(--accent, #c2410c); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, #c2410c) 35%, transparent); }
-    100% { box-shadow: 0 0 0 0 transparent; }
 }
 .post-audio-track .pat-play {
@@ -4983,278 +4142,2 @@
     }
 }
-
-/* === Fediverse interactions — inherits the native .comment styling === */
-.post-fediverse .fedi-stats { display: flex; gap: .45rem; margin: .35rem 0 1.5rem; align-items: center; }
-.post-fediverse .fedi-stat {
-  display: inline-flex; align-items: center; gap: .4rem;
-  padding: .32rem .7rem; border-radius: 999px; font-size: .85rem; line-height: 1;
-  font-variant-numeric: tabular-nums;
-  color: var(--ink-muted); background: transparent;
-  border: 1px solid var(--rule);
-}
-.post-fediverse .fedi-stat svg { width: 15px; height: 15px; }
-.post-fediverse .fedi-stat-btn { cursor: pointer; font: inherit; line-height: 1; transition: color .15s, border-color .15s, background .15s; }
-.post-fediverse .fedi-stat-btn:hover { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 55%, var(--rule)); background: var(--accent-tint); }
-.post-fediverse .fedi-stat-btn:hover svg { fill: color-mix(in srgb, var(--accent) 22%, transparent); }
-.post-fediverse .fedi-handle { color: var(--ink-soft, #888); font-size: .82rem; }
-.post-fediverse .comment-content p { margin: .2rem 0; }
-.post-fediverse .comment-content p:first-child { margin-top: 0; }
-.post-fediverse .comment-content p:last-child { margin-bottom: 0; }
-/* reply toggle: a plain "Reageer" action like the comment buttons (no triangle) */
-.post-fediverse .fedi-reply-toggle { list-style: none; cursor: pointer; display: inline-block; }
-.post-fediverse .fedi-reply-toggle::-webkit-details-marker { display: none; }
-.post-fediverse details[open] .fedi-reply-toggle { margin-bottom: .5rem; }
-
-/* Reply-via-fediverse CTA on post pages */
-.post-fediverse-cta { margin: 1.75rem 0; }
-.fedi-remote-reply-btn { display: inline-flex; align-items: center; gap: .45rem; }
-.post-fediverse-cta .fedi-remote-reply-btn {
-  border: 1px solid color-mix(in srgb, var(--accent, #888) 55%, transparent);
-  background: color-mix(in srgb, var(--accent, #888) 10%, transparent);
-  color: var(--ink, inherit); border-radius: 999px; padding: .5rem 1.05rem;
-  font-weight: 600; cursor: pointer; transition: background .15s ease;
-}
-.post-fediverse-cta .fedi-remote-reply-btn:hover { background: color-mix(in srgb, var(--accent, #888) 22%, transparent); }
-.post-fediverse-cta .fedi-remote-reply-btn svg { color: var(--accent, currentColor); }
-.post-fediverse .comment-actions .fedi-remote-reply-btn { gap: .3rem; }
-
-/* Inline "reply via fediverse" address form (replaces the browser prompt) */
-.fedi-remote-form {
-  position: absolute; z-index: 60; display: inline-flex; align-items: center; gap: .4rem;
-  padding: .4rem; border-radius: 999px;
-  background: var(--paper-2, var(--paper, #fff));
-  border: 1px solid color-mix(in srgb, var(--ink, #000) 12%, transparent);
-  box-shadow: 0 10px 30px rgba(0,0,0,.28);
-}
-.fedi-remote-form input {
-  height: 2.2rem; padding: 0 .9rem; border-radius: 999px; font: inherit; min-width: 230px;
-  border: 1px solid color-mix(in srgb, var(--ink, #000) 18%, transparent);
-  background: color-mix(in srgb, var(--ink, #000) 4%, transparent); color: var(--ink, #000);
-}
-.fedi-remote-form input::placeholder { color: var(--ink-soft, #999); }
-.fedi-remote-form input:focus { outline: none; border-color: color-mix(in srgb, var(--accent, #888) 70%, transparent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, #888) 22%, transparent); }
-.fedi-remote-form .fedi-remote-go {
-  height: 2.2rem; min-width: 2.2rem; padding: 0 .85rem; border-radius: 999px;
-  display: inline-flex; align-items: center; justify-content: center; font-size: 1rem;
-}
-.fedi-remote-form .fedi-remote-cancel { background: none; border: none; color: var(--ink-soft, #888); cursor: pointer; font-size: 1.1rem; line-height: 1; padding: .25rem .35rem; }
-.fedi-remote-form .fedi-remote-cancel:hover { color: var(--ink, #000); }
-
-/* ── NSFW / sensitive content — blur + click to reveal ───────────────── */
-.nsfw-media { position: relative; }
-.nsfw-veil {
-  position: absolute; inset: 0; z-index: 4; cursor: pointer; border-radius: inherit;
-  display: flex; flex-direction: column; align-items: center; justify-content: center;
-  gap: .5rem; padding: .75rem; text-align: center;
-  background: color-mix(in srgb, var(--paper, #14141a) 50%, transparent);
-  backdrop-filter: blur(22px) saturate(.6); -webkit-backdrop-filter: blur(22px) saturate(.6);
-}
-.nsfw-media.is-shown > .nsfw-veil { display: none; }
-.nsfw-veil-label { font-weight: 700; font-size: .82rem; color: var(--ink, #f3f1ea); text-shadow: 0 1px 2px rgba(0,0,0,.5); }
-.nsfw-veil-btn {
-  font-size: .72rem; font-weight: 600; padding: .3rem .8rem; border-radius: 999px;
-  border: 1px solid color-mix(in srgb, var(--ink, #fff) 55%, transparent);
-  background: color-mix(in srgb, var(--paper, #000) 35%, transparent); color: var(--ink, #f3f1ea);
-}
-/* Post page: blur cover + body behind a content-warning banner until revealed */
-.nsfw-gate .post-cover,
-.nsfw-gate .post-content { filter: blur(26px); pointer-events: none; user-select: none; transition: filter .2s ease; }
-.nsfw-gate.is-shown .post-cover,
-.nsfw-gate.is-shown .post-content { filter: none; pointer-events: auto; user-select: auto; }
-.nsfw-banner {
-  display: flex; align-items: center; gap: .7rem; flex-wrap: wrap; margin: 0 0 1.5rem;
-  padding: .8rem 1rem; border-radius: 12px;
-  border: 1px solid color-mix(in srgb, var(--accent, #e8b04b) 45%, transparent);
-  background: color-mix(in srgb, var(--accent, #e8b04b) 9%, transparent);
-}
-.nsfw-gate.is-shown .nsfw-banner { display: none; }
-.nsfw-banner-icon { font-size: 1.2rem; }
-.nsfw-banner-text { font-weight: 600; font-size: .92rem; color: var(--ink); }
-.nsfw-banner-btn {
-  margin-left: auto; cursor: pointer; font: inherit; font-size: .82rem; font-weight: 600;
-  padding: .4rem 1rem; border-radius: 999px; border: 0;
-  background: var(--accent, #e8b04b); color: #1a1205;
-}
-
-/* ── Het bandje (mixtape) ──────────────────────────────────────────────
-   Een cassette, getekend als SVG op de echte maat: 100,4 x 63,8 mm, dus een
-   viewBox van 314x200. De vorige versie tekende twee schijfjes met CSS; dit is
-   een replica -- behuizing, labelvlak, venster, twee spoelen met tandjes, de
-   schroefjes in de hoeken en de openingen voor de kop en de capstans onderin.
-
-   Alle kleuren komen uit de bestaande variabelen, dus licht, donker en het
-   accent van de site volgen vanzelf. */
-.post-tape {
-    position: relative;
-    border: 1px solid var(--rule, rgba(0,0,0,.1));
-    border-radius: 14px;
-    overflow: hidden;
-    margin: 1.25rem 0;
-    background: var(--paper-2, color-mix(in srgb, var(--ink) 3%, transparent));
-}
-
-.tape-shell { position: relative; padding: .8rem .8rem .3rem; }
-.tape-svg { display: block; width: 100%; height: auto; }
-
-/* De behuizing. Twee lagen zodat er een randje omheen zit, zoals bij een echt
-   doosje waar de bovenschaal iets uitsteekt. */
-.tape-body { fill: color-mix(in srgb, var(--ink) 82%, var(--paper, #fff)); }
-.tape-body-inner { fill: color-mix(in srgb, var(--ink) 70%, var(--paper, #fff)); }
-.tape-labelplate { fill: color-mix(in srgb, var(--paper, #fff) 92%, var(--accent)); }
-.tape-glass {
-    fill: color-mix(in srgb, var(--ink) 88%, transparent);
-    stroke: color-mix(in srgb, var(--paper, #fff) 25%, transparent);
-    stroke-width: 1.5;
-}
-.tape-pack { fill: color-mix(in srgb, var(--ink) 55%, var(--paper, #fff)); }
-.tape-ribbon { fill: color-mix(in srgb, var(--ink) 45%, var(--paper, #fff)); }
-.tape-hub { fill: var(--paper, #fff); }
-.tape-tooth { fill: color-mix(in srgb, var(--ink) 60%, var(--paper, #fff)); }
-.tape-screw { fill: color-mix(in srgb, var(--ink) 55%, var(--paper, #fff)); }
-.tape-slot { fill: color-mix(in srgb, var(--ink) 92%, transparent); }
-.tape-slot--capstan { fill: color-mix(in srgb, var(--ink) 96%, transparent); }
-
-/* Draaien. De tandjes zitten in dezelfde groep als de naaf, dus de hele spoel
-   draait als een geheel -- daar gaat het om, want aan die tandjes zie je pas
-   DAT hij draait. Het label blijft stil, anders wordt de titel onleesbaar. */
-.post-tape.is-playing .tape-reel { animation: tape-spin 3s linear infinite; }
-.post-tape.is-playing .tape-reel--right { animation-duration: 2.2s; }
-/* Spoelen: veel sneller, en achteruit ook echt achteruit. */
-.post-tape.is-winding .tape-reel { animation-duration: .45s; }
-.post-tape.is-winding-back .tape-reel { animation-direction: reverse; }
-@keyframes tape-spin { to { transform: rotate(360deg); } }
-@media (prefers-reduced-motion: reduce) {
-    .post-tape.is-playing .tape-reel,
-    .post-tape.is-winding .tape-reel { animation: none; }
-}
-
-/* De tekst staat OP het labelvlak van de cassette. Percentages volgen de
-   viewBox, dus het blijft kloppen op elke breedte. */
-.tape-label {
-    position: absolute;
-    left: 8.4%; right: 8.4%;
-    top: calc(.8rem + 11%);
-    height: 31%;
-    padding: .1rem .4rem;
-    color: color-mix(in srgb, var(--ink) 92%, transparent);
-    overflow: hidden;
-}
-.tape-kind {
-    margin: 0;
-    font-size: clamp(.55rem, 1.6vw, .68rem);
-    letter-spacing: .14em;
-    text-transform: uppercase;
-    opacity: .7;
-}
-.tape-title {
-    margin: 0;
-    font-size: clamp(.85rem, 2.6vw, 1.1rem);
-    line-height: 1.15;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-}
-.tape-artist { margin: 0; font-size: clamp(.65rem, 1.9vw, .8rem); opacity: .8; }
-.tape-meta { margin: 0; font-size: clamp(.6rem, 1.7vw, .72rem); opacity: .6; }
-
-/* De knoppen: terugspoelen, spelen, vooruitspoelen. Meer heeft een cassette
-   niet, en dat is de bedoeling -- er is geen zoekbalk omdat je op een bandje
-   niet kunt zoeken, alleen spoelen. */
-.tape-controls {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: .6rem;
-    padding: .5rem 0 .6rem;
-}
-.tape-btn {
-    display: grid;
-    place-items: center;
-    width: 40px; height: 40px;
-    border-radius: 50%;
-    border: 1px solid var(--rule, rgba(0,0,0,.15));
-    background: var(--paper, #fff);
-    color: var(--ink);
-    cursor: pointer;
-    padding: 0;
-    touch-action: manipulation;   /* vasthouden mag geen zoom of scroll worden */
-    user-select: none;
-}
-.tape-btn svg { width: 18px; height: 18px; }
-.tape-btn:hover { border-color: var(--accent); color: var(--accent); }
-.tape-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
-.tape-btn--play {
-    width: 52px; height: 52px;
-    background: var(--accent);
-    border-color: var(--accent);
-    color: var(--paper, #fff);
-}
-.tape-btn--play svg { width: 22px; height: 22px; }
-.post-tape.is-winding .tape-btn--back,
-.post-tape.is-winding .tape-btn--fwd { border-color: var(--accent); color: var(--accent); }
-
-.tape-now {
-    margin: 0;
-    padding: 0 .8rem .4rem;
-    text-align: center;
-    font-size: .8rem;
-    opacity: .8;
-    min-height: 1.2em;
-}
-
-/* De nummers staan er als INHOUD, niet als knoppen. Geen cursor, geen hover,
-   geen aanwijzing dat je erop kunt drukken: op een cassette kun je dat niet. */
-.tape-tracks {
-    margin: 0;
-    padding: 0 .8rem .8rem;
-    list-style: none;
-    counter-reset: tape;
-    display: grid;
-    gap: .1rem;
-}
-.tape-track {
-    counter-increment: tape;
-    display: flex;
-    gap: .5rem;
-    align-items: baseline;
-    font-size: .85rem;
-    padding: .18rem .3rem;
-    border-radius: 6px;
-    opacity: .75;
-}
-.tape-track::before {
-    content: counter(tape) ".";
-    opacity: .55;
-    min-width: 1.4em;
-    font-variant-numeric: tabular-nums;
-}
-.tape-track-title { flex: 1; }
-.tape-track-dur { opacity: .6; font-variant-numeric: tabular-nums; }
-.tape-track.is-current {
-    opacity: 1;
-    background: color-mix(in srgb, var(--accent) 12%, transparent);
-}
-
-
-/* ── De speler in bandmodus ────────────────────────────────────────────
-   Zelfde speler, ander gedrag: op een cassette kies je geen nummer. De lijst
-   blijft staan zodat je ziet wat erop staat, maar zonder de opmaak die belooft
-   dat je erin kunt prikken. */
-.audio-sheet-queue-list.is-tape .audio-sheet-queue-item {
-    cursor: default;
-    pointer-events: none;
-}
-.audio-sheet-queue-list.is-tape .audio-sheet-queue-item:hover {
-    background: none;
-}
-/* Het lopende nummer blijft wel gemarkeerd -- dat is de enige plek waar je op
-   een bandje leest waar je bent. */
-.audio-sheet-queue-list.is-tape .audio-sheet-queue-item.is-current {
-    opacity: 1;
-}
-/* Spoelknoppen mogen niet als "volgende" voelen: vasthouden hoort geen tekst te
-   selecteren of de pagina te laten zoomen. */
-.audio-btn.is-wind {
-    touch-action: manipulation;
-    user-select: none;
-}
