diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 13:24:07 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 13:34:00 -0300 |
| commit | 52820eb70af788db49735292e2a9ad89cbf1e457 (patch) | |
| tree | dfbefd44cffc76750b477c16c42a181b65963124 | |
| parent | 3d3929960bdc51aa491bdbb5cf83d1b9ab8bb197 (diff) | |
| download | pardes-52820eb70af788db49735292e2a9ad89cbf1e457.tar.gz pardes-52820eb70af788db49735292e2a9ad89cbf1e457.zip | |
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 <[email protected]>
| -rw-r--r-- | docs/site/casts/casts.toml | 2 | ||||
| -rw-r--r-- | docs/site/site.typ | 28 |
2 files changed, 24 insertions, 6 deletions
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/<name>.cast for each. The first plays by itself; all loop +# shown: casts/<name>.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, )) // "</" may not appear inside a script element let data = json.encode(list, pretty: false).replace("</", "<\\/") el("script", (src: "player/asciinema-player.min.js"))[] - el("script")[#("for (const c of " + data + ") AsciinemaPlayer.create({data: c.data}, document.getElementById(c.id), {cols: c.cols, rows: c.rows, autoPlay: c.autoPlay, loop: true, fit: 'width'});")] + el("script")[#("for (const c of " + data + ") { const el = document.getElementById(c.id); el.player = AsciinemaPlayer.create({data: c.data}, el, {cols: c.cols, rows: c.rows, autoPlay: true, loop: true, fit: 'width'}); }")] } +/// Plays a recording while it is on screen and pauses it once it leaves. +#let in-view-script = el("script")[#( + "(() => { 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) { |
