Changes in / [bc9ef3b:96a99bf] in Klonkt


Ignore:
Location:
src
Files:
5 edited

Legend:

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

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

    rbc9ef3b r96a99bf  
    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 */
     68const PAGINAMODUS = false;
     69
     70function paginaModus() {
     71  return PAGINAMODUS && OP_TOUCH.matches && document.body.dataset.feedView === 'reader';
     72}
     73
     74function 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. */
     79function 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
     87function 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 */
     104function 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 */
     116function 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 */
     141function 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
     159function 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}
    32169
    33170/**
     
    73210  const sel = window.getSelection && window.getSelection();
    74211  if (sel && String(sel).trim()) return;
     212
    75213  const slug = art.dataset.slug;
    76214  if (!slug) return;
     
    115253
    116254let bezig = false;          // loopt er een gebaar of een uitloop?
     255let ooitGescrold = false;   // snappen begint UIT, zie zetSnappen()
    117256let rustTimer = null;
    118257
     
    143282 */
    144283const 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.
     295const OP_TOUCH = window.matchMedia('(hover: none) and (pointer: coarse)');
    145296const VENDOR_V = 1;   // ophogen als de bestanden in /assets/js/vendor wijzigen
    146297
     
    149300
    150301async 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;
    152307  const [L, S] = await Promise.all([
    153308    import(`/assets/vendor/lenis.mjs?v=${VENDOR_V}`),
     
    155310  ]);
    156311  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')),
    157329    // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in
    158330    // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en
     
    165337    smoothWheel: true,
    166338    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 } : {}),
    168357    autoRaf: true,
    169358  });
    170359  snap = new S.default(lenis, {
    171360    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,
    175388    // Vlot weg, dan steeds langzamer aankomen (Robin, 20-8). easeOutQuart: op de
    176389    // helft van de tijd is 94% van de weg af, en de rest dempt zacht uit.
     
    179392    easing: (t) => 1 - Math.pow(1 - t, 4),
    180393  });
    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.
    183397  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  };
    185428  // Native snappen uit: twee mechanismen op dezelfde scroller vechten.
    186429  document.documentElement.style.scrollSnapType = 'none';
     
    199442function zetSnappen(aan) {
    200443  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.
    201448  const el = document.documentElement;
    202449  const wil = aan ? '' : 'none';
     
    241488
    242489export 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
    243506  const s = document.getElementById('read-stream');
    244507  if (!s) return;
     
    253516  // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet
    254517  // 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;
    260524
    261525  if (opScroll) {
     
    270534  // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar
    271535  // 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  };
    273541  window.addEventListener('scroll', opScroll, { passive: true });
    274542  window.addEventListener('scrollend', rustNu);
     
    279547  window.addEventListener('keydown', opToets);
    280548
     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
    281554  // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw
    282555  // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en
    283556  // 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
    286561}
    287562
  • src/services/i18n.js

    rbc9ef3b r96a99bf  
    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', '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)',
    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', '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)',
    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', '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)',
    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

    rbc9ef3b r96a99bf  
    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>
    7283  <button type="button" id="read-top" class="read-top" aria-label="<%= t('read.to_top') %>" title="<%= t('read.to_top') %>">
    7384    <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  
    222222
    223223<!-- 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">
    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 = 21;
     507  var MOD_V = 47;
    508508
    509509  // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
Note: See TracChangeset for help on using the changeset viewer.