summaryrefslogtreecommitdiff
path: root/docs/site/site.typ
diff options
context:
space:
mode:
Diffstat (limited to 'docs/site/site.typ')
-rw-r--r--docs/site/site.typ115
1 files changed, 99 insertions, 16 deletions
diff --git a/docs/site/site.typ b/docs/site/site.typ
index afcd1041..47903d6e 100644
--- a/docs/site/site.typ
+++ b/docs/site/site.typ
@@ -4,7 +4,7 @@
// typst compile --root . --format bundle --features bundle,html docs/site/site.typ OUT
// Every link is relative and every page sits at the top of OUT, so the site
// works from file:// and from any static server.
-#import "../typ/style.typ": chapters, tutor-path, doc-links, project
+#import "../typ/style.typ": chapters, tutor-path, doc-links, project, btn, chord
#let clips = toml("media/clips.toml").clip
#let casts = toml("casts/casts.toml").cast
@@ -19,11 +19,25 @@
// Change the landing here; the rest of the site is the chapters.
#let landing = (
lead: [pardes is a text environment, after acme and helix, where any text
- is live: right-click looks at it (opens the file at that line, the URL,
- or finds the word) and middle-click runs it (a builtin or a shell
- command). Read PDFs and source, edit code and use terminals with it, in
- a terminal or in its own window.],
+ is live: right-click `main.c:12` to open that line, middle-click `make`
+ to run it, in source, PDFs and terminals alike, in a terminal or in its
+ own window.],
+ credit: [It is built on ideas from #link("https://9p.io/plan9/")[Plan 9] from
+ Bell Labs: #link("https://9p.io/sys/doc/acme/acme.html")[acme]'s text as
+ the interface, and the 9P protocol.],
status: [Linux; macOS in progress. Version #version.],
+ // what's in it: a word each, linked to where it is told; no prose
+ // what's in it: each word jumps to its recording on this page when it
+ // has one, and that recording's caption links on to the docs
+ inside: (
+ (name: "Terminals", rec: "terminals", docs: "guide.html#terminals"),
+ (name: "PDFs", rec: "pdf-navigation", docs: "builtins.html#builtin-PdfSections"),
+ (name: "Images", rec: "image-pane", docs: "reference.html#panes"),
+ (name: "Language servers", rec: none, docs: "setup.html#language-servers"),
+ (name: "Diff review", rec: "diff-review", docs: "guide.html#reviewing-diffs"),
+ (name: "Scripting", rec: "scripting-over-9p", docs: "scripting.html"),
+ (name: "Themes", rec: none, docs: "themes.html"),
+ ),
terminal: [In a terminal],
window: [In its own window],
start: (
@@ -84,6 +98,20 @@
]
]
+#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);
+" +
+ "})();"
+)]
+
#let shell(id, title, body) = html.html(lang: "en", {
html.head({
html.meta(charset: "utf-8")
@@ -101,12 +129,16 @@
#el("footer", (class: "colophon"))[
pardes #version ·
source: #project.mirrors.map(m => el("a", (href: m.web))[#m.name]).join(", ") ·
+ #el("a", (href: project.changes))[changes] ·
#if project.license != none [#project.license license · ]
built from `docs/typ` with Typst ·
#el("a", (href: "fonts/LICENSE-DepartureMono.txt"))[fonts] (OFL) ·
#el("a", (href: "player/LICENSE-asciinema-player.txt"))[asciinema-player] (Apache-2.0)
]
]
+ // after the page's own scripts (the landing's players), on every page
+ // that has a recording
+ in-view-script
})
})
@@ -126,34 +158,55 @@
#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 recording's caption: its text (which may hold #link markup), then the
+/// docs of the "What's in it" word that jumps here, if any.
+#let caption(c) = {
+ if c.caption.contains("#") { eval(c.caption, mode: "markup") } else { c.caption }
+ let to = landing.inside.find(i => i.rec == c.name)
+ if to != none [ #el("a", (class: "more", href: to.docs))[#to.name in the docs →]]
+}
+
/// 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" }))[
+#let cast(c, i) = el("figure", (class: if i == 0 { "clip cast first" } else { "clip cast" }, id: "rec-" + c.name))[
#if c.ready { el("div", (class: "player", id: "cast-" + c.name, "aria-label": c.title))[] } else { placeholder }
- #el("figcaption")[#el("strong")[#c.title.] #c.caption]
+ #el("figcaption")[#el("strong")[#c.title.] #caption(c)]
]
#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, poster: "npt:0:" + str(c.at("poster_at", default: 2)),
))
// "</" 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: false, loop: true, fit: 'width', poster: c.poster}); }")]
}
+/// Plays a recording while it is on screen and pauses it once it leaves.
+/// The casts wait for it (autoPlay off) so each shows its poster frame
+/// until then; the videos autoplay by themselves too.
+
#document(href("index"), title: "pardes", shell("index", "pardes")[
#el("h1", (class: "masthead"))[pardes]
#el("p", (class: "lead"))[#landing.lead]
+ #el("ul", (class: "inside", "aria-label": "What's in it"))[
+ #for i in landing.inside {
+ let to = if i.rec != none { "#rec-" + i.rec } else { i.docs }
+ el("li")[#el("a", (href: to, title: if i.rec != none { "watch it here; the docs: " + i.docs } else { "the docs" }))[#i.name]]
+ }
+ ]
+ #el("p", (class: "credit"))[#landing.credit]
#el("p", (class: "runs"))[#landing.status]
#el("div", (class: "start"))[
#for s in landing.start {
@@ -169,10 +222,10 @@
#el("div", (class: "casts"))[#for (i, c) in casts.enumerate() { cast(c, i) }]
#el("h2", (class: "shelf"))[#landing.window]
#el("div", (class: "clips"))[
- #for c in clips {
- el("figure", (class: "clip"))[
+ #for c in clips.filter(c => c.at("landing", default: true)) {
+ el("figure", (class: "clip", id: "rec-" + c.name))[
#if c.ready { video(c) } else { placeholder }
- #el("figcaption")[#c.caption]
+ #el("figcaption")[#caption(c)]
]
}
]
@@ -197,8 +250,38 @@
])
}
+/// The tutor's text with its mouse buttons drawn: B1, B2, B3 and the
+/// chords it writes 1-2 (hold 1, click 2) become the docs' mouse glyphs,
+/// each with its hidden words, inside the preformatted block.
+#let tutor-text(t) = {
+ // the guide's sections by title, for "-> Guide: Modes, Config"
+ let guide-sections = (:)
+ for m in read("../typ/guide.typ").matches(regex("(?m)^= (.+?) <([a-z0-9-]+)>$")) {
+ guide-sections.insert(m.captures.at(0), m.captures.at(1))
+ }
+ let tokens = regex("\\bB([123])\\b|\\b([123])-([123])\\b|docs/typ/([a-z]+)\\.typ|(?m)(?:^ -> Guide: )(.+)$|https://[^\\s)]+")
+ let at = 0
+ for m in t.matches(tokens) {
+ t.slice(at, m.start)
+ let (b, c1, c2, page, sections) = m.captures
+ if b != none { btn("B" + b) }
+ else if c1 != none { chord("B" + c1, "B" + c2) }
+ else if page != none { link(page + ".html", m.text) }
+ else if sections != none {
+ " -> Guide: "
+ sections.split(", ").map(name => {
+ let l = guide-sections.at(name, default: none)
+ if l != none { link("guide.html#" + l, name) } else { name }
+ }).join(", ")
+ }
+ else { link(m.text, m.text) }
+ at = m.end
+ }
+ t.slice(at)
+}
+
#document(href("tutor"), title: "The tutor", shell("tutor", "The tutor")[
#heading(level: 1)[The tutor] <tutor>
The tutor as the editor shows it (#raw("Tutor"), or #raw("SPC h t")).
- #el("pre", (class: "tutor"))[#read(tutor-path)]
+ #el("pre", (class: "tutor"))[#tutor-text(read(tutor-path))]
])