diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 12:41:34 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 12:41:34 -0300 |
| commit | 3c1668e02395d41bb83a4df3d85bf47a7bbdbef4 (patch) | |
| tree | 93c25d9365a0800703f5f9ec2d81fe6acec59b3a /docs/site/site.typ | |
| parent | 9a7830c62d25d83754bd58d0e705501f71656c69 (diff) | |
| download | pardes-3c1668e02395d41bb83a4df3d85bf47a7bbdbef4.tar.gz pardes-3c1668e02395d41bb83a4df3d85bf47a7bbdbef4.zip | |
The landing leads with live text and plays terminal casts first; the Themes page gets a gallery; install starts at the clone
The landing's own words sit in one `landing` record in site.typ: a lead
on the core idea (any text is live: right-click looks, middle-click
runs), where it runs, and a status line whose version is read from
build.zig.zon. Recordings come in two shelves: "In a terminal", asciicast
players from docs/site/casts/casts.toml (the first autoplays; all loop
and fit the width; data inline, so they play from file://), then "In
its own window", the GUI clips. asciinema-player 3.17.0 is vendored in
docs/site/player with its Apache-2.0 licence, from `npm pack`. The
Themes page shows #theme-gallery() from media/themes/themes.toml, a
thumbnail per theme opening the full picture; on paper it is nothing.
Both shelves and the gallery show placeholders until their files exist.
`project` in style.typ lists the mirrors (sourcehut, git.0x4200.cafe);
install.typ clones from the first and names them all, and the footer
links each. The README clones before it builds. The scripting
interface is a virtual filesystem served over 9P, never just a
filesystem, in the tagline, captions and README.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'docs/site/site.typ')
| -rw-r--r-- | docs/site/site.typ | 102 |
1 files changed, 71 insertions, 31 deletions
diff --git a/docs/site/site.typ b/docs/site/site.typ index 068b5b9c..afcd1041 100644 --- a/docs/site/site.typ +++ b/docs/site/site.typ @@ -4,9 +4,34 @@ // 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, tagline, project +#import "../typ/style.typ": chapters, tutor-path, doc-links, project #let clips = toml("media/clips.toml").clip +#let casts = toml("casts/casts.toml").cast +#let themes = toml("media/themes/themes.toml") +/// The version, from build.zig.zon: 0.23.0 reads 0.23. +#let version = { + let v = read("/build.zig.zon").match(regex("\\.version\\s*=\\s*\"([^\"]+)\"")).captures.first() + if v.ends-with(".0") and v.split(".").len() == 3 { v.slice(0, -2) } else { v } +} + +// --- the landing page's own words: everything a visitor reads first ---------- +// 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.], + status: [Linux; macOS in progress. Version #version.], + terminal: [In a terminal], + window: [In its own window], + start: ( + (id: "guide", label: [Start: the guide], note: [about 15 min]), + (id: "tutor", label: [Hands on: the tutor], note: [run `Tutor` inside pardes]), + ), + install: [Install], +) #let pages = ( (id: "index", title: "pardes"), ..chapters.filter(c => c.file != none).map(c => (id: c.id, title: c.title, file: c.file)), @@ -22,6 +47,12 @@ asset("fonts/" + f, read("fonts/" + f, encoding: none)) } #asset("media/placeholder.svg", read("media/placeholder.svg", encoding: none)) +#for f in ("asciinema-player.min.js", "asciinema-player.css", "LICENSE-asciinema-player.txt") { + asset("player/" + f, read("player/" + f, encoding: none)) +} +#if themes.at("ready", default: true) { + for t in themes.theme { asset("media/themes/" + t.file, read("media/themes/" + t.file, encoding: none)) } +} #for c in clips.filter(c => c.ready) { for ext in ("webm", "mp4", "png") { asset("media/" + c.name + "." + ext, read("media/" + c.name + "." + ext, encoding: none)) @@ -68,11 +99,12 @@ #el("article", (class: "content"))[#body] ] #el("footer", (class: "colophon"))[ - pardes · - #if project.source != none [#el("a", (href: project.source))[source] · ] + pardes #version · + source: #project.mirrors.map(m => el("a", (href: m.web))[#m.name]).join(", ") · #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: "fonts/LICENSE-DepartureMono.txt"))[fonts] (OFL) · + #el("a", (href: "player/LICENSE-asciinema-player.txt"))[asciinema-player] (Apache-2.0) ] ] }) @@ -94,54 +126,62 @@ #doc-links.update(true) -// The first ready clip plays at the top; the grid shows the rest. -#let hero = clips.find(c => c.ready) -#let video(c, extra) = el("video", (poster: "media/" + c.name + ".png", muted: "", loop: "", playsinline: "", - controls: "") + extra)[ +#let video(c) = el("video", (poster: "media/" + c.name + ".png", muted: "", loop: "", playsinline: "", + controls: "", preload: "none"))[ #el("source", (src: "media/" + c.name + ".webm", type: "video/webm"))[] #el("source", (src: "media/" + c.name + ".mp4", type: "video/mp4"))[] ] -#let start = ( - (id: "guide", label: [Start: the guide], note: [about 15 min]), - (id: "tutor", label: [Hands on: the tutor], note: none), -) +#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. +#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 } + #el("figcaption")[#el("strong")[#c.title.] #c.caption] +] +#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, + )) + // "</" 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'});")] +} #document(href("index"), title: "pardes", shell("index", "pardes")[ #el("h1", (class: "masthead"))[pardes] - #el("p", (class: "lead"))[pardes is an editor and terminal in columns of panes, each - a file, a terminal, a PDF or an image. Click any text to run it or open - it; the keys are helix's; scripts drive it through files.] - #el("p", (class: "pitch"))[#tagline.] - #el("p", (class: "runs"))[It runs in a terminal or in its own window (SDL), on Linux; - macOS and browser builds are in the tree.] - #if hero != none { - el("figure", (class: "clip hero"))[ - #video(hero, (autoplay: "")) - #el("figcaption")[#hero.caption] - ] - } + #el("p", (class: "lead"))[#landing.lead] + #el("p", (class: "runs"))[#landing.status] #el("div", (class: "start"))[ - #for s in start { + #for s in landing.start { el("a", (class: "go", href: href(s.id)))[#s.label#if s.note != none [ #el("span")[(#s.note)]]] } ] #el("p", (class: "rest"))[ - #for p in pages.slice(1).filter(p => p.id not in start.map(s => s.id)) { + #for p in pages.slice(1).filter(p => p.id not in landing.start.map(s => s.id)) { el("a", (href: href(p.id)))[#p.title] } ] + #el("h2", (class: "shelf"))[#landing.terminal] + #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.filter(c => c != hero) { + #for c in clips { el("figure", (class: "clip"))[ - #if c.ready { video(c, (preload: "none")) } else { - el("img", (src: "media/placeholder.svg", alt: "", width: "640", height: "400"))[] - } + #if c.ready { video(c) } else { placeholder } #el("figcaption")[#c.caption] ] } ] - #el("h2", (class: "install"))[Install] + #el("h2", (class: "install"))[#landing.install] #include "../typ/install.typ" + #if ready-casts.len() > 0 { + el("link", (rel: "stylesheet", href: "player/asciinema-player.css"))[] + player-script + } ]) #for p in pages.slice(1, -1) { |
