Files
Tui-pages-website/README.md
2026-06-01 23:40:17 +02:00

309 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# tui-pages website
Static marketing site for the [`tui-pages`](https://gitlab.com/filipriec/tui-pages) Rust crate.
No build step required — open `index.html` in a browser and you're done.
## Stack
| Layer | Tech | Source |
| --- | --- | --- |
| HTML | Hand-written semantic | `*.html` |
| CSS framework | Tailwind CSS v3 (Play CDN) | runtime |
| Components | DaisyUI v4 (prebuilt CSS) | runtime |
| Interactivity | HTMX 2 | runtime |
| Light JS | Alpine.js 3 | runtime |
| Code highlight | highlight.js 11 (Rust, TOML, Bash) | runtime |
| Fonts | Inter + JetBrains Mono (Google Fonts) | runtime |
| Icons | Lucide (inline SVG) | hand-written |
| Page transitions | View Transitions API (native) | n/a |
Everything is loaded from public CDNs. The only thing served from this repo is
the HTML, our small `static/css/site.css` layer, and the SVG assets in
`static/img/`.
## Local development
```bash
# Just open the file
xdg-open index.html # Linux
open index.html # macOS
# Or serve it (recommended — gives you a stable URL for htmx)
python3 -m http.server 8000
# then visit http://localhost:8000
```
The `Makefile` wraps the Python server and a few convenience commands:
```bash
make serve # python3 -m http.server 8000 (serves the WORKING TREE — use this while iterating)
make size # report file sizes
make validate # quick HTML syntax check (search for unclosed tags)
make tidy # run html-tidy on every .html (warnings only)
make video NAME=foo # regenerate the ASCII background from video/foo.mp4
```
> **Note:** `make serve` reads from the working tree (so it picks up the latest
> `static/js/mountain.js` after a `make video`). For a production build, use
> `nix run .#serve` — but note that the Nix build only includes **git-tracked**
> files, so you must `git add` your new `mountain.js` (and any other new files)
> before `nix run .#serve` will pick them up.
## Animated ASCII video background
The page background is an **animated chafa ASCII playback of a video**, running
at 12 fps behind the entire page. `ffmpeg` extracts frames from a video you
provide; `chafa` converts each frame to an 80 × 24 ASCII grid; a bundler
(`tools/build_mountain_js.py`) concatenates all grids into a single JS array
(`static/js/mountain.js`). A tiny cycler (`static/js/mountain-bg.js`) swaps
`textContent` on a single `<pre>` every ~83 ms.
### Swap the video
1. Drop a `.mp4` in `video/`:
```bash
cp ~/Videos/my-clip.mp4 video/myclip.mp4
```
2. Build it into the site (run inside the nix dev shell for ffmpeg + chafa):
```bash
nix develop -c make video NAME=myclip
```
This runs three steps:
- `make video-frames NAME=myclip` — `ffmpeg` extracts PNGs at 12 fps into `build/myclip/`, then `chafa -s 80x24 --symbols ascii` produces ASCII for each
- `make video-bundle NAME=myclip` — bundles all `frame-*.txt` into `static/js/mountain.js`
- the wrapper `make video` does both
3. Serve and view:
```bash
make serve
# → http://localhost:8000
```
### Helper targets
```bash
make video-list # show available videos in video/
make video-frames NAME=foo # only extract + chafa, skip the bundle
make video-bundle NAME=foo # only re-bundle from an existing build/foo/
```
### Tuning
- **Frame rate** — edit `Makefile` `fps=12` in the `video-frames` target. If
you change it, also update `TP_MOUNTAIN_FPS` at the bottom of
`static/js/mountain.js` (regenerated automatically each `make video`).
- **ASCII resolution** — `-s 80x24` in the `chafa` call. Wider/higher = larger
bundle, more detail.
- **Bundle size** — the resulting `static/js/mountain.js` is `~1.7 kB × N_FRAMES`
(333 kB for a 15 s clip at 12 fps). For long videos, drop `fps` or downscale
the source video first.
```
↓ chafa -s 80x24
build/mountain/frame-*.txt intermediate ASCII grids
static/js/mountain.js bundled 60-frame array (≈120 kB)
↓ requestAnimationFrame
DOM <pre.tp-mountain-frame> what you actually see, 12 fps
```
To re-render (you need `chafa` and `python3` on `$PATH`, or use `nix develop`):
```bash
make mountain # frames + bundle, end-to-end
make mountain-frames # just the PNG + ASCII frames
make mountain-bundle # just rebundle the JS array from existing frames
```
### How it's wired
- `static/js/mountain.js` exposes `window.TP_MOUNTAIN_FRAMES` (an array of
60 template strings) plus `TP_MOUNTAIN_N_FRAMES = 60` and
`TP_MOUNTAIN_FPS = 12`.
- `static/js/mountain-bg.js` mounts a `<pre class="tp-mountain-frame"
aria-hidden="true">` to `document.body`, sets the first frame as
`textContent`, then cycles with `requestAnimationFrame` and a
`setInterval`-style accumulator so frame drops don't desync.
- The script pauses when `document.hidden` is true (no wasted CPU on
backgrounded tabs) and respects `prefers-reduced-motion: reduce` by
showing a single mid-loop frame and not animating.
- Style lives in `static/css/ascii.css`:
- `position: fixed; inset: 0; z-index: 0` — full-bleed, behind page content
- `color: #f4a26b` (rust-orange) with a 0.45 opacity and a soft
`text-shadow` glow — picks up the brand colour
- `font-family: 'JetBrains Mono'` for the in-browser renderer
- A `::after` pseudo-element overlays a 2 px CRT scanline pattern that
ticks downward at 8 s/cycle
- Light theme (`data-theme="winter"`) drops the glow, dims the opacity
to 0.20, and switches the scanlines to white-on-transparent
- The scanline animation is killed inside
`@media (prefers-reduced-motion: reduce)`
The hero (`#hero`) is **not** part of the mountain background — it keeps
the original static `static/img/terminal-default.svg` mockup, exactly as
designed. The terminal sits on top of the mountain as a discrete card.
### Tuning the animation
The renderer is parameterized at the top of `tools/mountain.py`:
| Constant | Default | What it does |
| --- | --- | --- |
| `W, H` | 320 × 200 | Output PNG resolution |
| `N_FRAMES` | 60 | How long the loop is |
| `HORIZON_FRAC` | 0.45 | Sky height as a fraction of the image |
| `SEED` | deterministic | Heightmap random seed |
| `STEPS` | 80 | Forward steps per frame in camera space |
Bump `W`/`H` for sharper terrain, or `--symbols ascii+block+half` in the
chafa call inside `tools/build_mountain_js.py` for denser glyphs. Both
changes require a `make mountain` rebuild and a higher page weight
(mountain.js is currently ≈120 kB).
## Nix (optional, recommended)
A `flake.nix` is included that ships a reproducible dev shell, a buildable
package, a `serve` app, and an `html-tidy` check. You do **not** need Nix to
use this project — it's a convenience.
```bash
# Enter the dev shell (gives you: gnumake, python3, asciinema, html-tidy, …)
nix develop
# Build the whole site → ./result/ (16 files, ~150 kB, ready to upload)
nix build
ls result/
# Build + serve on http://localhost:8000 (or $PORT)
nix run .#serve
# Validate the HTML, format the flake
nix flake check
nix fmt
```
### What the flake provides
| Output | Command | What it does |
| --- | --- | --- |
| `packages.<sys>.default` | `nix build` | Assembles the static site into a single derivation |
| `apps.<sys>.serve` | `nix run .#serve` | Builds the package, then `python3 -m http.server` it |
| `devShells.<sys>.default` | `nix develop` | Shell with `make`, `python3`, `asciinema`, `tidy`, `curl` |
| `checks.<sys>.tidy` | `nix flake check` | Runs `html-tidy` over every `.html` in the built site |
| `formatter.<sys>` | `nix fmt` | `nixpkgs-fmt` for the flake itself |
### Why no Rust toolchain?
The crate lives in `../komp_ac/tui-pages/`. This repo only contains the
*static* website, so the flake intentionally omits `rust-overlay` and a
`rustPlatform` — they would add hundreds of MB to the shell closure for
nothing. When the site eventually gains a Rust backend, those inputs come
back.
### Why the per-system pattern?
Mirrored from the upstream Codex CLI flake so future contributors see a
shape they already recognise. `forAllSystems` is a one-liner that
guarantees `linux`/`darwin` × `x86_64`/`aarch64` parity without sprinkling
`if` branches across the file.
## Going to production
The CDN approach is fine for marketing pages. For better performance
(smaller CSS, no runtime Tailwind compile), swap the Play CDN for the
**Tailwind standalone CLI**:
```bash
# 1. Download the standalone CLI
# https://tailwindcss.com/blog/standalone-cli
# 2. Put the binary in ./bin/tailwindcss
# 3. Create src/site.css that imports Tailwind and DaisyUI:
# @import "tailwindcss";
# @plugin "daisyui";
# 4. Build
./bin/tailwindcss -i src/site.css -o static/css/site.css --minify
```
Then in `index.html` remove the Tailwind Play CDN `<script>` and the DaisyUI
`<link>`, and ensure `/static/css/site.css` is loaded last in `<head>`.
## File layout
```
tui-pages-web/
├── index.html # landing page
├── examples.html # examples gallery
├── 404.html # not found
├── robots.txt # search engine hints
├── sitemap.xml # sitemap
├── tools/
│ ├── mountain.py # pure-Python 3D-mountain PNG renderer
│ └── build_mountain_js.py # bundles 60 ASCII frames into mountain.js
├── static/
│ ├── css/
│ │ ├── site.css # our custom layer (small)
│ │ └── ascii.css # animation rules for the mountain background
│ ├── img/
│ │ ├── favicon.svg
│ │ ├── logo.svg
│ │ ├── og-image.svg # 1200x630 social share card
│ │ ├── terminal-default.svg
│ │ ├── terminal-canvas.svg
│ │ └── terminal-keybindings.svg
│ ├── js/
│ │ ├── mountain.js # 60-frame ASCII array (≈120 kB)
│ │ └── mountain-bg.js # requestAnimationFrame cycler
│ └── demos/ # (empty — drop asciinema .cast files here)
├── content/ # (empty — markdown for blog/changelog later)
├── flake.nix # Nix dev shell, package, app, checks
├── flake.lock # (generated — pinned nixpkgs)
├── Makefile
├── .gitignore
└── README.md
```
## Adding an asciinema demo to the hero
The hero currently shows the **static SVG mockup**
`static/img/terminal-default.svg`. To replace it with a real asciinema
recording (much more impressive, but requires a recorded cast):
1. Record one of the examples:
```bash
cd ../komp_ac/tui-pages/examples/default
asciinema rec ../../tui-pages-web/static/demos/intro.cast
# ... run the app for a few seconds, hit ctrl-d to stop
```
2. In `index.html`, replace the `<!-- Hero terminal mockup -->` block
with:
```html
<div class="tp-terminal">
<asciinema-player src="/static/demos/intro.cast" autoplay loop></asciinema-player>
</div>
```
3. Add the asciinema player to the `<head>`:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/asciinema-player@3.7.0/dist/bundle/asciinema-player.css">
<script src="https://cdn.jsdelivr.net/npm/asciinema-player@3.7.0/dist/bundle/asciinema-player.js" defer></script>
```
The 3D-mountain ASCII background is independent of the hero and stays as-is.
## License
The website source is MIT-licensed. The terminal mockup SVGs in `static/img/`
are hand-drawn and original. The crate itself is at
<https://gitlab.com/filipriec/tui-pages>.