Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# syntax=docker/dockerfile:1.7-labs
FROM debian:trixie-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
zsh \
git \
curl \
ca-certificates \
locales \
fastfetch \
chafa \
jp2a \
imagemagick \
gnupg \
sudo \
procps \
libelf1 \
zlib1g \
bsdextrautils \
pipx \
bat \
tmux \
make \
fzf \
python3-rich \
vim \
emacs-nox \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://repo.charm.sh/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/charm.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" > /etc/apt/sources.list.d/charm.list \
&& apt-get update && apt-get install -y --no-install-recommends gum \
&& rm -rf /var/lib/apt/lists/* \
&& sed -i 's/# en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen \
&& locale-gen
ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
ENV TERM=xterm-256color COLORTERM=truecolor
ENV CLICOLOR=1 CLICOLOR_FORCE=1 FORCE_COLOR=1

ARG ZELLIJ_VERSION=0.42.2
RUN ARCH=$(uname -m) && \
case "$ARCH" in \
x86_64) ZARCH=x86_64-unknown-linux-musl ;; \
aarch64) ZARCH=aarch64-unknown-linux-musl ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac && \
curl -fsSL "https://github.com/zellij-org/zellij/releases/download/v${ZELLIJ_VERSION}/zellij-${ZARCH}.tar.gz" \
| tar -xz -C /usr/local/bin && \
chmod +x /usr/local/bin/zellij

ARG YEET_CACHEBUST=0
RUN curl -fsSL https://yeet.cx | sh -s -- --no-phone-home

RUN useradd -m -s /bin/zsh you \
&& install -m 0644 -o you -g you /dev/null /var/log/yeetd.log \
&& echo 'you ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/you \
&& chmod 0440 /etc/sudoers.d/you \
&& ln -sf /usr/bin/batcat /usr/local/bin/bat
ENV ASSETS=/opt/logos
COPY --chown=you:you --chmod=755 opt/ /opt/
USER you
WORKDIR /home/you
ENV PATH=/home/you/.local/bin:$PATH

# Frogmouth: TUI markdown browser (used by Space toggle inside the running banger).
RUN pipx install frogmouth

# Skip zellij's first-run wizard / unlock-first mode
RUN mkdir -p /home/you/.config/zellij \
&& printf '%s\n' \
'default_mode "normal"' \
'show_startup_tips false' \
'show_release_notes false' \
> /home/you/.config/zellij/config.kdl

# Bake yeet-scripts repo into ~ (everything except opt/ which lives at /opt/)
COPY --chown=you:you --exclude=opt --exclude=Dockerfile --exclude=Makefile . /home/you/

RUN chafa --format=symbols --symbols=block --size=30x15 $ASSETS/logo.png > $ASSETS/logo-block.txt && \
chafa --format=symbols --symbols=braille --size=35x18 $ASSETS/logo.png > $ASSETS/logo-braille.txt && \
convert $ASSETS/logo.png -alpha extract -threshold 50% /tmp/sil.png && \
convert /tmp/sil.png -morphology Erode Disk:5 /tmp/eroded.png && \
convert /tmp/sil.png /tmp/eroded.png -compose Minus_Src -composite /tmp/ring.png && \
convert -size 715x669 xc:black $ASSETS/logo.png -composite /tmp/flat.png && \
convert /tmp/flat.png -colorspace Gray -threshold 99% /tmp/eyes-raw.png && \
convert -size 715x335 xc:white -size 715x334 xc:black -append /tmp/top.png && \
convert /tmp/eyes-raw.png /tmp/top.png -compose Multiply -composite -define connected-components:area-threshold=400 -define connected-components:mean-color=true -connected-components 4 -morphology Dilate Disk:10 /tmp/eyes.png && \
convert /tmp/sil.png -morphology Erode Disk:8 /tmp/inner.png && \
convert /tmp/inner.png /tmp/top.png -compose Multiply -composite /tmp/upper.png && \
convert -size 715x669 xc:black -seed 7 +noise Random -channel R -separate -threshold 99% /tmp/noise-tiny.png && \
convert /tmp/noise-tiny.png -morphology Dilate Disk:6 /tmp/noise.png && \
convert /tmp/noise.png /tmp/upper.png -compose Multiply -composite /tmp/dots.png && \
convert /tmp/top.png -negate /tmp/bottom.png && \
convert /tmp/inner.png /tmp/bottom.png -compose Multiply -composite /tmp/lower-fill.png && \
convert /tmp/ring.png /tmp/eyes.png -compose Lighten -composite /tmp/step1.png && \
convert /tmp/step1.png /tmp/dots.png -compose Lighten -composite /tmp/step2.png && \
convert /tmp/step2.png /tmp/lower-fill.png -compose Lighten -composite /tmp/mask.png && \
convert /tmp/flat.png -modulate 130,250,100 /tmp/sat.png && \
convert /tmp/sat.png -modulate 60,100,100 -level 0%,75% /tmp/sat-dim.png && \
convert -size 715x669 xc:white /tmp/white-canvas.png && \
convert -size 715x669 xc:"rgb(0,0,255)" /tmp/blue-canvas.png && \
convert /tmp/flat.png -colorspace Gray -threshold 15% -negate /tmp/dark.png && \
convert /tmp/dark.png /tmp/eyes.png -compose Multiply -composite -morphology Dilate Disk:2 /tmp/pupils.png && \
convert /tmp/sat-dim.png /tmp/white-canvas.png /tmp/eyes.png -composite /tmp/sat-with-eyes.png && \
convert /tmp/sat-with-eyes.png /tmp/blue-canvas.png /tmp/pupils.png -composite /tmp/sat-final.png && \
convert /tmp/sat-final.png /tmp/mask.png -alpha off -compose CopyOpacity -composite $ASSETS/logo-outline.png && \
chafa --format=symbols --symbols='ascii-alpha-digit-bad-ugly' --colors=256 --fg-only --color-extractor=median --size=44x21 $ASSETS/logo-outline.png \
| perl -pe 's/(\e\[38;5;(?:231|21|19|20|27|33)m)./$1@/g' \
> $ASSETS/logo-ascii.txt && \
ln -sf $ASSETS/logo-ascii.txt $ASSETS/logo.txt && \
mkdir -p /home/you/.config/fastfetch && \
printf '%s\n' \
'{' \
' "logo": {' \
' "source": "/opt/logos/logo.txt",' \
' "type": "file-raw",' \
' "padding": { "right": 2 }' \
' },' \
' "display": {' \
' "color": {' \
' "keys": "red",' \
' "title": "red",' \
' "separator": "yellow",' \
' "output": "white"' \
' },' \
' "separator": " "' \
' },' \
' "modules": [' \
' { "type": "title", "color": { "user": "cyan", "at": "yellow", "host": "green" } },' \
' { "type": "separator", "string": "==============" },' \
' { "type": "os", "format": "yeet enterprise linux", "keyColor": "red" },' \
' { "type": "host", "keyColor": "yellow" },' \
' { "type": "kernel", "keyColor": "green" },' \
' { "type": "uptime", "keyColor": "cyan" },' \
' { "type": "loadavg", "keyColor": "blue" },' \
' { "type": "packages", "keyColor": "magenta" },' \
' { "type": "shell", "keyColor": "red" },' \
' { "type": "terminal", "keyColor": "yellow" },' \
' { "type": "cpu", "keyColor": "green" },' \
' { "type": "cpuusage", "keyColor": "cyan" },' \
' { "type": "memory", "keyColor": "blue" },' \
' { "type": "swap", "keyColor": "magenta" },' \
' { "type": "disk", "keyColor": "red" },' \
' { "type": "localip", "keyColor": "yellow" },' \
' { "type": "locale", "keyColor": "green" },' \
' { "type": "datetime", "keyColor": "cyan" },' \
' "break",' \
' "colors"' \
' ]' \
'}' \
> /home/you/.config/fastfetch/config.jsonc

ENV ZSH=/home/you/.oh-my-zsh
ENV ZSH_CUSTOM=$ZSH/custom

RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k \
&& git clone --depth=1 https://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions \
&& git clone --depth=1 https://github.com/zsh-users/zsh-syntax-highlighting $ZSH_CUSTOM/plugins/zsh-syntax-highlighting \
&& git clone --depth=1 https://github.com/zsh-users/zsh-completions $ZSH_CUSTOM/plugins/zsh-completions \
&& $ZSH_CUSTOM/themes/powerlevel10k/gitstatus/install -f

RUN sed -i 's|^ZSH_THEME=.*|ZSH_THEME="powerlevel10k/powerlevel10k"|' ~/.zshrc \
&& sed -i 's|^plugins=.*|plugins=(git docker zsh-autosuggestions zsh-syntax-highlighting zsh-completions)|' ~/.zshrc \
&& cp $ZSH_CUSTOM/themes/powerlevel10k/config/p10k-rainbow.zsh ~/.p10k.zsh \
&& echo '[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh' >> ~/.zshrc \
&& echo 'ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="fg=244"' >> ~/.zshrc \
&& echo 'alias logo-ascii="ln -sf /opt/logos/logo-ascii.txt /opt/logos/logo.txt && fastfetch"' >> ~/.zshrc \
&& echo 'alias logo-braille="ln -sf /opt/logos/logo-braille.txt /opt/logos/logo.txt && fastfetch"' >> ~/.zshrc \
&& echo 'alias logo-block="ln -sf /opt/logos/logo-block.txt /opt/logos/logo.txt && fastfetch"' >> ~/.zshrc

# Collapse rainbow preset to a single line
RUN cat >> ~/.p10k.zsh <<'EOF'

# --- single-line override: collapse rainbow to one line ---
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(os_icon context dir vcs prompt_char)
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX=
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX=
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX=
typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=
typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=
typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false
EOF

ENTRYPOINT ["/opt/scripts/entry.sh"]
27 changes: 27 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
IMAGE ?= yeet-try

# Pick your poison
HOSTNAMES := darling hilshire goat pirate the-bear noodle dopey ace \
penguin blacky mopey quick-save snake donut weasel tooth \
wilshire shadow ghost saucy prancer grumpy sleepy hambone
RANDOM_HOST = $(shell echo "$(HOSTNAMES)" | tr ' ' '\n' | grep -v '^$$' | \
awk 'BEGIN{srand()} {a[NR]=$$0} END{print a[int(rand()*NR)+1]}')

.PHONY: all build run banger

all: build run

banger:
@./opt/scripts/banger/manage.sh

build:
docker build --build-arg YEET_CACHEBUST=$$(date +%s) -t $(IMAGE) . > /dev/null

run:
@docker run --rm -it --hostname $(RANDOM_HOST) \
-e TERM=xterm-256color \
-e COLORTERM=truecolor \
-e FORCE_COLOR=1 \
-e CLICOLOR=1 \
-e CLICOLOR_FORCE=1 \
$(IMAGE)
7 changes: 7 additions & 0 deletions examples/fire/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: run info

run:
yeet run ./index.js

info:
@head -10 ./index.js | sed -n 's|^// \?||p'
37 changes: 37 additions & 0 deletions examples/fire/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# fire

**Doom's flame effect**, in your terminal. Originally Fabien Sanglard's
1993 algorithm — runs on anything, looks like nothing else.

## how it works

Keep a 2D buffer of "intensity" values (0–31). The bottom row is fixed
at 31 — pure white-hot. Then each frame:

1. For every pixel `(x, y)`, look at the pixel **below** it (with random
±1 horizontal jitter for the swirl effect).
2. Subtract a small random amount (0–2) so it cools as it rises.
3. Write that value into `(x, y)`.

Result: heat propagates upward and dissipates. The horizontal jitter
gives the flames their licking, organic motion. The random cool-down
amount gives the texture.

## why it's a banger

It's a perfect tiny algorithm — 4 lines of math, infinite visual depth.
And it teaches you something fundamental: **complex behavior from local
rules** is the entire story of cellular automata, fluid sims, neural
nets, even cities.

## palette

A 32-step ramp from black, through deep red, to orange, to yellow, to
white. Truecolor again, half-block double resolution again.

## controls

- **Esc** back to the picker
- **←/→** prev / next banger
- **Space** toggle this readme
- **Ctrl+C** interrupt the script
78 changes: 78 additions & 0 deletions examples/fire/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env -S yeet run
//
// FIRE — Fabien Sanglard's classic doom-style flame effect.
//
// We keep a buffer of "intensity" values. The bottom row is hot, fixed
// at max. Every frame each pixel cools by a small random amount and is
// pulled from one of the three pixels below it (with horizontal spread).
// Renders with truecolor and ▀ for double vertical density.

const { interval = 50 } = yeet.args;

const ESC = "\x1b[";

let { rows, cols } = tty.size();

// Internal buffer is double the row height so ▀ pairs up nicely.
let H = rows * 2;
let W = cols;
let buf = new Uint8Array(H * W);

// 32-step palette: black → red → orange → yellow → white
const PALETTE = [];
for (let i = 0; i < 32; i++) {
let r, g, b;
if (i < 8) { r = i * 32; g = 0; b = 0; }
else if (i < 16) { r = 255; g = (i - 8) * 32; b = 0; }
else if (i < 24) { r = 255; g = 255; b = (i - 16) * 32; }
else { r = 255; g = 255; b = 200 + (i - 24) * 7; }
PALETTE.push([r, g, b]);
}

function seedBottom() {
for (let x = 0; x < W; x++) buf[(H - 1) * W + x] = 31;
}

function step() {
for (let y = 0; y < H - 1; y++) {
for (let x = 0; x < W; x++) {
const src = (y + 1) * W + x + (Math.floor(Math.random() * 3) - 1);
if (src < 0 || src >= H * W) continue;
const cool = Math.floor(Math.random() * 3);
const v = buf[src] - cool;
buf[y * W + x] = v < 0 ? 0 : v;
}
}
}

function tick() {
const sz = tty.size();
if (sz.rows !== rows || sz.cols !== cols) {
rows = sz.rows; cols = sz.cols;
H = rows * 2;
W = cols;
buf = new Uint8Array(H * W);
tty.clear();
}
seedBottom();
step();
let frame = `${ESC}H`;
for (let r = 0; r < rows; r++) {
let line = "";
for (let c = 0; c < cols; c++) {
const top = PALETTE[buf[r * 2 * W + c]];
const bot = PALETTE[buf[(r * 2 + 1) * W + c]];
line += `\x1b[38;2;${top[0]};${top[1]};${top[2]}m`;
line += `\x1b[48;2;${bot[0]};${bot[1]};${bot[2]}m▀`;
}
frame += line + "\x1b[0m";
if (r + 1 < rows) frame += "\n";
}
tty.write(frame);
}

tty.alt();
tty.hideCursor();
tty.title("fire");
tty.clear();
setInterval(tick, interval);
7 changes: 7 additions & 0 deletions examples/life/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: run info

run:
yeet run ./index.js

info:
@head -10 ./index.js | sed -n 's|^// \?||p'
52 changes: 52 additions & 0 deletions examples/life/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# life

**Conway's Game of Life**, rendered into braille pixels for 8× density.

## the rules (1970)

For every cell, count its 8 neighbors. Then:

| state | neighbors | becomes |
|------:|----------:|--------:|
| alive | 2,3 | alive |
| alive | other | dead |
| dead | 3 | alive |

That's it. Three lines. From this, you get gliders, spaceships, oscillators,
guns, and (if you're patient) Turing-complete computation.

## the rendering trick

A single braille character holds an 8-dot grid (2 cols × 4 rows). So one
terminal cell paints 8 pixels of life. The screen runs at `(cols × 2) ×
(rows × 4)` resolution — typically several thousand cells.

## the aging trick

Each living cell tracks how long it's been alive. We use that to color
it:

```
< 3 → white (just born, energetic)
< 8 → cyan
< 15 → bright blue
< 30 → blue
≥ 30 → deep blue (the elders)
```

Watch the colonies: brand-new gliders flash white at the front, the old
oscillators burn cyan in the back.

## why it's a banger

Life is the canonical reminder that **simple rules → endless complexity**.
You'll see structures emerge that nobody designed. When the colony goes
quiet, the script reseeds — but if you watch long enough you'll catch
gliders escaping into the void anyway.

## controls

- **Esc** back to the picker
- **←/→** prev / next banger
- **Space** toggle this readme
- **Ctrl+C** interrupt the script
Loading