Changeset 22d2dba5 in Klonkt
- Timestamp:
- 08/20/2026 07:25:24 AM (3 weeks ago)
- Branches:
- main
- Children:
- 77019fa
- Parents:
- c581e5e
- git-author:
- Robin <roboburr@…> (08/20/2026 03:33:49 AM)
- git-committer:
- Claude (agent) <aiclaude@…> (08/20/2026 07:25:24 AM)
- Location:
- src
- Files:
-
- 5 edited
-
assets/css/style.css (modified) (1 diff)
-
assets/js/mod/read.js (modified) (13 diffs)
-
services/i18n.js (modified) (3 diffs)
-
views/pages/home.ejs (modified) (1 diff)
-
views/shell.ejs (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/css/style.css
rc581e5e r22d2dba5 2561 2561 witruimte en is een doorlopende stroom vaak prettiger. */ 2562 2562 @media (hover: none) and (pointer: coarse) { 2563 /* Op mobiel draait Lezen in paginamodus (body.is-paged), en die zet hieronder 2564 een vaste hoogte. Deze regel is de terugval voor het moment vóór de module 2565 geladen is, zodat de berichten niet eerst als losse stroom in beeld 2566 springen. */ 2563 2567 .feed-reader .read-post { min-height: 100svh; } 2564 } 2568 /* Geen native snap op mobiel: dat doet lenis/snap, met een instelbare vangzone 2569 en duur. Twee snapsystemen op dezelfde scroller vechten. */ 2570 html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: none; } 2571 } 2572 2573 /* -------- PAGINAMODUS -------- 2574 Elk bericht is een paneel van precies een scherm dat ZELF scrollt; tussen 2575 berichten ga je met knoppen. Daarmee doet het scrollgevoel er niet meer toe: 2576 geen momentum om na te bouwen en geen snap die vangt. 2577 De stroom zelf vangt geen vingers meer -- dat regelt lenis.stop() in 2578 mod/read.js -- en data-lenis-prevent op het paneel houdt de scroll BINNEN een 2579 bericht ongemoeid (die controle staat in Lenis vóór de gestopt-controle). 2580 Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop 2581 als de site dat zo instelt. */ 2582 body.is-paged .feed-reader .read-post { 2583 height: 100svh; 2584 min-height: 0; 2585 overflow-y: auto; 2586 overscroll-behavior: contain; /* raakt het einde, dan niet doorschuiven */ 2587 scroll-snap-align: none; 2588 /* Ruimte voor de twee balken, zodat er nooit tekst onder een knop verdwijnt. 2589 Het verloop laat een regel wel zacht wegvagen, maar wat je moet KUNNEN 2590 lezen hoort er niet achter te komen -- vandaar padding en niet alleen die 2591 fade. --read-balk staat hieronder bij .read-nav, zodat de twee niet uit 2592 elkaar kunnen lopen. */ 2593 padding-top: calc(var(--read-balk) + .5rem); 2594 padding-bottom: calc(var(--read-balk) + .5rem); 2595 /* BOVENAAN beginnen, niet gecentreerd. Ik had de inhoud een ronde lang 2596 verticaal gecentreerd om het gat onder een kort bericht te verdelen, maar 2597 dan hangt zo'n bericht halverwege het scherm en begint niet elk paneel op 2598 dezelfde plek -- dat leest onrustig. Robin ving het (20-8). Elk bericht 2599 begint nu bovenaan, en de ruimte die overblijft valt onderaan. */ 2600 } 2601 body.is-paged .feed-reader .read-anker { scroll-snap-align: none; } 2602 2603 /* DE DYNAMIC ISLAND, en met opzet alleen op iOS (Robin, 20-8: "op android is het 2604 een ander verhaal"). 2605 In paginamodus vult een bericht precies het scherm, dus de eerste regel komt 2606 bovenaan -- en op een iPhone ligt daar de island of de notch overheen. 2607 env(safe-area-inset-top) geeft daar de vrije ruimte; viewport-fit=cover staat 2608 al in de viewport-meta, dus die waarde is echt. 2609 De @supports-regel is de iOS-Safari-toets: -webkit-touch-callout bestaat alleen 2610 daar. Op Android is env() vaak 0 of betekent het iets anders (camera-uitsparing, 2611 statusbalk die al meegerekend is), en dan zou dit een gat bovenin maken dat er 2612 niet hoort. Vandaar afgeschermd in plaats van env() overal toepassen. 2613 De hoogte krimpt mee, anders duwt de padding het paneel groter dan het scherm 2614 en krijg je alsnog scroll tussen de berichten. */ 2615 @supports (-webkit-touch-callout: none) { 2616 body.is-paged .feed-reader .read-post { 2617 padding-top: calc(var(--read-balk) + .5rem + env(safe-area-inset-top, 0px)); 2618 height: calc(100svh - env(safe-area-inset-top, 0px)); 2619 } 2620 /* De bovenbalk houdt zijn knop al vrij van de island via padding-top 2621 hierboven; hier hoeft niets extra's meer. */ 2622 } 2623 2624 /* De knop onderaan een bericht: naar het volgende. */ 2625 2626 2627 /* Omhoog en omlaag: twee balken, boven en onder, die BLIJVEN staan terwijl je in 2628 een bericht scrolt (Robin, 20-8). Het verloop van paginakleur naar doorzichtig 2629 laat de tekst eronderdoor wegvagen -- zonder dat zou een regel halverwege hard 2630 achter de knop verdwijnen en oogt het als een afgeknipte pagina. 2631 De BALK vangt geen aanrakingen (pointer-events: none), alleen de knop zelf -- 2632 anders zou een strook van 5rem boven- en onderaan je scroll opeten. */ 2633 /* TWEE BALKEN over de volle breedte, boven en onder. Eerder waren dit losse 2634 cirkels (te rommelig naast de tabbalk en de speler) en daarna onzichtbare 2635 tikzones -- maar die werkten niet, omdat de onderste strook onder de 2636 mini-speler en de tabbalk ligt en die de tik opvangen. Een balk met een eigen 2637 z-index kan daar wel overheen. 2638 Slank gehouden: ze eten hooguit 2.6rem aan elke kant, en het paneel houdt daar 2639 al ruimte voor vrij. */ 2640 :root { --read-balk: 2.6rem; } 2641 .read-bar { 2642 position: fixed; left: 0; right: 0; z-index: 1060; /* boven tabbalk (1050) */ 2643 display: flex; align-items: center; 2644 /* Pijlen LINKS in de balk, niet gecentreerd (Robin, 20-8). */ 2645 justify-content: flex-start; 2646 padding-left: max(1.1rem, env(safe-area-inset-left, 0px)); 2647 box-sizing: border-box; 2648 height: var(--read-balk); 2649 border: 0; 2650 border-top: 1px solid transparent; 2651 background: color-mix(in srgb, var(--paper) 90%, var(--ink)); 2652 backdrop-filter: blur(14px) saturate(140%); 2653 -webkit-backdrop-filter: blur(14px) saturate(140%); 2654 color: var(--ink); 2655 cursor: pointer; 2656 transition: opacity .15s ease; 2657 } 2658 .read-bar--top { 2659 top: 0; 2660 height: calc(var(--read-balk) + env(safe-area-inset-top, 0px)); 2661 padding-top: env(safe-area-inset-top, 0px); 2662 border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); 2663 } 2664 .read-bar--bottom { 2665 /* De waarde komt uit mod/read.js: die meet hoe hoog de tabbalk en de 2666 mini-speler werkelijk staan. Geraden rem-waarden lieten hem zweven -- een 2667 kier boven de speler, of er half achter. Dit is de terugval voor de eerste 2668 frame en voor als de module niet laadt. */ 2669 bottom: 0; 2670 border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); 2671 } 2672 .read-bar:active { background: color-mix(in srgb, var(--paper) 78%, var(--ink)); } 2673 .read-bar[hidden], body:not(.is-paged) .read-bar { display: none; } 2565 2674 2566 2675 /* Een lijn tussen twee berichten: zonder grens is een stroom een lange brij. */ -
src/assets/js/mod/read.js
rc581e5e r22d2dba5 30 30 * scrollen, en cmd/ctrl-klik hoort de browser zelf af te handelen. 31 31 */ 32 33 // ── Paginamodus ───────────────────────────────────────────────────────────── 34 /** 35 * Elk bericht een paneel dat zelf scrollt; tussen berichten ga je met knoppen. 36 * 37 * Robins voorstel (20-8) nadat het namaken van iOS-momentum niet lukte, en dat 38 * is de winst: in dit model DOET het scrollgevoel er niet toe. Er is geen 39 * momentum om na te bouwen en geen snap die vangt. 40 * 41 * Hoe het werkt: 42 * - lenis.stop() zet het scrollen van de STROOM stil. Lenis vangt dan wiel en 43 * vinger af (hij preventDefault't in onVirtualScroll), dus je kunt niet meer 44 * tussen berichten door scrollen. 45 * - data-lenis-prevent op elk paneel houdt de scroll BINNEN een bericht 46 * ongemoeid. Dat werkt ook terwijl Lenis gestopt is, want die controle staat 47 * in zijn bron vóór de gestopt-controle. Nagekeken in de dist, niet gehoopt. 48 * - Bewegen tussen panelen gaat met lenis.scrollTo(..., { force: true }), want 49 * force is precies de uitzondering die een gestopte Lenis toch laat scrollen. 50 * 51 * Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop 52 * als de site dat instelt (reader_full_page). Buiten die modus verandert er 53 * niets -- dan is Lenis gewoon aan en scrol je vrij. 54 */ 55 /** 56 * PAGINAMODUS STAAT UIT -- maar de code blijft staan (Robin, 20-8: "weghalen 57 * maar bewaren, voor als ik het later weer wil"). 58 * 59 * Wat het is: elk bericht een paneel van een scherm dat zelf scrolt, met twee 60 * balken om ertussen te navigeren en de stroomscroll vastgezet. Gebouwd omdat 61 * het namaken van iOS-momentum niet lukte; in dit model doet het scrollgevoel er 62 * namelijk niet toe. Mobiel is nu terug op systeemscroll met snap, en dat is wat 63 * er ook stond voordat we dit probeerden. 64 * 65 * Aanzetten: deze constante op true. Dan komen de balken terug (CSS hangt aan 66 * body.is-paged), stopt Lenis de stroomscroll en scrollen de panelen zelf. 67 */ 68 const PAGINAMODUS = false; 69 70 function paginaModus() { 71 return PAGINAMODUS && OP_TOUCH.matches && document.body.dataset.feedView === 'reader'; 72 } 73 74 function panelen() { 75 return [...document.querySelectorAll('.feed-reader .read-post')]; 76 } 77 78 /** Welk paneel vult nu het scherm? Het eerste waarvan de bovenkant niet voorbij is. */ 79 function huidigIndex() { 80 const P = panelen(); 81 for (let i = 0; i < P.length; i++) { 82 if (P[i].getBoundingClientRect().top > 8) return Math.max(0, i - 1); 83 } 84 return Math.max(0, P.length - 1); 85 } 86 87 function gaNaar(i) { 88 const P = panelen(); 89 const doel = P[Math.max(0, Math.min(P.length - 1, i))]; 90 if (!doel) return; 91 if (lenis) lenis.scrollTo(doel, { force: true }); 92 else doel.scrollIntoView({ behavior: 'smooth', block: 'start' }); 93 setTimeout(zetBalken, 80); 94 } 95 96 /** 97 * Omhoog vanaf het EERSTE bericht brengt je naar de header. 98 * 99 * In paginamodus staat Lenis stil, dus de pagina scrolt niet meer met je vinger 100 * -- en dan is alles boven het eerste bericht onbereikbaar. Robin liep daar 101 * tegenaan (20-8): "ik kan niet meer terug scrollen naar de header". De 102 * omhoog-knop is daar de enige weg naartoe, dus die krijgt er een trede bij. 103 */ 104 function gaOmhoog() { 105 const i = huidigIndex(); 106 if (i > 0) { gaNaar(i - 1); return; } 107 if (lenis) lenis.scrollTo(0, { force: true }); 108 else window.scrollTo({ top: 0, behavior: 'smooth' }); 109 } 110 111 /** 112 * Welke balk hoort er te staan? Bovenaan de pagina is er niets boven je, dus dan 113 * geen bovenbalk. Buiten paginamodus staan ze allebei niet -- de CSS verbergt ze 114 * daar al, maar hidden houdt ze ook uit de toetsenbordvolgorde. 115 */ 116 function zetBalken() { 117 const boven = document.getElementById('read-prev'); 118 const onder = document.getElementById('read-next-nav'); 119 const aan = paginaModus(); 120 // De bovenbalk hoort NOOIT over de header te liggen. Hij verscheen al zodra je 121 // een paar pixels scrolde, en dekte dan de avatar, de omschrijving en de 122 // weergaveknoppen af (Robins schermafbeelding, 20-8). Nu komt hij pas als het 123 // eerste bericht de bovenrand van het scherm heeft bereikt -- dan is de header 124 // voorbij en is er ook echt iets om naar terug te gaan. 125 const eerste = panelen()[0]; 126 const headerNogInBeeld = eerste ? eerste.getBoundingClientRect().top > 4 : true; 127 if (boven) boven.hidden = !aan || headerNogInBeeld; 128 if (onder) onder.hidden = !aan; 129 if (onder && aan) onder.style.bottom = onderChroom() + 'px'; 130 } 131 132 /** 133 * Hoe hoog staat de onderrand van het scherm werkelijk vol? 134 * 135 * De onderbalk stond op een geraden 4.75rem boven de onderkant, en dan zweeft 136 * hij: soms een kier boven de speler, soms er half achter. De tabbalk en de 137 * mini-speler hebben allebei een eigen hoogte, ze stapelen op mobiel, en de 138 * speler komt en gaat. Dus meten in plaats van gokken: hoe ver ligt de BOVENKANT 139 * van het hoogste vaste element boven de onderrand van het venster? 140 */ 141 function onderChroom() { 142 // .bottom-tab-fab staat erbij omdat de Write-knop BOVEN de tabbalk uitsteekt: 143 // meet je alleen de balk, dan legt onze balk zich over die knop heen (Robins 144 // schermafbeelding, 20-8). 145 const kandidaten = ['.bottom-tab', '.bottom-tab-fab', '#pcms-audio-player']; 146 let hoogste = 0; 147 kandidaten.forEach((sel) => { 148 const el = document.querySelector(sel); 149 if (!el) return; 150 const st = getComputedStyle(el); 151 if (st.display === 'none' || st.visibility === 'hidden') return; 152 const r = el.getBoundingClientRect(); 153 if (r.height <= 0) return; 154 hoogste = Math.max(hoogste, window.innerHeight - r.top); 155 }); 156 return Math.round(hoogste); 157 } 158 159 function pasPaginaModusToe() { 160 const aan = paginaModus(); 161 document.body.classList.toggle('is-paged', aan); 162 panelen().forEach((a) => { 163 if (aan) a.setAttribute('data-lenis-prevent', ''); 164 else a.removeAttribute('data-lenis-prevent'); 165 }); 166 if (lenis) { if (aan) lenis.stop(); else lenis.start(); } 167 zetBalken(); 168 } 32 169 33 170 /** … … 73 210 const sel = window.getSelection && window.getSelection(); 74 211 if (sel && String(sel).trim()) return; 212 75 213 const slug = art.dataset.slug; 76 214 if (!slug) return; … … 115 253 116 254 let bezig = false; // loopt er een gebaar of een uitloop? 255 let ooitGescrold = false; // snappen begint UIT, zie zetSnappen() 117 256 let rustTimer = null; 118 257 … … 161 300 162 301 async function startLenis() { 302 // DESKTOP EN TOUCH ALLEBEI. Er heeft hier een tijd gestaan dat mobiel terug was 303 // op de systeemscroll; dat klopte niet meer met de regel hieronder, die 304 // syncTouch juist AANzet op touch. Reden dat Lenis ook op de telefoon meedraait: 305 // zijn snap heeft zijn scroll-gebeurtenissen nodig om te kunnen timen. 163 306 if (lenis || !(OP_DESKTOP.matches || OP_TOUCH.matches)) return; 164 307 const [L, S] = await Promise.all([ … … 167 310 ]); 168 311 lenis = new L.default({ 312 /** 313 * Wat Lenis MET RUST LAAT. 314 * 315 * In paginamodus staat Lenis stil en scrolt alleen het paneel van het 316 * bericht zelf. Dat regelde ik eerst met een data-lenis-prevent-attribuut 317 * dat de module op elk paneel zette -- maar dan hangt het scrollen af van of 318 * dat attribuut op tijd en op elk (ook later bijgeladen) paneel staat, en 319 * Robin kon binnen een lang bericht niet scrollen. 320 * 321 * Deze functie stelt dezelfde vraag zonder die afhankelijkheid: is dit een 322 * bericht-paneel? Dan bemoeit Lenis zich er niet mee en scrolt de browser 323 * het zelf -- wat op een telefoon precies is wat je wilt, want dat is de 324 * systeemscroll. 325 */ 326 prevent: (node) => !!(node && node.classList 327 && node.classList.contains('read-post') 328 && document.body.classList.contains('is-paged')), 169 329 // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in 170 330 // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en … … 179 339 // Alleen op touch overneemt hij het vingerscrollen; op desktop hoeft dat niet. 180 340 syncTouch: OP_TOUCH.matches, 181 // TOUCH-SPECIFIEK, want de standaardwaarden voelen op een telefoon traag. 341 // Twee touch-knoppen, en ze doen echt iets anders. Lenis rekent bij het 342 // LOSLATEN eenmalig een doel uit -- afstand = snelheid^touchInertiaExponent 343 // -- en kruipt daar dan naartoe met syncTouchLerp. 182 344 // 183 // Waar dat zit is precies aan te wijzen. In Lenis staat 184 // `lerp: d ? syncTouchLerp : 1`, waarbij d "de vinger is net losgelaten" 185 // betekent. Tijdens het SLEPEN is de lerp dus 1 -- de inhoud volgt je vinger 186 // exact, net als iOS, en daar is niets mis mee. De traagheid zit in de glijder 187 // NA het loslaten: die gebruikt syncTouchLerp, en dat staat standaard op 188 // 0.075. Dat dempt zo langzaam uit dat de pagina nog seconden naijlt. 345 // touchInertiaExponent hoe VER de flick draagt (standaard 1.7) 346 // syncTouchLerp hoe hard hij REMT (standaard 0.075) 189 347 // 190 // TWEE KNOPPEN, en ze doen iets anders -- dat verwarde ik eerst: 348 // Let op: `touchInertiaMultiplier` bestaat NIET in 1.3.26. Lenis slikt 349 // onbekende opties zonder fout, dus zo'n regel lijkt te werken en doet niets. 350 // Controleer een optienaam in src/assets/vendor/lenis.mjs voor je hem zet. 191 351 // 192 // touchInertiaMultiplier hoe VER een veeg je brengt 193 // syncTouchLerp hoe snel de glijder UITDEMPT 194 // 195 // Eerste ronde zette ik de multiplier op 25 (korter dan de standaard 35), 196 // omdat ik "te traag" las als "hij ijlt te lang na". Robin bedoelde het 197 // omgekeerde: een veeg moet je VERDER brengen, zoals op iOS -- daar draagt 198 // een flick een heel eind. Dus juist omhoog, ruim boven de standaard. 199 // 200 // De lerp gaat mee terug naar 0.1: tussen de trage standaard (0.075, dat 201 // seconden naijlt) en de kordate 0.15 in. Met een langere weg af te leggen 202 // mag de demping wat zachter, anders komt hij te abrupt tot stilstand. 203 // Waar we na een paar rondes proberen op uitkwamen, met de betekenis erbij 204 // omdat de tweede knop contra-intuitief is: 205 // 206 // touchInertiaMultiplier 70 hoe VER een veeg draagt (standaard 35). 207 // 25 en 45 waren allebei te kort. 208 // syncTouchLerp 0.05 hoe snel hij zijn doel BENADERT. Hoger = eerder 209 // aankomen en dus abrupt stoppen; LAGER = langer 210 // onderweg blijven. Robin: "te stroef bij het 211 // loslaten, mag echt een tijdje doorscrollen". 212 // Dus omlaag, niet omhoog -- 0.15 en 0.1 kapten 213 // de uitloop af. 214 // syncTouchLerp 0.25: VIER KEER de vorige waarde, en ruim boven de standaard 215 // van 0.075. Ik heb hem vier rondes lang de verkeerde kant op gedraaid omdat 216 // ik "stroef" las als "hij stopt te vroeg" -- maar het betekende STROPERIG. 217 // Een lage lerp benadert het doel langzaam en geeft dus een kruipende 218 // uitloop; hoog betekent er vlot naartoe. Met de multiplier op 70 blijft de 219 // AFSTAND groot, dus je gaat ver EN snel: dat is hoe een flick op iOS voelt. 220 // 221 // Kort samengevat, want dit is twee keer misgegaan: 222 // syncTouchLerp hoger = sneller weg, korter narollen 223 // multiplier hoger = verder komen 224 ...(OP_TOUCH.matches ? { syncTouchLerp: 0.25, touchInertiaMultiplier: 70 } : {}), 352 // Robin wilde langer en sneller uitrollen zonder remgevoel, dus de exponent 353 // gaat omhoog (een stevige flick draagt daarmee ruim vier tot zes keer zo 354 // ver) en de lerp gaat juist boven de standaard: hij legt die afstand vlot 355 // af in plaats van er stroperig naartoe te kruipen. 356 ...(OP_TOUCH.matches ? { syncTouchLerp: 0.09, touchInertiaExponent: 2.2 } : {}), 225 357 autoRaf: true, 226 358 }); 227 359 snap = new S.default(lenis, { 228 360 type: 'proximity', 229 distanceThreshold: '12%', // de vangzone, hier WEL instelbaar 361 // DE VANGZONE. Gemeten op dev: de voet van een bericht (de link naar de 362 // reacties) staat 138 tot 472 pixels boven de bovenkant van het volgende. 363 // Met de oude 12% was de zone 86 pixels op een venster van 720, dus hij hapte 364 // pas ruim onder die voet toe. 55% is ~400 pixels: de zone begint nu rond de 365 // voet, wat Robin vroeg (20-8) -- eerder al snappen, boven de reacties. 366 // Wil je het nog eerder, dan is dit het enige getal dat je hoeft te draaien. 367 distanceThreshold: '55%', 230 368 // Hoe lang na de laatste scrollbeweging hij mag vangen. De standaard is 500 231 369 // en dat voelt als te laat. Op touch nog korter dan op desktop, want daar … … 242 380 // "stroef bij het loslaten", en het werd er dan ook erger van. 200ms laat de 243 381 // uitloop eerst zijn werk doen. 244 debounce: OP_TOUCH.matches ? 200 : 60, 382 // Met een iOS-achtige uitloop duurt de staart lang; te kort wachten laat de 383 // snap midden in de vlucht ingrijpen. 150ms laat hem uitrollen en vangt dan. 384 debounce: OP_TOUCH.matches ? 150 : 60, 245 385 // Op touch korter: een telefoon vraagt om directer antwoord dan een muis, en 246 386 // de snap komt daar aan het eind van een lange uitloop -- dan mag hij kort. … … 256 396 const berichten = [...document.querySelectorAll('.feed-reader .read-post')]; 257 397 berichten.slice(0, -1).forEach((a) => snap.addElement(a, { align: 'start' })); 398 399 // ALLEEN VOORUIT VANGEN. Dit hoort bij de ruime zone hierboven en is niet 400 // optioneel: lenis/snap kiest het DICHTSTBIJZIJNDE punt en zijn drempel geldt 401 // naar twee kanten. Scroll je een lang bericht in en stop je 300 pixels onder 402 // de bovenkant, dan is die bovenkant het dichtstbij -- en met een zone van 400 403 // wordt je teruggetrokken. Dat is precies de klacht "ik kan niet scrollen 404 // binnen een lang bericht", en met de oude zone van 86 pixels viel het alleen 405 // niet op. 406 // 407 // Dus: een doel ACHTER je slaan we over. Ligt er een punt voor je binnen de 408 // zone, dan gaan we daarheen; anders gebeurt er niets en scrol je vrij door. 409 // Zo verruimt de zone alleen de kant waar hij bedoeld is. 410 // 411 // Waarom niet gewoon de drempel? Omdat lenis/snap alleen de dichtstbijzijnde 412 // kandidaat beoordeelt: zonder deze omleiding houdt de zone vooruit op bij de 413 // helft van de afstand tussen twee berichten, hoe groot je de drempel ook zet. 414 const echtGaNaar = snap.goTo.bind(snap); 415 snap.goTo = (index) => { 416 const punten = snap.computeSnaps(); // zelfde volgorde als goTo intern gebruikt 417 const nu = lenis.scroll; 418 const doel = punten[index]; 419 if (doel && doel.value > nu + 2) { echtGaNaar(index); return; } 420 let vooruit = -1; 421 punten.forEach((punt, i) => { 422 if (punt.value > nu + 2 && (vooruit < 0 || punt.value < punten[vooruit].value)) vooruit = i; 423 }); 424 if (vooruit < 0) return; 425 if (punten[vooruit].value - nu <= snap.distanceThreshold) echtGaNaar(vooruit); 426 }; 258 427 // Native snappen uit: twee mechanismen op dezelfde scroller vechten. 259 428 document.documentElement.style.scrollSnapType = 'none'; … … 272 441 function zetSnappen(aan) { 273 442 if (snap) { if (aan) snap.start(); else snap.stop(); return; } 443 // Zonder Lenis (mobiel) zetten we de CSS-eigenschap om. Hij staat bij het 444 // laden UIT -- dat is de standaardstand van deze variabele -- zodat de browser 445 // niet meteen naar het eerste bericht springt en de header wegvalt. Pas je 446 // eerste gebaar naar beneden zet hem aan. 274 447 const el = document.documentElement; 275 448 const wil = aan ? '' : 'none'; … … 314 487 315 488 export function init() { 489 // EERST OPRUIMEN, en pas daarna terugvallen als er geen leesstroom is. 490 // 491 // Dit stond andersom, en dat brak het scrollen op de HELE site: navigeerde je 492 // van Lezen naar het beheer, dan viel init() bij de ontbrekende leesstroom 493 // meteen terug -- en bleef Lenis leven EN gestopt (lenis.stop() vangt wiel en 494 // vinger af). Daarna scrolde niets meer, ook niet op pagina's die met Lezen 495 // niets te maken hebben. Robins melding (20-8): "scrollen werkt nu nergens ook 496 // niet in admin panels". 497 // 498 // Opruimen hoort dus bij het VERLATEN van de weergave, niet bij het opzetten 499 // ervan. init() draait bij elke paginawissel, dus dit is de plek. 500 stopLenis(); 501 document.body.classList.remove('is-paged'); 502 document.querySelectorAll('[data-lenis-prevent].read-post') 503 .forEach((a) => a.removeAttribute('data-lenis-prevent')); 504 316 505 const s = document.getElementById('read-stream'); 317 506 if (!s) return; … … 326 515 // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet 327 516 // hij niets, maar hij springt niet in beeld bij het laden. 328 knop = document.getElementById('read-top'); 329 if (knop) { 330 knop.onclick = naarBoven; 331 toonKnop(knop); 332 } 517 // De terug-naar-boven-knop staat er tijdelijk uit (Robin, 20-8) -- in 518 // paginamodus doet de bovenbalk dat werk al. De code blijft staan zodat hij 519 // met een regel terug is. 520 knop = null; 521 const oudeKnop = document.getElementById('read-top'); 522 if (oudeKnop) oudeKnop.hidden = true; 333 523 334 524 if (opScroll) { … … 343 533 // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar 344 534 // levend; pas als het stil blijft mag een nieuwe richting gelden. 345 opScroll = () => { if (knop) toonKnop(knop); planRust(); }; 535 opScroll = () => { 536 if (knop) toonKnop(knop); 537 planRust(); 538 if (document.body.classList.contains('is-paged')) zetBalken(); 539 }; 346 540 window.addEventListener('scroll', opScroll, { passive: true }); 347 541 window.addEventListener('scrollend', rustNu); … … 352 546 window.addEventListener('keydown', opToets); 353 547 548 const balkBoven = document.getElementById('read-prev'); 549 const balkOnder = document.getElementById('read-next-nav'); 550 if (balkBoven) balkBoven.onclick = gaOmhoog; 551 if (balkOnder) balkOnder.onclick = () => gaNaar(huidigIndex() + 1); 552 354 553 // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw 355 554 // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en 356 555 // wegnavigeren hoort hem op te ruimen. 357 stopLenis(); 358 if (document.body.dataset.feedView === 'reader') startLenis(); 556 if (document.body.dataset.feedView === 'reader') startLenis().then(pasPaginaModusToe); 557 else pasPaginaModusToe(); 558 559 359 560 } 360 561 -
src/services/i18n.js
rc581e5e r22d2dba5 33 33 'switch.solo': 'Solo', 34 34 'switch.circle': 'Cirkels', 35 'switch.grid': 'Grid', 'switch.reader': 'Lezen', 'switch.timeline': 'Tijdlijn', 'read.to_top': 'Terug naar boven', 'read.pinned': 'Vastgepind', ' asite.reader_full_page': 'Lezen op desktop: elk bericht een eigen scherm (op mobiel altijd)',35 'switch.grid': 'Grid', 'switch.reader': 'Lezen', 'switch.timeline': 'Tijdlijn', 'read.to_top': 'Terug naar boven', 'read.pinned': 'Vastgepind', 'read.next': 'Volgende', 'read.prev': 'Vorige bericht', 'read.nav': 'Door de berichten', 'read.hint': 'Tik boven- of onderaan om een bericht terug of verder', 'asite.reader_full_page': 'Lezen op desktop: elk bericht een eigen scherm (op mobiel altijd)', 36 36 'asite.feed_alt': 'Tweede weergave', 'asite.feed_alt_reader': 'Lezen', 'asite.feed_alt_timeline': 'Tijdlijn', 'asite.feed_alt_auto': 'Lezen op mobiel, Tijdlijn op desktop', 37 37 'switch.reader_solo_only': 'Lezen kan alleen in Solo — in de cirkel staan berichten van anderen', … … 1049 1049 'switch.solo': 'Solo', 1050 1050 'switch.circle': 'Circles', 1051 'switch.grid': 'Grid', 'switch.reader': 'Reader', 'switch.timeline': 'Timeline', 'read.to_top': 'Back to top', 'read.pinned': 'Pinned', ' asite.reader_full_page': 'Reader on desktop: one screen per post (always on mobile)',1051 'switch.grid': 'Grid', 'switch.reader': 'Reader', 'switch.timeline': 'Timeline', 'read.to_top': 'Back to top', 'read.pinned': 'Pinned', 'read.next': 'Next', 'read.prev': 'Previous post', 'read.nav': 'Through the posts', 'read.hint': 'Tap the top or bottom to move a post back or on', 'asite.reader_full_page': 'Reader on desktop: one screen per post (always on mobile)', 1052 1052 'asite.feed_alt': 'Second view', 'asite.feed_alt_reader': 'Reader', 'asite.feed_alt_timeline': 'Timeline', 'asite.feed_alt_auto': 'Reader on mobile, Timeline on desktop', 1053 1053 'switch.reader_solo_only': 'Reader is Solo only — a circle shows other people\u2019s posts', … … 2056 2056 'switch.solo': 'Solo', 2057 2057 'switch.circle': 'Zirkel', 2058 'switch.grid': 'Raster', 'switch.reader': 'Lesen', 'switch.timeline': 'Zeitleiste', 'read.to_top': 'Nach oben', 'read.pinned': 'Angeheftet', ' asite.reader_full_page': 'Lesen am Desktop: ein Bildschirm pro Beitrag (mobil immer)',2058 'switch.grid': 'Raster', 'switch.reader': 'Lesen', 'switch.timeline': 'Zeitleiste', 'read.to_top': 'Nach oben', 'read.pinned': 'Angeheftet', 'read.next': 'Weiter', 'read.prev': 'Vorheriger Beitrag', 'read.nav': 'Durch die Beitraege', 'read.hint': 'Oben oder unten tippen fuer einen Beitrag zurueck oder weiter', 'asite.reader_full_page': 'Lesen am Desktop: ein Bildschirm pro Beitrag (mobil immer)', 2059 2059 'asite.feed_alt': 'Zweite Ansicht', 'asite.feed_alt_reader': 'Lesen', 'asite.feed_alt_timeline': 'Zeitleiste', 'asite.feed_alt_auto': 'Lesen mobil, Zeitleiste am Desktop', 2060 2060 'switch.reader_solo_only': 'Lesen gibt es nur in Solo — im Zirkel stehen Beitr\u00e4ge anderer', -
src/views/pages/home.ejs
rc581e5e r22d2dba5 70 70 is als de module niet laadt -- dan doet hij niets, maar hij springt ook niet 71 71 opeens in beeld. Verschijnt pas als je ver genoeg bent (mod/read.js). %> 72 <%# Een bericht omhoog of omlaag. Alleen in paginamodus zichtbaar (CSS). %> 73 <%# Twee echte balken over de volle breedte. Tikzones zonder zichtbaar element 74 werkten niet: de onderste strook ligt onder de mini-speler en de tabbalk, 75 en die vangen de tik voordat de leesstroom hem ziet. Een balk kan daar wel 76 bovenop. %> 77 <button type="button" class="read-bar read-bar--top" id="read-prev" aria-label="<%= t('read.prev') %>"> 78 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="18 15 12 9 6 15"/></svg> 79 </button> 80 <button type="button" class="read-bar read-bar--bottom" id="read-next-nav" aria-label="<%= t('read.next') %>"> 81 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg> 82 </button> 72 83 <button type="button" id="read-top" class="read-top" aria-label="<%= t('read.to_top') %>" title="<%= t('read.to_top') %>"> 73 84 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg> -
src/views/shell.ejs
rc581e5e r22d2dba5 222 222 223 223 <!-- v9 stylesheet (full palette system) --> 224 <link rel="stylesheet" href="/assets/css/style.css?v= 84">224 <link rel="stylesheet" href="/assets/css/style.css?v=103"> 225 225 <script> 226 226 /* iOS safe-area, built by hand. env(safe-area-inset-top) resolves to 0 on this iOS in … … 505 505 // Eén nummer voor de hele map. Te vaak bumpen kost één download; te weinig 506 506 // bumpen kost een bugfix die nooit aankomt. 507 var MOD_V = 29;507 var MOD_V = 46; 508 508 509 509 // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)