From 52820eb70af788db49735292e2a9ad89cbf1e457 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 13:24:07 -0300 Subject: Every recording on the landing plays by itself, and only while it is on screen The GUI clips are autoplay muted loop playsinline with their controls, and every terminal cast autoplays and loops. A few lines of script watch them with an IntersectionObserver, playing each while a quarter of it is in view and pausing it when it leaves; without the script the videos still autoplay. Co-Authored-By: Claude Opus 5.5 --- docs/site/casts/casts.toml | 2 +- docs/site/site.typ | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'docs') diff --git a/docs/site/casts/casts.toml b/docs/site/casts/casts.toml index 67553089..5771e6be 100644 --- a/docs/site/casts/casts.toml +++ b/docs/site/casts/casts.toml @@ -1,5 +1,5 @@ # The landing page's terminal recordings (asciicast v2), in the order -# shown: casts/.cast for each. The first plays by itself; all loop +# shown: casts/.cast for each. Each plays by itself while on screen; all loop # and fit the page's width. While `ready` is false the cast shows # placeholder.svg, so the site builds before a recording exists. # test/record_casts.py writes the casts and this file. diff --git a/docs/site/site.typ b/docs/site/site.typ index b308853d..e0cd9d4d 100644 --- a/docs/site/site.typ +++ b/docs/site/site.typ @@ -128,15 +128,17 @@ #doc-links.update(true) -#let video(c) = el("video", (poster: "media/" + c.name + ".png", muted: "", loop: "", playsinline: "", - controls: "", preload: "none"))[ +// Every recording plays by itself; the script below plays each only while +// it is on screen. Without the script, `autoplay` still plays them. +#let video(c) = el("video", (poster: "media/" + c.name + ".png", autoplay: "", muted: "", loop: "", playsinline: "", + controls: ""))[ #el("source", (src: "media/" + c.name + ".webm", type: "video/webm"))[] #el("source", (src: "media/" + c.name + ".mp4", type: "video/mp4"))[] ] #let placeholder = el("img", (src: "media/placeholder.svg", alt: "", width: "640", height: "400"))[] /// A terminal recording: a box the player fills, its data inline (so it -/// plays from file://), its caption under it. The first ready one plays. +/// plays from file://), its caption under it. #let ready-casts = casts.filter(c => c.ready) #let cast(c, i) = el("figure", (class: if i == 0 { "clip cast first" } else { "clip cast" }))[ #if c.ready { el("div", (class: "player", id: "cast-" + c.name, "aria-label": c.title))[] } else { placeholder } @@ -145,14 +147,29 @@ #let player-script = { let list = ready-casts.enumerate().map(((i, c)) => ( id: "cast-" + c.name, data: read("casts/" + c.name + ".cast"), - cols: c.cols, rows: c.rows, autoPlay: i == 0, + cols: c.cols, rows: c.rows, )) // " { if (!('IntersectionObserver' in window)) return; +" + + " const p = el => el.player || (el.tagName === 'VIDEO' ? el : null); +" + + " const io = new IntersectionObserver(es => { for (const e of es) { const r = p(e.target); if (!r) continue; +" + + " if (e.isIntersecting) Promise.resolve(r.play()).catch(() => {}); else r.pause(); } }, {threshold: 0.25}); +" + + " for (const el of document.querySelectorAll('.clip video, .cast .player')) io.observe(el); +" + + "})();" +)] + #document(href("index"), title: "pardes", shell("index", "pardes")[ #el("h1", (class: "masthead"))[pardes] #el("p", (class: "lead"))[#landing.lead] @@ -185,6 +202,7 @@ el("link", (rel: "stylesheet", href: "player/asciinema-player.css"))[] player-script } + #in-view-script ]) #for p in pages.slice(1, -1) { -- cgit v1.3