bunflared 0.8.0

One command, one throwaway link for your localhost, several ports behind it. Chat with your client from the terminal, send them to a page, follow their cursor, catch their reactions 🔥 Or let your coding agent run the session over MCP.
//! The cast of the startup show, drawn in Braille. Each grid is dots, `#`
//! lit and `.` empty: four dot rows make one terminal line, two dot columns
//! one cell.

use ratatui::buffer::Buffer;
use ratatui::style::Style;

use super::fx::put;

pub const RUN_A: &str = r"
..................##..........
...................###........
...............########.......
................#..#####......
.................##..#####....
.......##............######...
......#########.....########..
.......#####################..
.......#####################..
.....#.###################....
...#.###################......
...#####################......
..#########..##############...
###.#...............#########.
##.##................#####....
........................###...
";

pub const RUN_B: &str = r"
..........#.......
.......#####......
.......#..###.....
...........##.....
...........####...
...######..#####..
..###############.
################..
#############.....
.############.....
..##########......
....###.##........
....######........
.....####.........
.....####.........
........##........
";

// Dragon grids share one frame, so the wings beat around the same body.
// Where the mouth is, in cells, for each pose.
pub const DRAGON_UP_MOUTH: (i32, i32) = (22, 4);
pub const DRAGON_DOWN_MOUTH: (i32, i32) = (22, 1);

pub const DRAGON_UP: &str = r"
.................#............................
..................###.........................
...................###........................
......##...........#####......................
.......####........######.....................
........#######....########...................
.........########...########..................
..........#########...######..................
..........##########...######.................
.........############...######................
.........#........####...#####................
.........#########...##...#.###...............
..........###########.......###...............
...........############..##.#####.............
............######.......##.####..............
............###...#####..#.####...............
............#..#######.##..####...............
.............########.###..###.......##.......
...........#########.###...###....#####.......
...............####.####...###...#########...#
...............###.#####..#.##...###..#.....#.
................#..#####..#.##...###....#.....
................#.######.#..##...###..........
.................#######.....##...###.........
................#########..#..#...####........
...........#..........#####.#..#...###........
............#...........####.##....####.......
.........................####.##.######.......
.........................#############........
........................##############........
...........#..........###############.........
..........#..........#########.###.##.........
.........#..........##########.##.##..........
........#..........##########.##..............
........#.........###.###.....##.#............
.......##........####.###.##...#.#............
.......##.......####...##.##...#..............
........#......####...##..#......#............
........##....####...##..#....#..#............
.........#######....##........................
..........####......#..#......................
....................#..#......................
...................##..#......................
..............................................
";

pub const DRAGON_DOWN: &str = r"
................................#.............
.................................##...........
................................######........
...............................########.......
..............................##########....#.
..............................###..##......#..
..............................###....#....#...
.............................####.............
.....##.......................###.............
.....##.......................####............
......#........................####...........
......#.........................####..........
......#.........................####..........
......................#..#......####..........
.....#...............###############..........
....#..............#################..........
...#.............###################..........
..#.............############.######...........
.##............####.#####.#..######...........
.#............#####.#####.#..#####............
##...........######.####.#...####.............
##..........######.#####...#.##..##...........
##.........#####..######.#.#...######.........
.##......####...########..#..#########........
.############.#.#########....#....######......
..#########....#..########....###......###....
....#####......###..#######....#####...#......
...............#####...#####.#..##..##.#......
..............#########......##...###.##......
..............#############......###.##.......
.............#############.......#..###.......
............############..#...#.#..###........
..........############..###...#..#####........
.........#...#####....####...#.######.........
.........####.....#######...#########.........
..........##############.....#######..........
..........############..#....#####............
.........############........####.............
........###########.........###...............
.......##########..........#..................
......#######.................................
.......##.....................................
..............................................
..............................................
";

