Changes in / [bc9ef3b:96a99bf] in Klonkt
- Location:
- src
- Files:
-
- 5 edited
-
assets/css/style.css (modified) (2 diffs)
-
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
rbc9ef3b r96a99bf 2544 2544 scroll-margin-top: 2rem; 2545 2545 } 2546 /* Het laatste bericht krijgt geen snappunt: zijn bovenkant is niet te bereiken 2547 (er zit te weinig pagina onder), en de browser snapt dan naar de dichtstbij- 2548 zijnde bereikbare positie -- de bodem. Gemeten: snappunt 2548, maxScroll 2356. */ 2549 .feed-reader .read-post:last-child .read-anker { scroll-snap-align: none; } 2546 /* HET LAATSTE BERICHT SNAPT OOK (Robin, 21-8). Dat kon eerst niet: zijn 2547 bovenkant was onbereikbaar -- er zat te weinig pagina onder om hem naar de 2548 bovenrand te brengen (gemeten: snappunt 2548, maxScroll 2356), en de browser 2549 viel dan terug op de dichtstbijzijnde bereikbare positie: de bodem. Het 2550 snappunt weghalen was de vorige oplossing; dit is de echte. 2551 Een volle schermhoogte op het LAATSTE bericht maakt die bovenkant precies 2552 bereikbaar. Korter dan het scherm: het bericht wordt even hoog en het snappunt 2553 valt exact op maxScroll. Langer dan het scherm: dan was het al haalbaar en 2554 doet min-height niets. 2555 100lvh en niet 100svh: op een telefoon met een verdwijnende adresbalk is het 2556 zichtbare scherm soms de GROTE viewport, en dan komt svh net tekort. 2557 Zichtbaar kost dit niets: berichten hebben geen kader, alleen een scheidings- 2558 lijn erboven, dus het laatste bericht krijgt hooguit witruimte eronder. */ 2559 .feed-reader .read-post:last-child { min-height: 100lvh; } 2550 2560 2551 2561 /* ELK BERICHT EEN EIGEN SCHERM, als de site dat wil (Instellingen -> Weergave). … … 2556 2566 -- want welke van de twee prettiger leest hangt af van wat je schrijft. */ 2557 2567 body[data-reader-pages="1"] .feed-reader .read-post { min-height: 100svh; } 2568 /* OP MOBIEL ALTIJD, wat de instelling ook zegt (Robin, 20-8). Op een telefoon is 2569 een bericht per scherm de vorm die werkt -- je veegt van bericht naar bericht. 2570 Op desktop blijft het een keuze, want daar lees je in een kolom naast veel 2571 witruimte en is een doorlopende stroom vaak prettiger. */ 2572 @media (hover: none) and (pointer: coarse) { 2573 /* Op mobiel draait Lezen in paginamodus (body.is-paged), en die zet hieronder 2574 een vaste hoogte. Deze regel is de terugval voor het moment vóór de module 2575 geladen is, zodat de berichten niet eerst als losse stroom in beeld 2576 springen. */ 2577 .feed-reader .read-post { min-height: 100svh; } 2578 /* Geen native snap op mobiel: dat doet lenis/snap, met een instelbare vangzone 2579 en duur. Twee snapsystemen op dezelfde scroller vechten. */ 2580 html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: none; } 2581 } 2582 2583 /* -------- PAGINAMODUS -------- 2584 Elk bericht is een paneel van precies een scherm dat ZELF scrollt; tussen 2585 berichten ga je met knoppen. Daarmee doet het scrollgevoel er niet meer toe: 2586 geen momentum om na te bouwen en geen snap die vangt. 2587 De stroom zelf vangt geen vingers meer -- dat regelt lenis.stop() in 2588 mod/read.js -- en data-lenis-prevent op het paneel houdt de scroll BINNEN een 2589 bericht ongemoeid (die controle staat in Lenis vóór de gestopt-controle). 2590 Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop 2591 als de site dat zo instelt. */ 2592 body.is-paged .feed-reader .read-post { 2593 height: 100svh; 2594 min-height: 0; 2595 overflow-y: auto; 2596 overscroll-behavior: contain; /* raakt het einde, dan niet doorschuiven */ 2597 scroll-snap-align: none; 2598 /* Ruimte voor de twee balken, zodat er nooit tekst onder een knop verdwijnt. 2599 Het verloop laat een regel wel zacht wegvagen, maar wat je moet KUNNEN 2600 lezen hoort er niet achter te komen -- vandaar padding en niet alleen die 2601 fade. --read-balk staat hieronder bij .read-nav, zodat de twee niet uit 2602 elkaar kunnen lopen. */ 2603 padding-top: calc(var(--read-balk) + .5rem); 2604 padding-bottom: calc(var(--read-balk) + .5rem); 2605 /* BOVENAAN beginnen, niet gecentreerd. Ik had de inhoud een ronde lang 2606 verticaal gecentreerd om het gat onder een kort bericht te verdelen, maar 2607 dan hangt zo'n bericht halverwege het scherm en begint niet elk paneel op 2608 dezelfde plek -- dat leest onrustig. Robin ving het (20-8). Elk bericht 2609 begint nu bovenaan, en de ruimte die overblijft valt onderaan. */ 2610 } 2611 body.is-paged .feed-reader .read-anker { scroll-snap-align: none; } 2612 2613 /* DE DYNAMIC ISLAND, en met opzet alleen op iOS (Robin, 20-8: "op android is het 2614 een ander verhaal"). 2615 In paginamodus vult een bericht precies het scherm, dus de eerste regel komt 2616 bovenaan -- en op een iPhone ligt daar de island of de notch overheen. 2617 env(safe-area-inset-top) geeft daar de vrije ruimte; viewport-fit=cover staat 2618 al in de viewport-meta, dus die waarde is echt. 2619 De @supports-regel is de iOS-Safari-toets: -webkit-touch-callout bestaat alleen 2620 daar. Op Android is env() vaak 0 of betekent het iets anders (camera-uitsparing, 2621 statusbalk die al meegerekend is), en dan zou dit een gat bovenin maken dat er 2622 niet hoort. Vandaar afgeschermd in plaats van env() overal toepassen. 2623 De hoogte krimpt mee, anders duwt de padding het paneel groter dan het scherm 2624 en krijg je alsnog scroll tussen de berichten. */ 2625 @supports (-webkit-touch-callout: none) { 2626 body.is-paged .feed-reader .read-post { 2627 padding-top: calc(var(--read-balk) + .5rem + env(safe-area-inset-top, 0px)); 2628 height: calc(100svh - env(safe-area-inset-top, 0px)); 2629 } 2630 /* De bovenbalk houdt zijn knop al vrij van de island via padding-top 2631 hierboven; hier hoeft niets extra's meer. */ 2632 } 2633 2634 /* De knop onderaan een bericht: naar het volgende. */ 2635 2636 2637 /* Omhoog en omlaag: twee balken, boven en onder, die BLIJVEN staan terwijl je in 2638 een bericht scrolt (Robin, 20-8). Het verloop van paginakleur naar doorzichtig 2639 laat de tekst eronderdoor wegvagen -- zonder dat zou een regel halverwege hard 2640 achter de knop verdwijnen en oogt het als een afgeknipte pagina. 2641 De BALK vangt geen aanrakingen (pointer-events: none), alleen de knop zelf -- 2642 anders zou een strook van 5rem boven- en onderaan je scroll opeten. */ 2643 /* TWEE BALKEN over de volle breedte, boven en onder. Eerder waren dit losse 2644 cirkels (te rommelig naast de tabbalk en de speler) en daarna onzichtbare 2645 tikzones -- maar die werkten niet, omdat de onderste strook onder de 2646 mini-speler en de tabbalk ligt en die de tik opvangen. Een balk met een eigen 2647 z-index kan daar wel overheen. 2648 Slank gehouden: ze eten hooguit 2.6rem aan elke kant, en het paneel houdt daar 2649 al ruimte voor vrij. */ 2650 :root { --read-balk: 2.6rem; } 2651 .read-bar { 2652 position: fixed; left: 0; right: 0; z-index: 1060; /* boven tabbalk (1050) */ 2653 display: flex; align-items: center; 2654 /* Pijlen LINKS in de balk, niet gecentreerd (Robin, 20-8). */ 2655 justify-content: flex-start; 2656 padding-left: max(1.1rem, env(safe-area-inset-left, 0px)); 2657 box-sizing: border-box; 2658 height: var(--read-balk); 2659 border: 0; 2660 border-top: 1px solid transparent; 2661 background: color-mix(in srgb, var(--paper) 90%, var(--ink)); 2662 backdrop-filter: blur(14px) saturate(140%); 2663 -webkit-backdrop-filter: blur(14px) saturate(140%); 2664 color: var(--ink); 2665 cursor: pointer; 2666 transition: opacity .15s ease; 2667 } 2668 .read-bar--top { 2669 top: 0; 2670 height: calc(var(--read-balk) + env(safe-area-inset-top, 0px)); 2671 padding-top: env(safe-area-inset-top, 0px); 2672 border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); 2673 } 2674 .read-bar--bottom { 2675 /* De waarde komt uit mod/read.js: die meet hoe hoog de tabbalk en de 2676 mini-speler werkelijk staan. Geraden rem-waarden lieten hem zweven -- een 2677 kier boven de speler, of er half achter. Dit is de terugval voor de eerste 2678 frame en voor als de module niet laadt. */ 2679 bottom: 0; 2680 border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); 2681 } 2682 .read-bar:active { background: color-mix(in srgb, var(--paper) 78%, var(--ink)); } 2683 .read-bar[hidden], body:not(.is-paged) .read-bar { display: none; } 2558 2684 2559 2685 /* Een lijn tussen twee berichten: zonder grens is een stroom een lange brij. */ -
src/assets/js/mod/read.js
rbc9ef3b r96a99bf 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 … … 143 282 */ 144 283 const OP_DESKTOP = window.matchMedia('(hover: hover) and (pointer: fine)'); 284 // PROEF (20-8): Lenis ook op touch. Robin meldde op mobiel drie dingen -- de 285 // header niet zichtbaar bij laden, snappen dat soms te ver gaat, en horizontaal 286 // kunnen scrollen. De eerste twee komen van de NATIVE snap: die vangt al bij het 287 // laden, en zijn uitloop vliegt bij een flick over een grens heen. Lenis zet de 288 // native snap uit en doet het zelf, dus die twee kunnen ermee verdwijnen. 289 // 290 // De prijs is echt: syncTouch betekent dat Lenis het VINGERSCROLLEN overneemt 291 // van iOS -- momentum, rubber-band en het wegschuiven van de adresbalk worden 292 // dan een benadering in JavaScript. De makers waarschuwen daar zelf voor op 293 // iOS < 16. Dit staat er dus als proef, niet als besluit: voelt het niet goed, 294 // dan is OP_TOUCH weer false zetten de hele terugweg. 295 const OP_TOUCH = window.matchMedia('(hover: none) and (pointer: coarse)'); 145 296 const VENDOR_V = 1; // ophogen als de bestanden in /assets/js/vendor wijzigen 146 297 … … 149 300 150 301 async function startLenis() { 151 if (lenis || !OP_DESKTOP.matches) return; 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. 306 if (lenis || !(OP_DESKTOP.matches || OP_TOUCH.matches)) return; 152 307 const [L, S] = await Promise.all([ 153 308 import(`/assets/vendor/lenis.mjs?v=${VENDOR_V}`), … … 155 310 ]); 156 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')), 157 329 // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in 158 330 // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en … … 165 337 smoothWheel: true, 166 338 lerp: 0.2, 167 syncTouch: false, // op touch blijft alles van het systeem 339 // Alleen op touch overneemt hij het vingerscrollen; op desktop hoeft dat niet. 340 syncTouch: OP_TOUCH.matches, 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. 344 // 345 // touchInertiaExponent hoe VER de flick draagt (standaard 1.7) 346 // syncTouchLerp hoe hard hij REMT (standaard 0.075) 347 // 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. 351 // 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 } : {}), 168 357 autoRaf: true, 169 358 }); 170 359 snap = new S.default(lenis, { 171 360 type: 'proximity', 172 distanceThreshold: '12%', // de vangzone, hier WEL instelbaar 173 debounce: 60, // niet de standaard 500: dat voelt als te laat 174 duration: 0.4, // in totaal onder de halve seconde 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%', 368 // Hoe lang na de laatste scrollbeweging hij mag vangen. De standaard is 500 369 // en dat voelt als te laat. Op touch nog korter dan op desktop, want daar 370 // eindigt een veeg in een lange, trage staart -- en juist dan wil je dat de 371 // snap er snel bij is in plaats van te wachten tot de laatste pixel stil ligt. 372 // 373 // LET OP DE SPANNING: een vloeiendere uitloop (lagere syncTouchLerp) maakt 374 // die staart langer, en stelt de snap dus uit. Deze twee getallen houden 375 // elkaar in evenwicht; draai je aan de een, kijk dan ook naar de ander. 376 // OP TOUCH JUIST LANG, en dat is het omgekeerde van wat ik vorige ronde deed. 377 // Tussen je vinger loslaten en het op gang komen van de uitloop zit een korte 378 // stilte in de scrollbeweging. Met 25ms viel de snap precies in dat gat: hij 379 // greep op het moment van loslaten en knipte de veeg af -- wat aanvoelt als 380 // "stroef bij het loslaten", en het werd er dan ook erger van. 200ms laat de 381 // uitloop eerst zijn werk doen. 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, 385 // Op touch korter: een telefoon vraagt om directer antwoord dan een muis, en 386 // de snap komt daar aan het eind van een lange uitloop -- dan mag hij kort. 387 duration: OP_TOUCH.matches ? 0.18 : 0.4, 175 388 // Vlot weg, dan steeds langzamer aankomen (Robin, 20-8). easeOutQuart: op de 176 389 // helft van de tijd is 94% van de weg af, en de rest dempt zacht uit. … … 179 392 easing: (t) => 1 - Math.pow(1 - t, 4), 180 393 }); 181 // Het snappunt is de bovenkant van elk bericht. Het laatste doet niet mee: 182 // zijn bovenkant is niet te bereiken, er zit te weinig pagina onder. 394 // Het snappunt is de bovenkant van elk bericht -- ook van het laatste. Dat 395 // deed eerder niet mee omdat zijn bovenkant onbereikbaar was; sinds het 396 // laatste bericht in CSS een volle schermhoogte krijgt, kan hij wel. 183 397 const berichten = [...document.querySelectorAll('.feed-reader .read-post')]; 184 berichten.slice(0, -1).forEach((a) => snap.addElement(a, { align: 'start' })); 398 berichten.forEach((a) => snap.addElement(a, { align: 'start' })); 399 400 // ALLEEN VOORUIT VANGEN. Dit hoort bij de ruime zone hierboven en is niet 401 // optioneel: lenis/snap kiest het DICHTSTBIJZIJNDE punt en zijn drempel geldt 402 // naar twee kanten. Scroll je een lang bericht in en stop je 300 pixels onder 403 // de bovenkant, dan is die bovenkant het dichtstbij -- en met een zone van 400 404 // wordt je teruggetrokken. Dat is precies de klacht "ik kan niet scrollen 405 // binnen een lang bericht", en met de oude zone van 86 pixels viel het alleen 406 // niet op. 407 // 408 // Dus: een doel ACHTER je slaan we over. Ligt er een punt voor je binnen de 409 // zone, dan gaan we daarheen; anders gebeurt er niets en scrol je vrij door. 410 // Zo verruimt de zone alleen de kant waar hij bedoeld is. 411 // 412 // Waarom niet gewoon de drempel? Omdat lenis/snap alleen de dichtstbijzijnde 413 // kandidaat beoordeelt: zonder deze omleiding houdt de zone vooruit op bij de 414 // helft van de afstand tussen twee berichten, hoe groot je de drempel ook zet. 415 const echtGaNaar = snap.goTo.bind(snap); 416 snap.goTo = (index) => { 417 const punten = snap.computeSnaps(); // zelfde volgorde als goTo intern gebruikt 418 const nu = lenis.scroll; 419 const doel = punten[index]; 420 if (doel && doel.value > nu + 2) { echtGaNaar(index); return; } 421 let vooruit = -1; 422 punten.forEach((punt, i) => { 423 if (punt.value > nu + 2 && (vooruit < 0 || punt.value < punten[vooruit].value)) vooruit = i; 424 }); 425 if (vooruit < 0) return; 426 if (punten[vooruit].value - nu <= snap.distanceThreshold) echtGaNaar(vooruit); 427 }; 185 428 // Native snappen uit: twee mechanismen op dezelfde scroller vechten. 186 429 document.documentElement.style.scrollSnapType = 'none'; … … 199 442 function zetSnappen(aan) { 200 443 if (snap) { if (aan) snap.start(); else snap.stop(); return; } 444 // Zonder Lenis (mobiel) zetten we de CSS-eigenschap om. Hij staat bij het 445 // laden UIT -- dat is de standaardstand van deze variabele -- zodat de browser 446 // niet meteen naar het eerste bericht springt en de header wegvalt. Pas je 447 // eerste gebaar naar beneden zet hem aan. 201 448 const el = document.documentElement; 202 449 const wil = aan ? '' : 'none'; … … 241 488 242 489 export function init() { 490 // EERST OPRUIMEN, en pas daarna terugvallen als er geen leesstroom is. 491 // 492 // Dit stond andersom, en dat brak het scrollen op de HELE site: navigeerde je 493 // van Lezen naar het beheer, dan viel init() bij de ontbrekende leesstroom 494 // meteen terug -- en bleef Lenis leven EN gestopt (lenis.stop() vangt wiel en 495 // vinger af). Daarna scrolde niets meer, ook niet op pagina's die met Lezen 496 // niets te maken hebben. Robins melding (20-8): "scrollen werkt nu nergens ook 497 // niet in admin panels". 498 // 499 // Opruimen hoort dus bij het VERLATEN van de weergave, niet bij het opzetten 500 // ervan. init() draait bij elke paginawissel, dus dit is de plek. 501 stopLenis(); 502 document.body.classList.remove('is-paged'); 503 document.querySelectorAll('[data-lenis-prevent].read-post') 504 .forEach((a) => a.removeAttribute('data-lenis-prevent')); 505 243 506 const s = document.getElementById('read-stream'); 244 507 if (!s) return; … … 253 516 // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet 254 517 // hij niets, maar hij springt niet in beeld bij het laden. 255 knop = document.getElementById('read-top'); 256 if (knop) { 257 knop.onclick = naarBoven; 258 toonKnop(knop); 259 } 518 // De terug-naar-boven-knop staat er tijdelijk uit (Robin, 20-8) -- in 519 // paginamodus doet de bovenbalk dat werk al. De code blijft staan zodat hij 520 // met een regel terug is. 521 knop = null; 522 const oudeKnop = document.getElementById('read-top'); 523 if (oudeKnop) oudeKnop.hidden = true; 260 524 261 525 if (opScroll) { … … 270 534 // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar 271 535 // levend; pas als het stil blijft mag een nieuwe richting gelden. 272 opScroll = () => { if (knop) toonKnop(knop); planRust(); }; 536 opScroll = () => { 537 if (knop) toonKnop(knop); 538 planRust(); 539 if (document.body.classList.contains('is-paged')) zetBalken(); 540 }; 273 541 window.addEventListener('scroll', opScroll, { passive: true }); 274 542 window.addEventListener('scrollend', rustNu); … … 279 547 window.addEventListener('keydown', opToets); 280 548 549 const balkBoven = document.getElementById('read-prev'); 550 const balkOnder = document.getElementById('read-next-nav'); 551 if (balkBoven) balkBoven.onclick = gaOmhoog; 552 if (balkOnder) balkOnder.onclick = () => gaNaar(huidigIndex() + 1); 553 281 554 // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw 282 555 // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en 283 556 // wegnavigeren hoort hem op te ruimen. 284 stopLenis(); 285 if (document.body.dataset.feedView === 'reader') startLenis(); 557 if (document.body.dataset.feedView === 'reader') startLenis().then(pasPaginaModusToe); 558 else pasPaginaModusToe(); 559 560 286 561 } 287 562 -
src/services/i18n.js
rbc9ef3b r96a99bf 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: elk bericht een eigen scherm',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: one screen per post',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: ein Bildschirm pro Beitrag',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
rbc9ef3b r96a99bf 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
rbc9ef3b r96a99bf 222 222 223 223 <!-- v9 stylesheet (full palette system) --> 224 <link rel="stylesheet" href="/assets/css/style.css?v= 83">224 <link rel="stylesheet" href="/assets/css/style.css?v=104"> 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 = 21;507 var MOD_V = 47; 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)