Changes in / [96a99bf:bc9ef3b] in Klonkt


Ignore:
Location:
src
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/assets/css/style.css

    r96a99bf rbc9ef3b  
    25442544    scroll-margin-top: 2rem;
    25452545}
    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; }
     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; }
    25602550
    25612551/* ELK BERICHT EEN EIGEN SCHERM, als de site dat wil (Instellingen -> Weergave).
     
    25662556   -- want welke van de twee prettiger leest hangt af van wat je schrijft. */
    25672557body[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; }
    26842558
    26852559/* Een lijn tussen twee berichten: zonder grens is een stroom een lange brij. */
  • src/assets/js/mod/read.js

    r96a99bf rbc9ef3b  
    3030 * scrollen, en cmd/ctrl-klik hoort de browser zelf af te handelen.
    3131 */
    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 }
    16932
    17033/**
     
    21073  const sel = window.getSelection && window.getSelection();
    21174  if (sel && String(sel).trim()) return;
    212 
    21375  const slug = art.dataset.slug;
    21476  if (!slug) return;
     
    253115
    254116let bezig = false;          // loopt er een gebaar of een uitloop?
    255 let ooitGescrold = false;   // snappen begint UIT, zie zetSnappen()
    256117let rustTimer = null;
    257118
     
    282143 */
    283144const 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)');
    296145const VENDOR_V = 1;   // ophogen als de bestanden in /assets/js/vendor wijzigen
    297146
     
    300149
    301150async 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.
    306   if (lenis || !(OP_DESKTOP.matches || OP_TOUCH.matches)) return;
     151  if (lenis || !OP_DESKTOP.matches) return;
    307152  const [L, S] = await Promise.all([
    308153    import(`/assets/vendor/lenis.mjs?v=${VENDOR_V}`),
     
    310155  ]);
    311156  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')),
    329157    // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in
    330158    // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en
     
    337165    smoothWheel: true,
    338166    lerp: 0.2,
    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 } : {}),
     167    syncTouch: false,   // op touch blijft alles van het systeem
    357168    autoRaf: true,
    358169  });
    359170  snap = new S.default(lenis, {
    360171    type: 'proximity',
    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,
     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
    388175    // Vlot weg, dan steeds langzamer aankomen (Robin, 20-8). easeOutQuart: op de
    389176    // helft van de tijd is 94% van de weg af, en de rest dempt zacht uit.
     
    392179    easing: (t) => 1 - Math.pow(1 - t, 4),
    393180  });
    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.
     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.
    397183  const berichten = [...document.querySelectorAll('.feed-reader .read-post')];
    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   };
     184  berichten.slice(0, -1).forEach((a) => snap.addElement(a, { align: 'start' }));
    428185  // Native snappen uit: twee mechanismen op dezelfde scroller vechten.
    429186  document.documentElement.style.scrollSnapType = 'none';
     
    442199function zetSnappen(aan) {
    443200  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.
    448201  const el = document.documentElement;
    449202  const wil = aan ? '' : 'none';
     
    488241
    489242export 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 
    506243  const s = document.getElementById('read-stream');
    507244  if (!s) return;
     
    516253  // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet
    517254  // hij niets, maar hij springt niet in beeld bij het laden.
    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;
     255  knop = document.getElementById('read-top');
     256  if (knop) {
     257    knop.onclick = naarBoven;
     258    toonKnop(knop);
     259  }
    524260
    525261  if (opScroll) {
     
    534270  // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar
    535271  // levend; pas als het stil blijft mag een nieuwe richting gelden.
    536   opScroll = () => {
    537     if (knop) toonKnop(knop);
    538     planRust();
    539     if (document.body.classList.contains('is-paged')) zetBalken();
    540   };
     272  opScroll = () => { if (knop) toonKnop(knop); planRust(); };
    541273  window.addEventListener('scroll', opScroll, { passive: true });
    542274  window.addEventListener('scrollend', rustNu);
     
    547279  window.addEventListener('keydown', opToets);
    548280
    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 
    554281  // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw
    555282  // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en
    556283  // wegnavigeren hoort hem op te ruimen.
    557   if (document.body.dataset.feedView === 'reader') startLenis().then(pasPaginaModusToe);
    558   else pasPaginaModusToe();
    559 
    560 
     284  stopLenis();
     285  if (document.body.dataset.feedView === 'reader') startLenis();
    561286}
    562287
  • src/services/i18n.js

    r96a99bf rbc9ef3b  
    3333    'switch.solo': 'Solo',
    3434    'switch.circle': 'Cirkels',
    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)',
     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',
    3636    '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',
    3737    'switch.reader_solo_only': 'Lezen kan alleen in Solo — in de cirkel staan berichten van anderen',
     
    10491049    'switch.solo': 'Solo',
    10501050    'switch.circle': 'Circles',
    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)',
     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',
    10521052    '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',
    10531053    'switch.reader_solo_only': 'Reader is Solo only — a circle shows other people\u2019s posts',
     
    20562056    'switch.solo': 'Solo',
    20572057    'switch.circle': 'Zirkel',
    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)',
     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',
    20592059    'asite.feed_alt': 'Zweite Ansicht', 'asite.feed_alt_reader': 'Lesen', 'asite.feed_alt_timeline': 'Zeitleiste', 'asite.feed_alt_auto': 'Lesen mobil, Zeitleiste am Desktop',
    20602060    'switch.reader_solo_only': 'Lesen gibt es nur in Solo — im Zirkel stehen Beitr\u00e4ge anderer',
  • src/views/pages/home.ejs

    r96a99bf rbc9ef3b  
    7070      is als de module niet laadt -- dan doet hij niets, maar hij springt ook niet
    7171      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>
    8372  <button type="button" id="read-top" class="read-top" aria-label="<%= t('read.to_top') %>" title="<%= t('read.to_top') %>">
    8473    <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

    r96a99bf rbc9ef3b  
    222222
    223223<!-- v9 stylesheet (full palette system) -->
    224 <link rel="stylesheet" href="/assets/css/style.css?v=104">
     224<link rel="stylesheet" href="/assets/css/style.css?v=83">
    225225<script>
    226226/* iOS safe-area, built by hand. env(safe-area-inset-top) resolves to 0 on this iOS in
     
    505505  // Eén nummer voor de hele map. Te vaak bumpen kost één download; te weinig
    506506  // bumpen kost een bugfix die nooit aankomt.
    507   var MOD_V = 47;
     507  var MOD_V = 21;
    508508
    509509  // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
Note: See TracChangeset for help on using the changeset viewer.