Changeset 22d2dba5 in Klonkt


Ignore:
Timestamp:
08/20/2026 07:25:24 AM (3 weeks ago)
Author:
Claude (agent) <aiclaude@…>
Branches:
main
Children:
77019fa
Parents:
c581e5e
git-author:
Robin <roboburr@…> (08/20/2026 03:33:49 AM)
git-committer:
Claude (agent) <aiclaude@…> (08/20/2026 07:25:24 AM)
Message:

wip paginamodus

Location:
src
Files:
5 edited

Legend:

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

    rc581e5e r22d2dba5  
    25612561   witruimte en is een doorlopende stroom vaak prettiger. */
    25622562@media (hover: none) and (pointer: coarse) {
     2563    /* Op mobiel draait Lezen in paginamodus (body.is-paged), en die zet hieronder
     2564       een vaste hoogte. Deze regel is de terugval voor het moment vóór de module
     2565       geladen is, zodat de berichten niet eerst als losse stroom in beeld
     2566       springen. */
    25632567    .feed-reader .read-post { min-height: 100svh; }
    2564 }
     2568/* Geen native snap op mobiel: dat doet lenis/snap, met een instelbare vangzone
     2569       en duur. Twee snapsystemen op dezelfde scroller vechten. */
     2570    html:has(body.on-home[data-feed-view="reader"]) { scroll-snap-type: none; }
     2571}
     2572
     2573/* -------- PAGINAMODUS --------
     2574   Elk bericht is een paneel van precies een scherm dat ZELF scrollt; tussen
     2575   berichten ga je met knoppen. Daarmee doet het scrollgevoel er niet meer toe:
     2576   geen momentum om na te bouwen en geen snap die vangt.
     2577   De stroom zelf vangt geen vingers meer -- dat regelt lenis.stop() in
     2578   mod/read.js -- en data-lenis-prevent op het paneel houdt de scroll BINNEN een
     2579   bericht ongemoeid (die controle staat in Lenis vóór de gestopt-controle).
     2580   Geldt waar een bericht een eigen scherm heeft: op mobiel altijd, op desktop
     2581   als de site dat zo instelt. */
     2582body.is-paged .feed-reader .read-post {
     2583    height: 100svh;
     2584    min-height: 0;
     2585    overflow-y: auto;
     2586    overscroll-behavior: contain;   /* raakt het einde, dan niet doorschuiven */
     2587    scroll-snap-align: none;
     2588    /* Ruimte voor de twee balken, zodat er nooit tekst onder een knop verdwijnt.
     2589       Het verloop laat een regel wel zacht wegvagen, maar wat je moet KUNNEN
     2590       lezen hoort er niet achter te komen -- vandaar padding en niet alleen die
     2591       fade. --read-balk staat hieronder bij .read-nav, zodat de twee niet uit
     2592       elkaar kunnen lopen. */
     2593    padding-top: calc(var(--read-balk) + .5rem);
     2594    padding-bottom: calc(var(--read-balk) + .5rem);
     2595    /* BOVENAAN beginnen, niet gecentreerd. Ik had de inhoud een ronde lang
     2596       verticaal gecentreerd om het gat onder een kort bericht te verdelen, maar
     2597       dan hangt zo'n bericht halverwege het scherm en begint niet elk paneel op
     2598       dezelfde plek -- dat leest onrustig. Robin ving het (20-8). Elk bericht
     2599       begint nu bovenaan, en de ruimte die overblijft valt onderaan. */
     2600}
     2601body.is-paged .feed-reader .read-anker { scroll-snap-align: none; }
     2602
     2603/* DE DYNAMIC ISLAND, en met opzet alleen op iOS (Robin, 20-8: "op android is het
     2604   een ander verhaal").
     2605   In paginamodus vult een bericht precies het scherm, dus de eerste regel komt
     2606   bovenaan -- en op een iPhone ligt daar de island of de notch overheen.
     2607   env(safe-area-inset-top) geeft daar de vrije ruimte; viewport-fit=cover staat
     2608   al in de viewport-meta, dus die waarde is echt.
     2609   De @supports-regel is de iOS-Safari-toets: -webkit-touch-callout bestaat alleen
     2610   daar. Op Android is env() vaak 0 of betekent het iets anders (camera-uitsparing,
     2611   statusbalk die al meegerekend is), en dan zou dit een gat bovenin maken dat er
     2612   niet hoort. Vandaar afgeschermd in plaats van env() overal toepassen.
     2613   De hoogte krimpt mee, anders duwt de padding het paneel groter dan het scherm
     2614   en krijg je alsnog scroll tussen de berichten. */
     2615@supports (-webkit-touch-callout: none) {
     2616    body.is-paged .feed-reader .read-post {
     2617        padding-top: calc(var(--read-balk) + .5rem + env(safe-area-inset-top, 0px));
     2618        height: calc(100svh - env(safe-area-inset-top, 0px));
     2619    }
     2620    /* De bovenbalk houdt zijn knop al vrij van de island via padding-top
     2621       hierboven; hier hoeft niets extra's meer. */
     2622}
     2623
     2624/* De knop onderaan een bericht: naar het volgende. */
     2625
     2626
     2627/* Omhoog en omlaag: twee balken, boven en onder, die BLIJVEN staan terwijl je in
     2628   een bericht scrolt (Robin, 20-8). Het verloop van paginakleur naar doorzichtig
     2629   laat de tekst eronderdoor wegvagen -- zonder dat zou een regel halverwege hard
     2630   achter de knop verdwijnen en oogt het als een afgeknipte pagina.
     2631   De BALK vangt geen aanrakingen (pointer-events: none), alleen de knop zelf --
     2632   anders zou een strook van 5rem boven- en onderaan je scroll opeten. */
     2633/* TWEE BALKEN over de volle breedte, boven en onder. Eerder waren dit losse
     2634   cirkels (te rommelig naast de tabbalk en de speler) en daarna onzichtbare
     2635   tikzones -- maar die werkten niet, omdat de onderste strook onder de
     2636   mini-speler en de tabbalk ligt en die de tik opvangen. Een balk met een eigen
     2637   z-index kan daar wel overheen.
     2638   Slank gehouden: ze eten hooguit 2.6rem aan elke kant, en het paneel houdt daar
     2639   al ruimte voor vrij. */
     2640:root { --read-balk: 2.6rem; }
     2641.read-bar {
     2642    position: fixed; left: 0; right: 0; z-index: 1060;   /* boven tabbalk (1050) */
     2643    display: flex; align-items: center;
     2644    /* Pijlen LINKS in de balk, niet gecentreerd (Robin, 20-8). */
     2645    justify-content: flex-start;
     2646    padding-left: max(1.1rem, env(safe-area-inset-left, 0px));
     2647    box-sizing: border-box;
     2648    height: var(--read-balk);
     2649    border: 0;
     2650    border-top: 1px solid transparent;
     2651    background: color-mix(in srgb, var(--paper) 90%, var(--ink));
     2652    backdrop-filter: blur(14px) saturate(140%);
     2653    -webkit-backdrop-filter: blur(14px) saturate(140%);
     2654    color: var(--ink);
     2655    cursor: pointer;
     2656    transition: opacity .15s ease;
     2657}
     2658.read-bar--top {
     2659    top: 0;
     2660    height: calc(var(--read-balk) + env(safe-area-inset-top, 0px));
     2661    padding-top: env(safe-area-inset-top, 0px);
     2662    border-bottom: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
     2663}
     2664.read-bar--bottom {
     2665    /* De waarde komt uit mod/read.js: die meet hoe hoog de tabbalk en de
     2666       mini-speler werkelijk staan. Geraden rem-waarden lieten hem zweven -- een
     2667       kier boven de speler, of er half achter. Dit is de terugval voor de eerste
     2668       frame en voor als de module niet laadt. */
     2669    bottom: 0;
     2670    border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
     2671}
     2672.read-bar:active { background: color-mix(in srgb, var(--paper) 78%, var(--ink)); }
     2673.read-bar[hidden], body:not(.is-paged) .read-bar { display: none; }
    25652674
    25662675/* Een lijn tussen twee berichten: zonder grens is een stroom een lange brij. */
  • src/assets/js/mod/read.js

    rc581e5e r22d2dba5  
    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
     
    161300
    162301async 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.
    163306  if (lenis || !(OP_DESKTOP.matches || OP_TOUCH.matches)) return;
    164307  const [L, S] = await Promise.all([
     
    167310  ]);
    168311  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')),
    169329    // Lenis tekent de scrollbeweging zelf, maar STEVIG GEDEMPT (lerp 0.2 in
    170330    // plaats van de standaard 0.1). Reden: een muiswiel scrollt in schokken en
     
    179339    // Alleen op touch overneemt hij het vingerscrollen; op desktop hoeft dat niet.
    180340    syncTouch: OP_TOUCH.matches,
    181     // TOUCH-SPECIFIEK, want de standaardwaarden voelen op een telefoon traag.
     341    // Twee touch-knoppen, en ze doen echt iets anders. Lenis rekent bij het
     342    // LOSLATEN eenmalig een doel uit -- afstand = snelheid^touchInertiaExponent
     343    // -- en kruipt daar dan naartoe met syncTouchLerp.
    182344    //
    183     // Waar dat zit is precies aan te wijzen. In Lenis staat
    184     // `lerp: d ? syncTouchLerp : 1`, waarbij d "de vinger is net losgelaten"
    185     // betekent. Tijdens het SLEPEN is de lerp dus 1 -- de inhoud volgt je vinger
    186     // exact, net als iOS, en daar is niets mis mee. De traagheid zit in de glijder
    187     // NA het loslaten: die gebruikt syncTouchLerp, en dat staat standaard op
    188     // 0.075. Dat dempt zo langzaam uit dat de pagina nog seconden naijlt.
     345    //   touchInertiaExponent  hoe VER de flick draagt   (standaard 1.7)
     346    //   syncTouchLerp         hoe hard hij REMT         (standaard 0.075)
    189347    //
    190     // TWEE KNOPPEN, en ze doen iets anders -- dat verwarde ik eerst:
     348    // Let op: `touchInertiaMultiplier` bestaat NIET in 1.3.26. Lenis slikt
     349    // onbekende opties zonder fout, dus zo'n regel lijkt te werken en doet niets.
     350    // Controleer een optienaam in src/assets/vendor/lenis.mjs voor je hem zet.
    191351    //
    192     //   touchInertiaMultiplier   hoe VER een veeg je brengt
    193     //   syncTouchLerp            hoe snel de glijder UITDEMPT
    194     //
    195     // Eerste ronde zette ik de multiplier op 25 (korter dan de standaard 35),
    196     // omdat ik "te traag" las als "hij ijlt te lang na". Robin bedoelde het
    197     // omgekeerde: een veeg moet je VERDER brengen, zoals op iOS -- daar draagt
    198     // een flick een heel eind. Dus juist omhoog, ruim boven de standaard.
    199     //
    200     // De lerp gaat mee terug naar 0.1: tussen de trage standaard (0.075, dat
    201     // seconden naijlt) en de kordate 0.15 in. Met een langere weg af te leggen
    202     // mag de demping wat zachter, anders komt hij te abrupt tot stilstand.
    203     // Waar we na een paar rondes proberen op uitkwamen, met de betekenis erbij
    204     // omdat de tweede knop contra-intuitief is:
    205     //
    206     //   touchInertiaMultiplier 70   hoe VER een veeg draagt (standaard 35).
    207     //                               25 en 45 waren allebei te kort.
    208     //   syncTouchLerp 0.05          hoe snel hij zijn doel BENADERT. Hoger = eerder
    209     //                               aankomen en dus abrupt stoppen; LAGER = langer
    210     //                               onderweg blijven. Robin: "te stroef bij het
    211     //                               loslaten, mag echt een tijdje doorscrollen".
    212     //                               Dus omlaag, niet omhoog -- 0.15 en 0.1 kapten
    213     //                               de uitloop af.
    214     // syncTouchLerp 0.25: VIER KEER de vorige waarde, en ruim boven de standaard
    215     // van 0.075. Ik heb hem vier rondes lang de verkeerde kant op gedraaid omdat
    216     // ik "stroef" las als "hij stopt te vroeg" -- maar het betekende STROPERIG.
    217     // Een lage lerp benadert het doel langzaam en geeft dus een kruipende
    218     // uitloop; hoog betekent er vlot naartoe. Met de multiplier op 70 blijft de
    219     // AFSTAND groot, dus je gaat ver EN snel: dat is hoe een flick op iOS voelt.
    220     //
    221     // Kort samengevat, want dit is twee keer misgegaan:
    222     //   syncTouchLerp hoger  = sneller weg, korter narollen
    223     //   multiplier hoger     = verder komen
    224     ...(OP_TOUCH.matches ? { syncTouchLerp: 0.25, touchInertiaMultiplier: 70 } : {}),
     352    // Robin wilde langer en sneller uitrollen zonder remgevoel, dus de exponent
     353    // gaat omhoog (een stevige flick draagt daarmee ruim vier tot zes keer zo
     354    // ver) en de lerp gaat juist boven de standaard: hij legt die afstand vlot
     355    // af in plaats van er stroperig naartoe te kruipen.
     356    ...(OP_TOUCH.matches ? { syncTouchLerp: 0.09, touchInertiaExponent: 2.2 } : {}),
    225357    autoRaf: true,
    226358  });
    227359  snap = new S.default(lenis, {
    228360    type: 'proximity',
    229     distanceThreshold: '12%',   // de vangzone, hier WEL instelbaar
     361    // DE VANGZONE. Gemeten op dev: de voet van een bericht (de link naar de
     362    // reacties) staat 138 tot 472 pixels boven de bovenkant van het volgende.
     363    // Met de oude 12% was de zone 86 pixels op een venster van 720, dus hij hapte
     364    // pas ruim onder die voet toe. 55% is ~400 pixels: de zone begint nu rond de
     365    // voet, wat Robin vroeg (20-8) -- eerder al snappen, boven de reacties.
     366    // Wil je het nog eerder, dan is dit het enige getal dat je hoeft te draaien.
     367    distanceThreshold: '55%',
    230368    // Hoe lang na de laatste scrollbeweging hij mag vangen. De standaard is 500
    231369    // en dat voelt als te laat. Op touch nog korter dan op desktop, want daar
     
    242380    // "stroef bij het loslaten", en het werd er dan ook erger van. 200ms laat de
    243381    // uitloop eerst zijn werk doen.
    244     debounce: OP_TOUCH.matches ? 200 : 60,
     382    // Met een iOS-achtige uitloop duurt de staart lang; te kort wachten laat de
     383    // snap midden in de vlucht ingrijpen. 150ms laat hem uitrollen en vangt dan.
     384    debounce: OP_TOUCH.matches ? 150 : 60,
    245385    // Op touch korter: een telefoon vraagt om directer antwoord dan een muis, en
    246386    // de snap komt daar aan het eind van een lange uitloop -- dan mag hij kort.
     
    256396  const berichten = [...document.querySelectorAll('.feed-reader .read-post')];
    257397  berichten.slice(0, -1).forEach((a) => snap.addElement(a, { align: 'start' }));
     398
     399  // ALLEEN VOORUIT VANGEN. Dit hoort bij de ruime zone hierboven en is niet
     400  // optioneel: lenis/snap kiest het DICHTSTBIJZIJNDE punt en zijn drempel geldt
     401  // naar twee kanten. Scroll je een lang bericht in en stop je 300 pixels onder
     402  // de bovenkant, dan is die bovenkant het dichtstbij -- en met een zone van 400
     403  // wordt je teruggetrokken. Dat is precies de klacht "ik kan niet scrollen
     404  // binnen een lang bericht", en met de oude zone van 86 pixels viel het alleen
     405  // niet op.
     406  //
     407  // Dus: een doel ACHTER je slaan we over. Ligt er een punt voor je binnen de
     408  // zone, dan gaan we daarheen; anders gebeurt er niets en scrol je vrij door.
     409  // Zo verruimt de zone alleen de kant waar hij bedoeld is.
     410  //
     411  // Waarom niet gewoon de drempel? Omdat lenis/snap alleen de dichtstbijzijnde
     412  // kandidaat beoordeelt: zonder deze omleiding houdt de zone vooruit op bij de
     413  // helft van de afstand tussen twee berichten, hoe groot je de drempel ook zet.
     414  const echtGaNaar = snap.goTo.bind(snap);
     415  snap.goTo = (index) => {
     416    const punten = snap.computeSnaps();   // zelfde volgorde als goTo intern gebruikt
     417    const nu = lenis.scroll;
     418    const doel = punten[index];
     419    if (doel && doel.value > nu + 2) { echtGaNaar(index); return; }
     420    let vooruit = -1;
     421    punten.forEach((punt, i) => {
     422      if (punt.value > nu + 2 && (vooruit < 0 || punt.value < punten[vooruit].value)) vooruit = i;
     423    });
     424    if (vooruit < 0) return;
     425    if (punten[vooruit].value - nu <= snap.distanceThreshold) echtGaNaar(vooruit);
     426  };
    258427  // Native snappen uit: twee mechanismen op dezelfde scroller vechten.
    259428  document.documentElement.style.scrollSnapType = 'none';
     
    272441function zetSnappen(aan) {
    273442  if (snap) { if (aan) snap.start(); else snap.stop(); return; }
     443  // Zonder Lenis (mobiel) zetten we de CSS-eigenschap om. Hij staat bij het
     444  // laden UIT -- dat is de standaardstand van deze variabele -- zodat de browser
     445  // niet meteen naar het eerste bericht springt en de header wegvalt. Pas je
     446  // eerste gebaar naar beneden zet hem aan.
    274447  const el = document.documentElement;
    275448  const wil = aan ? '' : 'none';
     
    314487
    315488export function init() {
     489  // EERST OPRUIMEN, en pas daarna terugvallen als er geen leesstroom is.
     490  //
     491  // Dit stond andersom, en dat brak het scrollen op de HELE site: navigeerde je
     492  // van Lezen naar het beheer, dan viel init() bij de ontbrekende leesstroom
     493  // meteen terug -- en bleef Lenis leven EN gestopt (lenis.stop() vangt wiel en
     494  // vinger af). Daarna scrolde niets meer, ook niet op pagina's die met Lezen
     495  // niets te maken hebben. Robins melding (20-8): "scrollen werkt nu nergens ook
     496  // niet in admin panels".
     497  //
     498  // Opruimen hoort dus bij het VERLATEN van de weergave, niet bij het opzetten
     499  // ervan. init() draait bij elke paginawissel, dus dit is de plek.
     500  stopLenis();
     501  document.body.classList.remove('is-paged');
     502  document.querySelectorAll('[data-lenis-prevent].read-post')
     503    .forEach((a) => a.removeAttribute('data-lenis-prevent'));
     504
    316505  const s = document.getElementById('read-stream');
    317506  if (!s) return;
     
    326515  // De knop staat in de HTML, zodat hij er ook is zonder deze module -- dan doet
    327516  // hij niets, maar hij springt niet in beeld bij het laden.
    328   knop = document.getElementById('read-top');
    329   if (knop) {
    330     knop.onclick = naarBoven;
    331     toonKnop(knop);
    332   }
     517  // De terug-naar-boven-knop staat er tijdelijk uit (Robin, 20-8) -- in
     518  // paginamodus doet de bovenbalk dat werk al. De code blijft staan zodat hij
     519  // met een regel terug is.
     520  knop = null;
     521  const oudeKnop = document.getElementById('read-top');
     522  if (oudeKnop) oudeKnop.hidden = true;
    333523
    334524  if (opScroll) {
     
    343533  // Elke scroll -- van een vinger of van de browser zelf -- houdt het gebaar
    344534  // levend; pas als het stil blijft mag een nieuwe richting gelden.
    345   opScroll = () => { if (knop) toonKnop(knop); planRust(); };
     535  opScroll = () => {
     536    if (knop) toonKnop(knop);
     537    planRust();
     538    if (document.body.classList.contains('is-paged')) zetBalken();
     539  };
    346540  window.addEventListener('scroll', opScroll, { passive: true });
    347541  window.addEventListener('scrollend', rustNu);
     
    352546  window.addEventListener('keydown', opToets);
    353547
     548  const balkBoven = document.getElementById('read-prev');
     549  const balkOnder = document.getElementById('read-next-nav');
     550  if (balkBoven) balkBoven.onclick = gaOmhoog;
     551  if (balkOnder) balkOnder.onclick = () => gaNaar(huidigIndex() + 1);
     552
    354553  // Alleen in de leesweergave, en alleen op desktop. Bij elke init() opnieuw
    355554  // beoordelen: van Grid naar Lezen schakelen hoort hem aan te zetten, en
    356555  // wegnavigeren hoort hem op te ruimen.
    357   stopLenis();
    358   if (document.body.dataset.feedView === 'reader') startLenis();
     556  if (document.body.dataset.feedView === 'reader') startLenis().then(pasPaginaModusToe);
     557  else pasPaginaModusToe();
     558
     559
    359560}
    360561
  • src/services/i18n.js

    rc581e5e r22d2dba5  
    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 op desktop: elk bericht een eigen scherm (op mobiel altijd)',
     35    'switch.grid': 'Grid', 'switch.reader': 'Lezen', 'switch.timeline': 'Tijdlijn', 'read.to_top': 'Terug naar boven', 'read.pinned': 'Vastgepind', 'read.next': 'Volgende', 'read.prev': 'Vorige bericht', 'read.nav': 'Door de berichten', 'read.hint': 'Tik boven- of onderaan om een bericht terug of verder', 'asite.reader_full_page': 'Lezen op desktop: elk bericht een eigen scherm (op mobiel altijd)',
    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 on desktop: one screen per post (always on mobile)',
     1051    'switch.grid': 'Grid', 'switch.reader': 'Reader', 'switch.timeline': 'Timeline', 'read.to_top': 'Back to top', 'read.pinned': 'Pinned', 'read.next': 'Next', 'read.prev': 'Previous post', 'read.nav': 'Through the posts', 'read.hint': 'Tap the top or bottom to move a post back or on', 'asite.reader_full_page': 'Reader on desktop: one screen per post (always on mobile)',
    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 am Desktop: ein Bildschirm pro Beitrag (mobil immer)',
     2058    'switch.grid': 'Raster', 'switch.reader': 'Lesen', 'switch.timeline': 'Zeitleiste', 'read.to_top': 'Nach oben', 'read.pinned': 'Angeheftet', 'read.next': 'Weiter', 'read.prev': 'Vorheriger Beitrag', 'read.nav': 'Durch die Beitraege', 'read.hint': 'Oben oder unten tippen fuer einen Beitrag zurueck oder weiter', 'asite.reader_full_page': 'Lesen am Desktop: ein Bildschirm pro Beitrag (mobil immer)',
    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

    rc581e5e r22d2dba5  
    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

    rc581e5e r22d2dba5  
    222222
    223223<!-- v9 stylesheet (full palette system) -->
    224 <link rel="stylesheet" href="/assets/css/style.css?v=84">
     224<link rel="stylesheet" href="/assets/css/style.css?v=103">
    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 = 29;
     507  var MOD_V = 46;
    508508
    509509  // name -> 1 (aan het laden) of de module-namespace (geladen). Een module
Note: See TracChangeset for help on using the changeset viewer.