/// Inks: `f` the fur, `h` the helmet, `j` the jacket, `y` its band, `b` the
/// boots, `s` the water cannon.
pub const FIREFIGHTER_NOZZLE: (i32, i32) = (11, 3);
pub const FIREFIGHTER: &str = r"
....ff..................
....fff.................
ffffffff................
.ffffffff...............
..ffffffhh..............
...fffffhhhh............
......ffhhhhh...........
.......fhhhhhh..........
......hhfffffff.........
......hhffffff..........
.....hhhfffffff.........
.......hfffffff.........
........jjjjff..........
........jjjj............
.......jjjjj............
.......jjjjjf........ss.
......yyyyyyyyff...sss..
......yyyyyyyyff..sss...
......yyyyyyyyffsss.....
.....fyyyyyyyyffss......
....ffjjjjjjjjssss......
.....fjjjjjjj....s......
......jjjjjj.....s......
......jjjjjj.....s......
......bbbbbb....sss.....
.......bbbbb....sss.....
.......bbbbb...s.s.s....
.......bbbbbb....s......
";

pub const DANCE_LEFT: &str = r"
...#........
...##...#...
...#...#....
...#.#.#....
.....#.##...
....####....
....#####...
....#####...
##.######...
##..#####...
.########...
..########..
....######..
...######...
...#######..
..########..
..########..
..########..
...###.###..
.####...####
";

pub const DANCE_RIGHT: &str = r"
........#...
...#...##...
....#...#...
....#.#.#...
...##.#.....
....####....
...#####....
...#####....
...######.##
...#####..##
...########.
..########..
..######....
...######...
..#######...
..########..
..########..
..########..
..###.###...
####...####.
";

/// Inks: `c` the carrot, `l` its leaves.
pub const CARROT: &str = r"
........l.l..l
........lll.l.
.......cclll..
......ccccl...
....cc.cccc...
...ccccc......
.ccc..........
.c............
";

// Where each dot of a cell sits, and its bit in the Braille block.
const DOTS: [(usize, usize, u32); 8] = [
    (0, 0, 0x01),
    (0, 1, 0x02),
    (0, 2, 0x04),
    (1, 0, 0x08),
    (1, 1, 0x10),
    (1, 2, 0x20),
    (0, 3, 0x40),
    (1, 3, 0x80),
];

fn rows(grid: &str) -> Vec<&[u8]> {
    grid.lines()
        .filter(|line| !line.is_empty())
        .map(str::as_bytes)
        .collect()
}

/// Width in cells.
pub fn width(grid: &str) -> i32 {
    rows(grid)
        .iter()
        .map(|row| row.len())
        .max()
        .unwrap_or(0)
        .div_ceil(2) as i32
}

/// Draws the grid with its top left cell at `x`, `y`, mirrored when `flip`.
/// A cell takes the color `paint` gives the ink most of its dots carry.
/// Empty cells keep what is behind them.
pub fn draw(
    buf: &mut Buffer,
    (x, y): (i32, i32),
    grid: &str,
    flip: bool,
    paint: impl Fn(u8) -> Style,
) {
    let rows = rows(grid);
    let span = width(grid) as usize * 2;
    let ink = |col: usize, row: usize| {
        let col = if flip { span - 1 - col } else { col };
        rows.get(row)
            .and_then(|line| line.get(col))
            .copied()
            .filter(|&dot| dot != b'.')
    };
    for cy in 0..rows.len().div_ceil(4) {
        for cx in 0..span / 2 {
            let (mut bits, mut inks) = (0, Vec::with_capacity(8));
            for &(dx, dy, bit) in &DOTS {
                if let Some(dot) = ink(cx * 2 + dx, cy * 4 + dy) {
                    bits |= bit;
                    inks.push(dot);
                }
            }
            let most = inks
                .iter()
                .max_by_key(|&&a| inks.iter().filter(|&&b| b == a).count());
            if let (Some(&most), Some(ch)) = (most, char::from_u32(0x2800 + bits)) {
                put(
                    buf,
                    x + cx as i32,
                    y + cy as i32,
                    &ch.to_string(),
                    paint(most),
                );
            }
        }
    }
}