aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-30 14:18:57 +0200
committeruser <user@clank>2026-08-30 14:18:57 +0200
commita4e1bb12760c2b9768e2917e41ac4600dcdf3156 (patch)
tree51bdafee58c62a542dd2e1f295a30f71bc824798
parentweb: sloshcast (diff)
downloadslosh-a4e1bb12760c2b9768e2917e41ac4600dcdf3156.tar.gz
slosh-a4e1bb12760c2b9768e2917e41ac4600dcdf3156.tar.xz
slosh-a4e1bb12760c2b9768e2917e41ac4600dcdf3156.zip
web: a whole lot of html/css wrangling
-rw-r--r--web/README.md8
-rw-r--r--web/index.html89
-rwxr-xr-xweb/install.sh134
-rw-r--r--web/recordings/demos/config.py6
-rw-r--r--web/recordings/demos/reflow.py15
-rw-r--r--web/recordings/demos/scripting.py14
-rw-r--r--web/recordings/demos/shaders.py7
-rw-r--r--web/site.css119
8 files changed, 278 insertions, 114 deletions
diff --git a/web/README.md b/web/README.md
index f720bd6..9eb599b 100644
--- a/web/README.md
+++ b/web/README.md
@@ -48,6 +48,10 @@ the one condition the webdemo README already states: `.wasm` must be served as
- Every feature in the tour has its recording now; what remains on the
recordings side (a detach demo, storyline polish, static no-JS posters) is
in `recordings/PLAN.md`.
-- `install.sh` clones `https://slosh.foo/src` (override: `SLOSH_REPO=`). Pin
- the real public repo URL once it exists — same for the `src` links in
+- `install.sh` downloads the prebuilt binary from `https://slosh.foo/dist/`
+ (override: `SLOSH_DIST=`), picking the newest version listed in its
+ `SHA256SUMS`. Once the GitHub repo is public, the front page and the
+ script can point at GitHub releases instead — same for the `src` links in
`index.html`.
+- The windows zips (`make release-windows`) are not uploaded to `/dist/` yet;
+ the windows install tab points there, so upload them with the next release.
diff --git a/web/index.html b/web/index.html
index adcb8f7..56dad19 100644
--- a/web/index.html
+++ b/web/index.html
@@ -10,18 +10,33 @@
</head>
<body>
-<nav class="nav" aria-label="Site">
- <a href="./" aria-current="page">slosh</a>
- <a href="demo/">demo</a>
- <a href="docs/">docs</a>
- <span class="nav-fill"></span>
- <a href="https://slosh.foo/src">src</a>
-</nav>
+<!-- The little logo: the big one, shrunk into the corner the stage's
+ v-centering leaves empty. It fades in when the hero scrolls away
+ (the observer at the bottom of the page) and is a road back up. -->
+<a class="logo-float" href="#" aria-label="slosh — back to top">
+<pre aria-hidden="true">
+▄██████▄ ███ ▄██████▄ ▄██████▄ ▄██ ▄██
+███ ▀▀ ███ ███ ███ ███ ▀▀ ███ ███
+▀██████▄ ███ ▄▄▄ ███ ███ ▀██████▄ ████████
+▄▄ ███ ███ ███ ███ ███ ▄▄ ███ ███ ███
+▀██████▀ ███████▀ ▀██████▀ ███████▀ ███ ███
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+</pre>
+</a>
<main>
<header class="hero">
- <h1>slosh</h1>
+<pre class="logo">
+▄██████▄ ███ ▄██████▄ ▄██████▄ ▄██ ▄██
+███ ▀▀ ███ ███ ███ ███ ▀▀ ███ ███
+▀██████▄ ███ ▄▄▄ ███ ███ ▀██████▄ ████████
+▄▄ ███ ███ ███ ███ ███ ▄▄ ███ ███ ███
+▀██████▀ ███████▀ ▀██████▀ ███████▀ ███ ███
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+</pre>
<p class="tagline">a modern terminal multiplexer written in C on top of
<a href="https://github.com/ghostty-org/ghostty">libghostty-vt</a>.</p>
@@ -139,8 +154,11 @@
<section id="install">
<h2>install</h2>
-<p>we aim to make installing `slosh` as easy as possible. we provide binaries for most popular platforms/architectures on our <a href="">github</a>.
-and we also provide some arch package repo's as well as a brew formula, check below!
+<p>Prebuilt binaries for the popular platforms live at
+<a href="dist/">slosh.foo/dist</a> — one static file each, checksummed. The
+tabs below are the short roads to one: an installer for linux, a brew formula
+for macos, a signed pacman repo for arch, a zip for windows. Building from
+source works too, but you never need to.</p>
<!-- Pure CSS tabs: hidden radios, labels drawn like a slosh tab strip,
one panel per platform. The only JavaScript is the preselect at the
@@ -166,10 +184,18 @@ and we also provide some arch package repo's as well as a brew formula, check be
<pre><code><span class="prompt">$</span> curl -fsSL https://slosh.foo/install.sh | sudo sh</code></pre>
<button class="copy" type="button" aria-label="Copy command">copy</button>
</div>
- <p class="aside">Piping a URL into a root shell should make you flinch —
- <a href="install.sh">read it first</a>, it is short: git,
- <a href="https://ziglang.org">zig</a> 0.16, one static binary into
- <code>/usr/local/bin</code>, nothing else touched.</p>
+ <div class="cmd" data-copy="curl -fsSL https://slosh.foo/install.sh | PREFIX=$HOME/.local sh">
+ <pre><code><span class="prompt">$</span> curl -fsSL https://slosh.foo/install.sh | PREFIX=$HOME/.local sh <span class="c"># no root</span></code></pre>
+ <button class="copy" type="button" aria-label="Copy command">copy</button>
+ </div>
+ <p class="aside">Piping a URL into a shell should make you flinch —
+ <a href="install.sh">read it first</a>, it is short: it picks the
+ prebuilt static binary for your architecture from
+ <a href="dist/">dist/</a>, checks it against the published SHA256SUMS,
+ and puts the one file in <code>PREFIX/bin</code>. The sudo in the first
+ line is only for writing to <code>/usr/local/bin</code>; the second line
+ needs no root at any step and lands it in <code>~/.local/bin</code>.
+ Nothing compiled, nothing else touched.</p>
</div>
<div class="os-panel" id="install-macos">
@@ -179,7 +205,9 @@ and we also provide some arch package repo's as well as a brew formula, check be
<button class="copy" type="button" aria-label="Copy commands">copy</button>
</div>
<p class="aside">No Homebrew? The linux tab's installer works here too —
- it only needs <a href="https://ziglang.org">zig</a> 0.16 and git.</p>
+ on Apple silicon it fetches the signed prebuilt binary from
+ <a href="dist/">dist/</a>, verifies it, and installs that. No Xcode, no
+ build.</p>
</div>
<div class="os-panel" id="install-arch">
@@ -199,13 +227,14 @@ Server = https://slosh.foo/arch/$arch
<div class="os-panel" id="install-windows">
<div class="cmd">
- <pre><code><span class="prompt">$</span> make -f Makefile.windows <span class="c"># from a mac or linux box</span></code></pre>
+ <pre><code><span class="prompt">&gt;</span> curl.exe -LO https://slosh.foo/dist/slosh-&lt;version&gt;-windows-x86_64.zip
+<span class="prompt">&gt;</span> tar xf slosh-*-windows-x86_64.zip <span class="c"># tar ships with Windows 10+</span></code></pre>
</div>
- <p class="aside">The same one binary: <code>slosh.exe</code>, Windows
- 10/11, x86-64 and ARM64, no runtime dependency beyond the OS. Panes are
+ <p class="aside">Pick the newest zip from <a href="dist/">dist/</a> —
+ x86-64 and ARM64, Windows 10/11. Inside is the same one binary:
+ <code>slosh.exe</code>, no runtime dependency beyond the OS. Panes are
ConPTYs, sessions are AF_UNIX sockets, everything on this page works
- there. Cross-compiled, then copied across —
- <a href="docs/windows.html">the details</a>.</p>
+ there — <a href="docs/windows.html">the details</a>.</p>
</div>
<div class="os-panel" id="install-source">
@@ -215,20 +244,15 @@ Server = https://slosh.foo/arch/$arch
<span class="prompt">$</span> make <span class="c"># about a second</span>
<span class="prompt">$</span> sudo install build/slosh /usr/local/bin/</code></pre>
</div>
- <p class="aside">Needs <a href="https://ziglang.org">zig</a> 0.16 and
- nothing else. No cmake, no autotools, no libraries. The result is one
+ <p class="aside">For the curious — the tabs above hand you this same
+ binary prebuilt. Needs <a href="https://ziglang.org">zig</a> 0.16 and
+ nothing else: no cmake, no autotools, no libraries. The result is one
static binary.</p>
</div>
</div>
</section>
</main>
-<footer class="foot">
- <span>MIT</span>
- <span>C on <a href="https://github.com/ghostty-org/ghostty">libghostty-vt</a></span>
- <span>one static binary</span>
-</footer>
-
<script src="recordings/player.js"></script>
<script>
// The scripts on the page: the copy buttons, the install preselect and the
@@ -247,6 +271,15 @@ for (const box of document.querySelectorAll(".cmd[data-copy]")) {
});
}
+// The little logo in the corner: on while the big one is off screen.
+const bigLogo = document.querySelector(".hero .logo");
+const littleLogo = document.querySelector(".logo-float");
+if (bigLogo && littleLogo && "IntersectionObserver" in window) {
+ new IntersectionObserver(([e]) => {
+ littleLogo.classList.toggle("show", !e.isIntersecting);
+ }).observe(bigLogo);
+}
+
// The install box opens on the platform reading it; the tabs are CSS.
const ua = navigator.userAgent;
const os = /Windows/i.test(ua) ? "windows" : /Mac/i.test(ua) ? "macos" : "linux";
diff --git a/web/install.sh b/web/install.sh
index ebf5a64..8e93e21 100755
--- a/web/install.sh
+++ b/web/install.sh
@@ -6,67 +6,125 @@
# brew tap sl0p/slosh https://git.sl0p.foo/homebrew-slosh.git
# brew install slosh
#
+# and on Arch there is a signed pacman repo -- https://slosh.foo/arch/.
+#
# You are reading this before running it, which is correct. What it does:
#
-# 1. checks for zig 0.16 and git,
-# 2. clones the source into a temporary directory,
-# 3. `make vendor && make` -- the vendored terminal core, then one static
-# binary, no other dependencies,
-# 4. installs build/slosh into /usr/local/bin (the sudo is for this step
+# 1. works out your OS and architecture,
+# 2. downloads the prebuilt static binary for it from
+# https://slosh.foo/dist/ (the newest release, or SLOSH_VERSION=),
+# 3. checks it against the SHA256SUMS published next to it,
+# 4. installs the one file into /usr/local/bin (the sudo is for this step
# and nothing else).
#
-# Nothing is downloaded except the git clone and what `make vendor` fetches
-# (the pinned libghostty-vt). No package manager, no config written, nothing
-# started. Uninstall is `rm /usr/local/bin/slosh`.
+# No root at all is also fine: pipe into `PREFIX=$HOME/.local sh` instead of
+# `sudo sh` and the binary lands in ~/.local/bin, owned by you.
+#
+# Nothing is compiled, no package manager is touched, no config is written,
+# nothing is started. Uninstall is `rm $PREFIX/bin/slosh`.
+#
+# Windows is not this script's job: slosh runs there natively as slosh.exe --
+# grab the zip from https://slosh.foo/dist/ and see docs/windows.md.
#
-# Windows is not this script's job: slosh runs there natively, but as a
-# cross-compiled slosh.exe rather than a shell-script install -- see
-# docs/windows.md in the repo.
+# Prefer to build from source? It is three commands and one dependency
+# (zig 0.16): clone https://slosh.foo/src, `make vendor`, `make`.
set -eu
-REPO="${SLOSH_REPO:-https://slosh.foo/src}" # TODO: pin to the public repo URL
+BASE="${SLOSH_DIST:-https://slosh.foo/dist}"
PREFIX="${PREFIX:-/usr/local}"
-ZIG="${ZIG:-zig}"
say() { printf ' %s\n' "$*"; }
fail() { printf 'install.sh: %s\n' "$*" >&2; exit 1; }
-case "$(uname -s 2>/dev/null || true)" in
- *MINGW*|*MSYS*|*CYGWIN*)
- fail "this builds the POSIX binary; Windows runs slosh.exe -- see docs/windows.md" ;;
-esac
-
-command -v git >/dev/null 2>&1 || fail "git is required"
-
-# zig 0.16, the one thing the build wants. Also try the location the
-# Makefile's own docs suggest.
-if ! command -v "$ZIG" >/dev/null 2>&1; then
- if [ -x "$HOME/zig-0.16.0/zig" ]; then
- ZIG="$HOME/zig-0.16.0/zig"
+fetch() { # fetch URL [outfile] -- curl where it exists, wget where it doesn't
+ if command -v curl >/dev/null 2>&1; then
+ if [ $# -gt 1 ]; then curl -fsSL -o "$2" "$1"; else curl -fsSL "$1"; fi
+ elif command -v wget >/dev/null 2>&1; then
+ if [ $# -gt 1 ]; then wget -qO "$2" "$1"; else wget -qO- "$1"; fi
else
- fail "zig 0.16 not found -- https://ziglang.org/download, or ZIG=/path/to/zig"
+ fail "neither curl nor wget found"
fi
-fi
-case "$("$ZIG" version 2>/dev/null)" in
- 0.16.*) ;;
- *) fail "zig 0.16 is required, found $("$ZIG" version 2>/dev/null || echo nothing)" ;;
+}
+
+sha256_of() { # portable: coreutils on linux, shasum on mac, openssl anywhere
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
+ elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1
+ elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 "$1" | sed 's/.*= *//'
+ else fail "no sha256sum, shasum or openssl here -- cannot verify the download"
+ fi
+}
+
+# The platform, spelled the way the release artifacts spell it.
+os="$(uname -s 2>/dev/null || true)"
+arch="$(uname -m 2>/dev/null || true)"
+case "$os" in
+ Linux)
+ case "$arch" in
+ x86_64|amd64) plat=linux-x86_64; ext=tar.gz ;;
+ aarch64|arm64) plat=linux-aarch64; ext=tar.gz ;;
+ *) fail "no prebuilt binary for linux/$arch yet -- building from
+ source is short (zig 0.16, then make vendor && make): https://slosh.foo/src" ;;
+ esac ;;
+ Darwin)
+ case "$arch" in
+ arm64) plat=macos-arm64; ext=zip ;;
+ *) fail "no prebuilt binary for intel macs yet -- use Homebrew
+ (brew tap sl0p/slosh https://git.sl0p.foo/homebrew-slosh.git && brew install slosh)" ;;
+ esac ;;
+ *MINGW*|*MSYS*|*CYGWIN*)
+ fail "Windows runs slosh.exe -- download the zip from $BASE/ (see docs/windows.md)" ;;
+ *)
+ fail "unrecognized platform $os/$arch -- the binaries live at $BASE/" ;;
esac
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT INT TERM
-say "cloning $REPO"
-git clone --quiet --depth 1 "$REPO" "$tmp/slosh"
+# The version: whatever SHA256SUMS says is newest for this platform, unless
+# the caller pinned one. SHA256SUMS is also the checksum check below, so one
+# fetch serves twice.
+say "fetching $BASE/SHA256SUMS"
+fetch "$BASE/SHA256SUMS" "$tmp/SHA256SUMS"
+if [ -n "${SLOSH_VERSION:-}" ]; then
+ ver="${SLOSH_VERSION#v}"
+else
+ ver="$(sed -n "s/^.* slosh-\(.*\)-$plat\.$ext\$/\1/p" "$tmp/SHA256SUMS" \
+ | sort -V | tail -1)"
+ [ -n "$ver" ] || fail "no $plat build listed in $BASE/SHA256SUMS"
+fi
+
+stem="slosh-$ver-$plat"
+asset="$stem.$ext"
+
+say "downloading $BASE/$asset"
+fetch "$BASE/$asset" "$tmp/$asset"
-say "building (make vendor, once; then make all)"
-make -C "$tmp/slosh" ZIG="$ZIG" vendor
-# `all`, spelled out: the default goal is the help text, so a bare `make` here
-# would print it cheerfully and install whatever build/slosh happened to exist.
-make -C "$tmp/slosh" ZIG="$ZIG" all
+want="$(sed -n "s/^\([0-9a-f]\{64\}\) $asset\$/\1/p" "$tmp/SHA256SUMS" | tail -1)"
+[ -n "$want" ] || fail "$asset is not in SHA256SUMS -- refusing to install it"
+got="$(sha256_of "$tmp/$asset")"
+[ "$got" = "$want" ] || fail "checksum mismatch for $asset
+ expected $want
+ got $got"
+say "sha256 verified"
+
+case "$ext" in
+ tar.gz) tar -xzf "$tmp/$asset" -C "$tmp" ;;
+ zip) unzip -q "$tmp/$asset" -d "$tmp" ;;
+esac
+[ -f "$tmp/$stem/slosh" ] || fail "$asset did not contain $stem/slosh"
say "installing to $PREFIX/bin/slosh"
install -d "$PREFIX/bin"
-install -m 755 "$tmp/slosh/build/slosh" "$PREFIX/bin/slosh"
+install -m 755 "$tmp/$stem/slosh" "$PREFIX/bin/slosh"
+
+# The one way a non-root install goes quietly wrong: ~/.local/bin exists now
+# but the shell does not look there. Say so instead of letting `slosh` print
+# "command not found" a minute after "done".
+case ":$PATH:" in
+ *":$PREFIX/bin:"*) ;;
+ *) say "note: $PREFIX/bin is not in your PATH -- add it, e.g.:"
+ say ' export PATH="'"$PREFIX/bin"':$PATH"' ;;
+esac
say "done. run: slosh"
diff --git a/web/recordings/demos/config.py b/web/recordings/demos/config.py
index b8509ab..760ca59 100644
--- a/web/recordings/demos/config.py
+++ b/web/recordings/demos/config.py
@@ -23,15 +23,15 @@ shutil.copyfile(THEME, cfg)
r = Recorder(["/bin/sh"], cols=100, rows=28, title="make it yours", config=cfg, env=ENV)
-# A neighbour with something on it, so geometry changes have a witness.
-r.api("split", dir="cols")
+# Something on the one pane, so geometry changes have a witness. No neighbour
+# beyond that: the editor C-a e opens is the only split, and gets half the
+# window to itself.
r.capture(force=True)
r.pause(0.6)
r.run(
"for i in 1 1 2 2 3 3 4 4 5 5 6 6; do"
" printf '\\033[4%sm%-40s\\033[0m\\n' $i ' '; done"
)
-r.api("focus", id=sorted(p["id"] for p in r.api("panes")["panes"])[0])
r.capture(dt=0.4)
r.pause(0.8)
diff --git a/web/recordings/demos/reflow.py b/web/recordings/demos/reflow.py
index 5287650..d9afc2a 100644
--- a/web/recordings/demos/reflow.py
+++ b/web/recordings/demos/reflow.py
@@ -10,11 +10,22 @@ import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-from castgen import Recorder, out_path
+from castgen import ROOT, Recorder, out_path
ENV = {"PS1": "$ ", "ENV": "/dev/null"}
-r = Recorder(["/bin/sh"], cols=100, rows=28, title="layouts that reflow", env=ENV)
+# Slate, not the house pink: each cast in the tour records under its own
+# theme, so scrolling to the next step reads as a scene change.
+THEME = os.path.join(ROOT, "contrib", "themes", "slate.kdl")
+
+r = Recorder(
+ ["/bin/sh"],
+ cols=100,
+ rows=28,
+ title="layouts that reflow",
+ config=THEME,
+ env=ENV,
+)
# Three named panes, built over the socket -- the demo is about the layout.
r.api("split", dir="cols")
diff --git a/web/recordings/demos/scripting.py b/web/recordings/demos/scripting.py
index 65ee4f5..a3a2ac8 100644
--- a/web/recordings/demos/scripting.py
+++ b/web/recordings/demos/scripting.py
@@ -15,10 +15,14 @@ import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-from castgen import Recorder, out_path
+from castgen import ROOT, Recorder, out_path
ENV = {"PS1": "$ ", "ENV": "/dev/null"}
+# Amber, not the house pink: each cast in the tour records under its own
+# theme, so scrolling to the next step reads as a scene change.
+THEME = os.path.join(ROOT, "contrib", "themes", "amber.kdl")
+
work = tempfile.mkdtemp(prefix="slosh-demo-")
# The left pane's shell: reads a line, answers like the socket does. The
@@ -63,7 +67,13 @@ with open(lay, "w") as f:
)
r = Recorder(
- ["/bin/sh"], cols=100, rows=28, title="built to be driven", env=ENV, layout=lay
+ ["/bin/sh"],
+ cols=100,
+ rows=28,
+ title="built to be driven",
+ config=THEME,
+ env=ENV,
+ layout=lay,
)
r.capture(force=True, settle_ms=150)
diff --git a/web/recordings/demos/shaders.py b/web/recordings/demos/shaders.py
index 889b0de..f7ce3ac 100644
--- a/web/recordings/demos/shaders.py
+++ b/web/recordings/demos/shaders.py
@@ -16,10 +16,15 @@ import sys
import tempfile
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-from castgen import ROOT, THEME, Recorder, out_path
+from castgen import ROOT, Recorder, out_path
ENV = {"PS1": "$ ", "ENV": "/dev/null"}
+# Mono, not the house pink: this demo is about colour passes, so the base
+# theme is the one without colour -- every hue on screen is a shader's. It
+# also gives this cast its own look in the tour's theme rotation.
+THEME = os.path.join(ROOT, "contrib", "themes", "mono.kdl")
+
work = tempfile.mkdtemp(prefix="slosh-demo-")
cfg = os.path.join(work, "config.kdl")
with open(cfg, "w") as f:
diff --git a/web/site.css b/web/site.css
index aba3bcd..70d7d7c 100644
--- a/web/site.css
+++ b/web/site.css
@@ -13,6 +13,10 @@
--faint: #45454a;
--pink: #ff5fd7;
--dead: #ff875f;
+ /* The feature tour's geometry, up here because the floating logo (a fixed
+ * element, outside the tour) centres itself on the tour's stage column. */
+ --tour-w: min(94vw, 88rem);
+ --tour-gap: clamp(1.5rem, 4vw, 4.5rem);
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas,
"DejaVu Sans Mono", monospace;
--sans: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
@@ -45,28 +49,41 @@ kbd {
font-size: 0.85em;
}
-/* ---- the nav -------------------------------------------------------- */
+/* ---- the floating logo ---------------------------------------------- */
+/* The hero logo again, small, in the corner the stage's v-centering leaves
+ * empty on desktop. Faded in by the observer in index.html once the big one
+ * is off screen; a link back to the top. Hidden on one-column screens,
+ * where that corner is content, not air. */
-.nav {
- position: sticky;
- top: 0;
- z-index: 3;
- display: flex;
- gap: 1.5em;
- overflow-x: auto;
- padding: 0 1.25rem;
- border-bottom: 1px solid var(--panel);
- background: var(--bg);
+.logo-float {
+ position: fixed;
+ top: 13.8rem;
+ /* Centred on the stage column: the tour is layout-centred and the stage
+ * is the 3fr of its 3fr/2fr split, so the terminal's midline sits at
+ * 50% - tourW/2 + (tourW - gap) * 3/10. 50%, not 50vw: a fixed element's
+ * percentages exclude the scrollbar, like the layout the tour centres in;
+ * vw would drag the logo half a scrollbar to the right. The translateX
+ * does the logo's own half. */
+ left: calc(50% - var(--tour-w) / 2 + (var(--tour-w) - var(--tour-gap)) * 0.3);
+ z-index: 4;
+ color: var(--pink);
+ opacity: 0;
+ transform: translateX(-50%) translateY(-0.4rem);
+ transition: opacity 0.25s, transform 0.25s;
+ pointer-events: none;
+}
+.logo-float.show { opacity: 1; transform: translateX(-50%); pointer-events: auto; }
+.logo-float:hover { color: var(--fg); }
+.logo-float pre {
+ margin: 0;
font-family: var(--mono);
- font-size: 13px;
- line-height: 2.6;
- white-space: nowrap;
+ font-size: 12px;
+ line-height: 1em;
}
-.nav a { color: var(--dim); }
-.nav a:hover { color: var(--pink); text-decoration: none; }
-.nav a[aria-current] { color: var(--fg); }
-.nav-fill { flex: 1; }
+@media (max-width: 54rem) {
+ .logo-float { display: none; }
+}
/* ---- the hero ------------------------------------------------------- */
@@ -81,6 +98,11 @@ main {
text-align:center !important;
}
+.hero .logo {
+ color: var(--pink);
+ line-height: 1em;
+}
+
.hero h1 {
margin: 0;
color: var(--pink);
@@ -235,13 +257,33 @@ section > p:last-child { margin-bottom: 0; }
* recordings are 100 columns and want the room. */
.scrolly {
- --tour-w: min(94vw, 88rem);
width: var(--tour-w);
margin-left: calc((100% - var(--tour-w)) / 2);
display: grid;
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
- gap: clamp(1.5rem, 4vw, 4.5rem);
+ gap: var(--tour-gap);
+ /* Tuck the tour up under the hero. The stage's 100vh box centres the
+ * terminal, so before it pins, everything above the terminal is air —
+ * about 45vh minus half the player (≤ ~500px tall at this column width),
+ * minus the optical lift. Overlap the hero by that much, less ~2rem of
+ * breathing room; min() keeps it at 0 on viewports short enough that the
+ * air runs out. Pinned geometry is scroll-invariant, so alignment inside
+ * the tour is untouched. */
+ margin-top: min(0rem, 17.5rem - 45vh);
+ /* ...and the same at the exit, where the air below the terminal is larger
+ * still (the optical lift rides it high — ~54.5vh minus half the player).
+ * This collapses with the next section's 3.5rem margin-top, which the
+ * constant accounts for; the result is ~2.75rem from the terminal's final
+ * resting bottom to the install heading at any viewport height. */
+ margin-bottom: min(0rem, 14.5rem - 54.5vh);
+ /* The overlapped regions are transparent air, but they would still sit on
+ * top of the hero's buttons above and the install tabs below — let clicks
+ * fall through, and switch hit-testing back on for the things inside that
+ * want it. */
+ pointer-events: none;
}
+.stage-pin [data-sloshcast],
+.scrolly-steps .step { pointer-events: auto; }
.stage-pin {
position: sticky;
@@ -251,11 +293,25 @@ section > p:last-child { margin-bottom: 0; }
flex-direction: column;
justify-content: center;
align-items: center;
+ /* Sit a touch above true centre: the steps on the right centre their text
+ * on the viewport, and against them a dead-centred terminal reads as low
+ * (the tab strip weights its top). border-box, so this lifts the centred
+ * content rather than growing the 100vh box. */
+ padding-bottom: clamp(2.5rem, 9vh, 6rem);
}
.stage-pin [data-sloshcast] { display: none; }
.stage-pin .stage-active { display: block; }
+/* Breathing room at both ends of the steps column. Without it the column is
+ * exactly steps*50vh tall and the sticky stage has no travel margin: the
+ * terminal is still riding below centre when the first step reaches the
+ * middle of the viewport, and has already been dragged up past centre by the
+ * container's bottom edge when the last one does. 25vh each side means the
+ * stage is pinned (centred) from before the first step centres until after
+ * the last step does. */
+.scrolly-steps { padding: 25vh 0; }
+
.step {
min-height: 50vh;
display: flex;
@@ -300,10 +356,10 @@ section > p:last-child { margin-bottom: 0; }
* beneath it. The sticky moves up to .scrolly-stage here: in block layout
* .stage-pin's parent is only as tall as the player, so a sticky inside it
* has nowhere to stick; .scrolly-stage is bounded by the whole tour. */
- .scrolly { display: block; }
+ .scrolly { display: block; margin-top: 0; margin-bottom: 0; pointer-events: auto; }
.scrolly-stage {
position: sticky;
- top: 2.2rem; /* below the nav */
+ top: 0;
z-index: 2;
padding: 0.6rem 0;
background: var(--bg);
@@ -313,6 +369,9 @@ section > p:last-child { margin-bottom: 0; }
height: auto;
padding: 0;
}
+ /* One column: the stage sticks above the steps, so the travel margin is
+ * pointless air here. */
+ .scrolly-steps { padding: 0; }
.step { min-height: 50vh; }
.step:first-child { padding-top: 1.5rem; }
}
@@ -353,22 +412,6 @@ section > p:last-child { margin-bottom: 0; }
.doclist a { font-family: var(--mono); font-size: 14px; }
.doclist span { color: var(--faint); }
-/* ---- the footer ----------------------------------------------------- */
-
-.foot {
- display: flex;
- flex-wrap: wrap;
- gap: 0 1.5rem;
- padding: 0.4rem 1.25rem;
- border-top: 1px solid var(--panel);
- color: var(--faint);
- font-family: var(--mono);
- font-size: 12.5px;
- line-height: 2;
-}
-
-.foot a { color: var(--dim); }
-
/* ---- small screens -------------------------------------------------- */
@media (max-width: 44rem) {