Created
December 5, 2025 23:11
-
-
Save sdan/fc702f2328c4a04a5fdfe2a78646c275 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useState, useEffect, useRef } from 'react'; | |
| export default function BrailleTsunami() { | |
| const [frame, setFrame] = useState(''); | |
| const timeRef = useRef(0); | |
| // Braille character encoding - each char is 2x4 dots | |
| // Dot positions: 1 4 | |
| // 2 5 | |
| // 3 6 | |
| // 7 8 | |
| const dotMap = [0x01, 0x02, 0x04, 0x40, 0x08, 0x10, 0x20, 0x80]; | |
| const toBraille = (dots) => String.fromCharCode(0x2800 + dots); | |
| useEffect(() => { | |
| const WIDTH = 80; // characters wide | |
| const HEIGHT = 24; // characters tall | |
| const PIXEL_W = WIDTH * 2; // actual pixel width (2 dots per char horizontally) | |
| const PIXEL_H = HEIGHT * 4; // actual pixel height (4 dots per char vertically) | |
| const renderFrame = () => { | |
| const t = timeRef.current; | |
| timeRef.current += 0.05; | |
| // Z-buffer and pixel buffer | |
| const zbuf = new Float32Array(PIXEL_W * PIXEL_H); | |
| const pixels = new Uint8Array(PIXEL_W * PIXEL_H); | |
| // Camera/view parameters | |
| const camDist = 8; | |
| const fov = 60; | |
| // Rotation matrices | |
| const rotX = t * 0.3; | |
| const rotY = t * 0.5; | |
| const cosX = Math.cos(rotX), sinX = Math.sin(rotX); | |
| const cosY = Math.cos(rotY), sinY = Math.sin(rotY); | |
| // Sample the wave surface | |
| for (let u = -3; u <= 3; u += 0.08) { | |
| for (let v = -3; v <= 3; v += 0.08) { | |
| // Wave function - multiple sine waves create tsunami-like surface | |
| const wave1 = Math.sin(u * 2 + t * 2) * 0.4; | |
| const wave2 = Math.sin(v * 1.5 - t * 1.5) * 0.3; | |
| const wave3 = Math.sin((u + v) * 1.2 + t) * 0.2; | |
| const wave4 = Math.sin(u * 3 - t * 3) * 0.15 * Math.exp(-Math.abs(u - 1)); | |
| // Big tsunami wave | |
| const tsunami = Math.exp(-Math.pow(u - 1.5 + Math.sin(t * 0.5), 2) * 0.5) * | |
| Math.sin(u * 4 + t * 2) * 0.8; | |
| let y = wave1 + wave2 + wave3 + wave4 + tsunami; | |
| let x = u; | |
| let z = v; | |
| // Rotate around X axis | |
| let y1 = y * cosX - z * sinX; | |
| let z1 = y * sinX + z * cosX; | |
| // Rotate around Y axis | |
| let x2 = x * cosY + z1 * sinY; | |
| let z2 = -x * sinY + z1 * cosY; | |
| let y2 = y1; | |
| // Project to screen | |
| const depth = z2 + camDist; | |
| if (depth <= 0.1) continue; | |
| const scale = fov / depth; | |
| const screenX = Math.floor(PIXEL_W / 2 + x2 * scale * 3); | |
| const screenY = Math.floor(PIXEL_H / 2 - y2 * scale * 3); | |
| if (screenX >= 0 && screenX < PIXEL_W && screenY >= 0 && screenY < PIXEL_H) { | |
| const idx = screenY * PIXEL_W + screenX; | |
| const invZ = 1 / depth; | |
| if (invZ > zbuf[idx]) { | |
| zbuf[idx] = invZ; | |
| // Calculate normal for lighting | |
| const du = 0.01; | |
| const dv = 0.01; | |
| const h0 = wave1 + wave2 + wave3 + wave4 + tsunami; | |
| const hu = Math.sin((u + du) * 2 + t * 2) * 0.4 + | |
| Math.sin(v * 1.5 - t * 1.5) * 0.3 + | |
| Math.sin((u + du + v) * 1.2 + t) * 0.2; | |
| const hv = Math.sin(u * 2 + t * 2) * 0.4 + | |
| Math.sin((v + dv) * 1.5 - t * 1.5) * 0.3 + | |
| Math.sin((u + v + dv) * 1.2 + t) * 0.2; | |
| const nx = -(hu - h0) / du; | |
| const ny = 1; | |
| const nz = -(hv - h0) / dv; | |
| const len = Math.sqrt(nx * nx + ny * ny + nz * nz); | |
| // Light direction | |
| const lx = 0.5, ly = 0.8, lz = -0.3; | |
| const lLen = Math.sqrt(lx * lx + ly * ly + lz * lz); | |
| const light = Math.max(0, (nx * lx + ny * ly + nz * lz) / (len * lLen)); | |
| pixels[idx] = light > 0.3 ? 1 : 0; | |
| } | |
| } | |
| } | |
| } | |
| // Convert pixel buffer to braille characters | |
| let output = ''; | |
| for (let cy = 0; cy < HEIGHT; cy++) { | |
| for (let cx = 0; cx < WIDTH; cx++) { | |
| let dots = 0; | |
| // Sample 2x4 pixel block for this braille character | |
| for (let dy = 0; dy < 4; dy++) { | |
| for (let dx = 0; dx < 2; dx++) { | |
| const px = cx * 2 + dx; | |
| const py = cy * 4 + dy; | |
| const idx = py * PIXEL_W + px; | |
| if (pixels[idx]) { | |
| // Map to braille dot position | |
| const dotIdx = dy + (dx * 4); | |
| const braillePos = [0, 1, 2, 6, 3, 4, 5, 7][dotIdx]; | |
| dots |= dotMap[braillePos]; | |
| } | |
| } | |
| } | |
| output += toBraille(dots); | |
| } | |
| output += '\n'; | |
| } | |
| setFrame(output); | |
| }; | |
| const interval = setInterval(renderFrame, 50); | |
| renderFrame(); | |
| return () => clearInterval(interval); | |
| }, []); | |
| return ( | |
| <div className="min-h-screen bg-black flex flex-col items-center justify-center p-4"> | |
| <pre | |
| className="font-mono text-cyan-300 leading-none" | |
| style={{ | |
| fontSize: '10px', | |
| letterSpacing: '0px', | |
| lineHeight: '10px', | |
| textShadow: '0 0 10px rgba(0, 255, 255, 0.5)' | |
| }} | |
| > | |
| {frame} | |
| </pre> | |
| </div> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment