readme ready for prod
This commit is contained in:
308
README.md
308
README.md
@@ -1,308 +1,46 @@
|
|||||||
# tui-pages website
|
# tui-pages website
|
||||||
|
|
||||||
Static marketing site for the [`tui-pages`](https://gitlab.com/filipriec/tui-pages) Rust crate.
|
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.
|
No build step — it's plain HTML/CSS/JS.
|
||||||
|
|
||||||
## Stack
|
## Build & serve
|
||||||
|
|
||||||
| 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
|
```bash
|
||||||
# Just open the file
|
make serve # serve the working tree on http://localhost:8000
|
||||||
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 size # report file sizes
|
||||||
make validate # quick HTML syntax check (search for unclosed tags)
|
make validate # quick HTML syntax check
|
||||||
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
|
Just edit the `.html` / `static/` files and refresh.
|
||||||
> `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
|
## ASCII video background
|
||||||
|
|
||||||
The page background is an **animated chafa ASCII playback of a video**, running
|
The full-page background is a video re-rendered into animated ASCII. `ffmpeg`
|
||||||
at 12 fps behind the entire page. `ffmpeg` extracts frames from a video you
|
extracts frames, `chafa` turns each into an ASCII grid, and
|
||||||
provide; `chafa` converts each frame to an 80 × 24 ASCII grid; a bundler
|
`tools/build_mountain_js.py` bundles them into `static/js/mountain.js`, which
|
||||||
(`tools/build_mountain_js.py`) concatenates all grids into a single JS array
|
the page plays back.
|
||||||
(`static/js/mountain.js`). A tiny cycler (`static/js/mountain-bg.js`) swaps
|
|
||||||
`textContent` on a single `<pre>` every ~83 ms.
|
|
||||||
|
|
||||||
### Swap the video
|
### Render a video
|
||||||
|
|
||||||
1. Drop a `.mp4` in `video/`:
|
1. Drop your clip in `video/`, e.g. `video/nature1.mp4`.
|
||||||
|
2. Render + bundle (needs `ffmpeg` + `chafa`, so run in the nix shell):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp ~/Videos/my-clip.mp4 video/myclip.mp4
|
nix develop -c make video NAME=nature1 SIZE=480x144 SCALE=1920
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Build it into the site (run inside the nix dev shell for ffmpeg + chafa):
|
3. `make serve` and hard-refresh the browser (Ctrl+Shift+R).
|
||||||
|
|
||||||
```bash
|
- `NAME` — the file name without `.mp4`.
|
||||||
nix develop -c make video NAME=myclip
|
- `SIZE` — ASCII grid `cols×rows`. Bigger = sharper + heavier bundle. Keep
|
||||||
```
|
the ~3.33:1 ratio (e.g. `80x24`, `160x48`, `240x72`, `480x144`).
|
||||||
|
- `SCALE` — source pixel width fed to chafa; scale it up with `SIZE`.
|
||||||
|
|
||||||
This runs three steps:
|
After changing `SIZE`, set a matching `font-size` in `static/css/ascii.css`
|
||||||
- `make video-frames NAME=myclip` — `ffmpeg` extracts PNGs at 12 fps into `build/myclip/`, then `chafa -s 80x24 --symbols ascii` produces ASCII for each
|
(it halves each time you double the grid) so the ASCII fills the screen.
|
||||||
- `make video-bundle NAME=myclip` — bundles all `frame-*.txt` into `static/js/mountain.js`
|
|
||||||
- the wrapper `make video` does both
|
|
||||||
|
|
||||||
3. Serve and view:
|
Current setup: `480x144`, font-size `max(0.35vw, 0.695vh)`.
|
||||||
|
|
||||||
```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
|
## License
|
||||||
|
|
||||||
The website source is MIT-licensed. The terminal mockup SVGs in `static/img/`
|
MIT. Crate: <https://gitlab.com/filipriec/tui-pages>.
|
||||||
are hand-drawn and original. The crate itself is at
|
|
||||||
<https://gitlab.com/filipriec/tui-pages>.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user