summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 14:03:11 -0300
committerGabriel Schneider <[email protected]>2026-10-01 15:24:04 -0300
commit3b186b7724dd3ca0ff67c3c8a0f9823f0f161fc5 (patch)
treec331f529df447bbeee56cfa21e7f7d3438bcf62b
parent54948bf22fb891bd31fcd5dc33e0bce7552fe1d4 (diff)
downloadpardes-3b186b7724dd3ca0ff67c3c8a0f9823f0f161fc5.tar.gz
pardes-3b186b7724dd3ca0ff67c3c8a0f9823f0f161fc5.zip
The landing's words jump to their recordings, the casts open on their telling frame, and #mode-box draws the box
Each recording on the landing has an anchor (rec-<name>); the "What's in it" words jump to theirs (Terminals, PDFs, Images, Diff review, Scripting) and the recording's caption ends in a link to the docs; Language servers and Themes, with no recording, link to the docs. A cast's `poster_at` in casts.toml picks the frame it shows until it plays: live-text at the error, terminals at the copied line, petscii on the Mandelbrot; the casts wait for the in-view script to play them. A caption may hold markup: the 9P clip links 9ns to setup. The downloads give their size, read from project.download, and the footer links the changes (project.changes). The guide's annotated pane has alt text, pin 1 beside the workspace tag rather than on Changelog, and pin 5 on the body's text; pin positions are written with repr, since str puts a U+2212 minus CSS cannot read. #mode-box(mark, unsaved:) draws a pane's box as it looks, in print and HTML. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/site/casts/casts.toml4
-rw-r--r--docs/site/media/clips.toml4
-rw-r--r--docs/site/site.css7
-rw-r--r--docs/site/site.typ44
-rw-r--r--docs/typ/README12
-rw-r--r--docs/typ/cheatsheet-a4.pdf6
-rw-r--r--docs/typ/guide.typ5
-rw-r--r--docs/typ/install.typ2
-rw-r--r--docs/typ/style.typ31
9 files changed, 86 insertions, 29 deletions
diff --git a/docs/site/casts/casts.toml b/docs/site/casts/casts.toml
index 9f0dd1a5..9cf8afb6 100644
--- a/docs/site/casts/casts.toml
+++ b/docs/site/casts/casts.toml
@@ -2,6 +2,7 @@
# 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.
+# `poster_at` is the second whose frame stands for the cast before it plays.
# test/record_casts.py writes the casts and this file.
[[cast]]
@@ -11,6 +12,7 @@ caption = "Rename a variable with helix keys, middle-click make, right-click the
cols = 100
rows = 30
ready = true
+poster_at = 14
[[cast]]
name = "terminals"
@@ -19,6 +21,7 @@ caption = "A shell beside a file: Ctrl-b, copy a line, page git log."
cols = 100
rows = 30
ready = true
+poster_at = 11
[[cast]]
name = "petscii"
@@ -27,3 +30,4 @@ caption = "No kitty graphics: a Mandelbrot in PETSCII, then the Intel manual sea
cols = 100
rows = 30
ready = true
+poster_at = 3
diff --git a/docs/site/media/clips.toml b/docs/site/media/clips.toml
index 11f6e8f3..37dc9db9 100644
--- a/docs/site/media/clips.toml
+++ b/docs/site/media/clips.toml
@@ -1,6 +1,6 @@
# The landing page's recordings, in the order shown. For each clip the
# site ships media/<name>.webm and media/<name>.mp4, with media/<name>.png
-# as the poster. `landing = false` keeps a clip off the landing (the
+# as the poster; a caption may hold markup (#link). `landing = false` keeps a clip off the landing (the
# terminal casts show the same) while its files stay for the chapters. While `ready` is false the clip shows placeholder.svg and
# no video, so the site builds before a recording exists.
@@ -32,7 +32,7 @@ landing = false
[[clip]]
name = "scripting-over-9p"
-caption = "A shell under 9ns, where the session is a directory: ls, cat, echo."
+caption = "A shell under #link(\"setup.html#ninens\")[9ns], where the session is a directory: ls, cat, echo."
ready = true
[[clip]]
diff --git a/docs/site/site.css b/docs/site/site.css
index 6e4a2e93..dc682eff 100644
--- a/docs/site/site.css
+++ b/docs/site/site.css
@@ -207,3 +207,10 @@ main[data-page="builtins"] .content h4 + p { margin-top:0; }
.annotated .pins li { margin:.25em 0; }
.annotated .pins li::marker { font:700 15px var(--mono); color:var(--amber); }
@media (max-width:600px) { .annotated { grid-template-columns:minmax(0, 1fr); } }
+.clip figcaption .more { font:15px var(--pixel); color:var(--leaf); margin-left:.3em; white-space:nowrap; }
+.clip { scroll-margin-top:16px; }
+
+/* a pane's box: blank normal, ^ insert, $ raw; filled while unsaved */
+.mode-box { display:inline-flex; align-items:center; justify-content:center; width:1.15em; height:1.15em; vertical-align:-.15em;
+ font:700 .8em var(--mono); background:var(--tag); color:var(--ink); border:2px solid var(--leaf); border-radius:2px; }
+.mode-box.unsaved { background:var(--amber); border-color:var(--amber); color:#1b120a; }
diff --git a/docs/site/site.typ b/docs/site/site.typ
index 787844f5..62b1292e 100644
--- a/docs/site/site.typ
+++ b/docs/site/site.typ
@@ -27,14 +27,16 @@
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: (
- ("Terminals", "guide.html#terminals"),
- ("PDFs", "builtins.html#builtin-PdfSections"),
- ("Images", "reference.html#panes"),
- ("Language servers", "setup.html#language-servers"),
- ("Diff review", "guide.html#reviewing-diffs"),
- ("Scripting", "scripting.html"),
- ("Themes", "themes.html"),
+ (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],
@@ -113,6 +115,7 @@
#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) ·
@@ -147,25 +150,35 @@
]
#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.
#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,
+ 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 + ") { 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', poster: 'npt:0:2'}); }")]
+ 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.
#let in-view-script = el("script")[#(
"(() => { if (!('IntersectionObserver' in window)) return;
" +
@@ -184,7 +197,10 @@
#el("h1", (class: "masthead"))[pardes]
#el("p", (class: "lead"))[#landing.lead]
#el("ul", (class: "inside", "aria-label": "What's in it"))[
- #for (name, to) in landing.inside { el("li")[#el("a", (href: to))[#name]] }
+ #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]
@@ -203,9 +219,9 @@
#el("h2", (class: "shelf"))[#landing.window]
#el("div", (class: "clips"))[
#for c in clips.filter(c => c.at("landing", default: true)) {
- el("figure", (class: "clip"))[
+ el("figure", (class: "clip", id: "rec-" + c.name))[
#if c.ready { video(c) } else { placeholder }
- #el("figcaption")[#c.caption]
+ #el("figcaption")[#caption(c)]
]
}
]
diff --git a/docs/typ/README b/docs/typ/README
index 5db2cf76..9d3913c0 100644
--- a/docs/typ/README
+++ b/docs/typ/README
@@ -35,7 +35,7 @@ Build (from the repo root):
The contract for content files
------------------------------
Start a content file with
- #import "style.typ": key, keys, btn, chord, word, tag, addr, file, cmd, doc, pairs, fstree
+ #import "style.typ": key, keys, btn, chord, word, tag, addr, file, cmd, doc, pairs, fstree, annotated, mode-box
(a content file does not import page setup; the same file goes into the
book, the HTML and, for the cheatsheet, the A4 page)
and use plain markup (= headings, - lists, `raw`, *strong*) plus these.
@@ -82,6 +82,16 @@ new function instead.
pane/
new open: a new pane @panes
```)
+ annotated(src, size, alt:, ..pins)
+ a screenshot with numbered pins, their labels as text
+ beside it. src from the repository root, size in
+ pixels, each pin ([label], x, y) in its pixels.
+ #annotated("/docs/site/media/themes/orchard.png", (640, 513),
+ alt: "a pardes window", ([the box], 8, 58))
+ mode-box(mark, unsaved:)
+ a pane's box as it looks: " " normal, "^" insert,
+ "$" raw; unsaved: true fills it.
+ #mode-box("^") #mode-box(" ", unsaved: true)
theme-gallery() every theme as a picture, on the site's Themes page;
nothing on paper.
pairs(split:, ..cells)
diff --git a/docs/typ/cheatsheet-a4.pdf b/docs/typ/cheatsheet-a4.pdf
index d33db316..3b97b21a 100644
--- a/docs/typ/cheatsheet-a4.pdf
+++ b/docs/typ/cheatsheet-a4.pdf
@@ -1575,12 +1575,12 @@ x�}��Kq�?�U�X 94%MQKS��N�#Ԧ��Q�v��B�ˡ�h�Fk ������!
endstream
endobj
426 0 obj
-<</Creator(Typst 0.15.1)/ModDate(D:20261001134538-03'00)/CreationDate(D:20261001134538-03'00)>>
+<</Creator(Typst 0.15.1)/ModDate(D:20261001140600-03'00)/CreationDate(D:20261001140600-03'00)>>
endobj
427 0 obj
<</Length 996/Type/Metadata/Subtype/XML>>
stream
-<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="xmp-writer"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" xmlns:pdf="http://ns.adobe.com/pdf/1.3/" ><xmp:CreatorTool>Typst 0.15.1</xmp:CreatorTool><dc:language><rdf:Bag><rdf:li>en</rdf:li></rdf:Bag></dc:language><xmp:ModifyDate>2026-10-01T13:45:38-03:00</xmp:ModifyDate><xmp:CreateDate>2026-10-01T13:45:38-03:00</xmp:CreateDate><xmpTPg:NPages>1</xmpTPg:NPages><dc:format>application/pdf</dc:format><xmpMM:InstanceID>Wg9IHgE89UC5Ijb2EbcBRQ==</xmpMM:InstanceID><xmpMM:DocumentID>Wg9IHgE89UC5Ijb2EbcBRQ==</xmpMM:DocumentID><xmpMM:RenditionClass>proof</xmpMM:RenditionClass><pdf:PDFVersion>1.7</pdf:PDFVersion></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="r"?>
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?><x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="xmp-writer"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:xmp="http://ns.adobe.com/xap/1.0/" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/" xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/" xmlns:pdf="http://ns.adobe.com/pdf/1.3/" ><xmp:CreatorTool>Typst 0.15.1</xmp:CreatorTool><dc:language><rdf:Bag><rdf:li>en</rdf:li></rdf:Bag></dc:language><xmp:ModifyDate>2026-10-01T14:06:00-03:00</xmp:ModifyDate><xmp:CreateDate>2026-10-01T14:06:00-03:00</xmp:CreateDate><xmpTPg:NPages>1</xmpTPg:NPages><dc:format>application/pdf</dc:format><xmpMM:InstanceID>X3jRtdT4EfXhbAEUSxNMOw==</xmpMM:InstanceID><xmpMM:DocumentID>X3jRtdT4EfXhbAEUSxNMOw==</xmpMM:DocumentID><xmpMM:RenditionClass>proof</xmpMM:RenditionClass><pdf:PDFVersion>1.7</pdf:PDFVersion></rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="r"?>
endstream
endobj
428 0 obj
@@ -2018,7 +2018,7 @@ xref
0000109208 00000 n
0000110281 00000 n
trailer
-<</Size 429/Root 428 0 R/Info 426 0 R/ID[(Wg9IHgE89UC5Ijb2EbcBRQ==)(Wg9IHgE89UC5Ijb2EbcBRQ==)]>>
+<</Size 429/Root 428 0 R/Info 426 0 R/ID[(X3jRtdT4EfXhbAEUSxNMOw==)(X3jRtdT4EfXhbAEUSxNMOw==)]>>
startxref
110468
%%EOF \ No newline at end of file
diff --git a/docs/typ/guide.typ b/docs/typ/guide.typ
index 1505afbc..7cf2b38f 100644
--- a/docs/typ/guide.typ
+++ b/docs/typ/guide.typ
@@ -12,11 +12,12 @@ terminal, a PDF or an image. The keys are helix's, and every session is a
virtual filesystem, served over 9P, for scripts.
#annotated("/docs/site/media/themes/orchard.png", (640, 513),
- ([the workspace tag: words for the whole session], 330, 11),
+ alt: "a pardes window: workspace tag, column tag, a file pane, a terminal and a diff",
+ ([the workspace tag: words for the whole session], -22, 11),
([a column's tag], 400, 34),
([a pane's tag: its path, then its words], 560, 58),
([the box: the pane's mode, and the handle to drag it], 8, 58),
- ([the body: here a file, below it a terminal], 500, 128),
+ ([the body: here a file, below it a terminal], 200, 104),
([the scrollbar: how much of the body is in view], 8, 160),
)
diff --git a/docs/typ/install.typ b/docs/typ/install.typ
index c293b3fb..9758777d 100644
--- a/docs/typ/install.typ
+++ b/docs/typ/install.typ
@@ -7,7 +7,7 @@
let d = project.download
for f in d.files {
let name = f.url.split("/").last()
- [pardes #d.version, #f.what:]
+ [pardes #d.version, #f.what (#str(calc.round(f.bytes / 1000000)) MB):]
cmd("curl -LO " + f.url + "\necho '" + f.sha256 + " " + name + "' | sha256sum -c\nchmod +x " + name + " && mv " + name + " ~/.local/bin/" + f.program)
}
[The checksums are also in #link(d.sums)[`SHA256SUMS`].]
diff --git a/docs/typ/style.typ b/docs/typ/style.typ
index c7e91495..23bdab3c 100644
--- a/docs/typ/style.typ
+++ b/docs/typ/style.typ
@@ -308,6 +308,7 @@
(name: "git.0x4200.cafe", web: "https://git.0x4200.cafe/pardes", clone: "https://git.0x4200.cafe/pardes.git"),
),
license: "ISC",
+ changes: "https://git.sr.ht/~gbrls/pardes/tree/main/item/src/CHANGELOG.md",
// built binaries to download: the version they are, where the
// checksums are, and a file per program (install.typ shows them)
download: (
@@ -315,10 +316,10 @@
sums: "https://pub-66384ba880784f119ce95123b92fa5c6.r2.dev/SHA256SUMS",
files: (
(program: "pardes", what: "the terminal build, static, any x86_64 Linux",
- url: "https://pub-66384ba880784f119ce95123b92fa5c6.r2.dev/pardes-x86_64-linux-static",
+ url: "https://pub-66384ba880784f119ce95123b92fa5c6.r2.dev/pardes-x86_64-linux-static", bytes: 62034424,
sha256: "9db75765a8ad2fc881164b98ff69f0d34ded582304428c28aee7f87ccb211061"),
(program: "pardes-gui", what: "the window, x86_64 Linux with glibc 2.29 or later; it loads Wayland or X11 and GL or Vulkan from the system",
- url: "https://pub-66384ba880784f119ce95123b92fa5c6.r2.dev/pardes-gui-x86_64-linux",
+ url: "https://pub-66384ba880784f119ce95123b92fa5c6.r2.dev/pardes-gui-x86_64-linux", bytes: 66620552,
sha256: "d788213d14e69ee32348cee9b707b046c71c208c6436d84988e76aaf117095da"),
),
),
@@ -534,17 +535,18 @@
/// the repository root (the site serves it from the same path under
/// docs/site/); `size` is its pixel (width, height); each pin is (label,
/// x, y) in pixels of that image.
-#let annotated(src, size, ..pins) = {
+#let annotated(src, size, alt: "", ..pins) = {
let (w, h) = size
let pins = pins.pos()
context if html-out() {
let web = src.trim("/docs/site/", at: start)
html.elem("figure", attrs: (class: "annotated"), {
html.elem("div", attrs: (class: "frame"), {
- html.elem("img", attrs: (src: web, alt: "", width: str(w), height: str(h)))
+ html.elem("img", attrs: (src: web, alt: alt, width: str(w), height: str(h)))
for (i, (label, x, y)) in pins.enumerate() {
html.elem("span", attrs: (class: "pin", "aria-hidden": "true",
- style: "left:" + str(calc.round(100 * x / w, digits: 2)) + "%;top:" + str(calc.round(100 * y / h, digits: 2)) + "%"), str(i + 1))
+ // repr, not str: str writes a negative with U+2212, which CSS rejects
+ style: "left:" + repr(calc.round(100 * x / w, digits: 2)) + "%;top:" + repr(calc.round(100 * y / h, digits: 2)) + "%"), str(i + 1))
}
})
html.elem("ol", attrs: (class: "pins"), for (label, x, y) in pins { html.elem("li", label) })
@@ -556,7 +558,7 @@
align(center + horizon, text(size: 0.75em, weight: "bold", fill: white, str(n))))
block(breakable: false, grid(columns: (width, 1fr), column-gutter: 1.2em, align: horizon,
box(width: width, height: h * k, {
- place(top + left, image(src, width: width))
+ place(top + left, image(src, width: width, alt: alt))
for (i, (label, x, y)) in pins.enumerate() {
place(top + left, dx: x * k - 0.625em, dy: y * k - 0.625em, pin(i + 1))
}
@@ -565,3 +567,20 @@
))
}
}
+
+/// A pane's box as it looks: blank in normal mode, `^` in insert, `$` raw
+/// (a terminal's own input); `unsaved: true` fills it, as an unsaved
+/// file's is. In HTML a span the stylesheet draws, its mode said in words.
+#let mode-box(mark, unsaved: false) = {
+ let mode = if mark == "^" { "insert" } else if mark == "$" { "raw" } else { "normal" }
+ context if html-out() {
+ html.elem("span", attrs: (class: "mode-box" + if unsaved { " unsaved" } else { "" },
+ role: "img", "aria-label": mode + " mode box" + if unsaved { ", unsaved" } else { "" }), mark)
+ } else {
+ box(width: 1.05em, height: 1.05em, baseline: 0.15em, radius: 1pt,
+ fill: if unsaved { palette.col-grip } else { palette.tag-bg },
+ stroke: 0.7pt + palette.grip,
+ align(center + horizon, text(font: mono, size: 0.8em, weight: "bold",
+ fill: if unsaved { white } else { palette.ink }, mark)))
+ }
+}