Skip to content

Instantly share code, notes, and snippets.

@VictorTaelin
Created January 20, 2026 15:03
Show Gist options
  • Select an option

  • Save VictorTaelin/aac0f1d6b582e2ef9a0af4e3f3999f3d to your computer and use it in GitHub Desktop.

Select an option

Save VictorTaelin/aac0f1d6b582e2ef9a0af4e3f3999f3d to your computer and use it in GitHub Desktop.
vibitiles
╭─ Claude Code ──────────────────────────────────────────╮
│ │
│ Welcome back Victor! │
│ │
│ │
│ │
│ ▐▛███▜▌ │
│ ▝▜█████▛▘ │
│ ▘▘ ▝▝ │
│ │
│ Opus 4.5 │
│ Claude Max │
│ ~/vibi/vibitiles │
│ │
╰────────────────────────────────────────────────────────╯
❯ create a tile slicer tool in HTM5, JavaScript, with
BunJS to build (bun run server). in this tool, we
will be able to load an image from the user's
computer. that image will be displayed on the
center of the screen. on the left side, there will
be a menu, using 100% of the height of the page,
with some configs. one config will be a 'slice
dim', which will be a number like 1x1, 5x3, etc.,
which the user will enter in a textbox. then that
textbox changes, we will parse that string and
update a state containing that info. when the user
moves the mouse around the loaded image, we will
render a red rectangle, with 70% opacity, and a
16x16 grid with lines darker than the rectangle
itself. when we press space, we will slice the
highlighted region, we will ask the user for a name
(ex: grass). then we will create a zip with each
sliced 16x16 image, named as
<chosen_name>_<x>_<y>.png (ex: grass_01_03.png)
(the x / y always have 2 chars). then we will pass
that to the user as a download so they can saved
anywhere in their machine. we should also have, on
the left menu, a zoom option, allowing us to go
2x/4x/8x/16x on the displayed image. also, clicking
and dragging will move the image around. bun run
server should build and run it on port 4100 (kill
another process if existing).
⏺ I'll create this tile slicer tool for you. Let me plan
and build it step by step.
⏺ Write(server.ts)
⎿  Wrote 53 lines to server.ts
1 import { $ } from "bun";
2 const PORT = 4100;
3 // Kill any existing process on port 4100
4 try {
5 const result = await $`lsof
-ti:${PORT}`.text();
6 if (result.trim()) {
7 const pids = result.trim().split('\n');
8 for (const pid of pids) {
… +43 lines (ctrl+o to expand)
⏺ Bash(mkdir -p /Users/v/vibi/vibitiles/public)
⎿  (No content)
⏺ Write(public/index.html)
⎿  Wrote 249 lines to public/index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
content="width=device-widt
h, initial-scale=1.0">
6 <title>Tile Slicer</title>
7 <style>
8 * {
9 margin: 0;
10 padding: 0;
… +239 lines (ctrl+o to expand)
⏺ Write(public/main.js)
⎿  Wrote 361 lines to public/main.js
1 // State
2 const state = {
3 image: null,
4 zoom: 2,
5 sliceDim: { width: 1, height: 1 },
6 pan: { x: 0, y: 0 },
7 isDragging: false,
8 dragStart: { x: 0, y: 0 },
9 mousePos: { x: 0, y: 0 },
10 isMouseOverImage: false
… +351 lines (ctrl+o to expand)
⏺ Write(package.json)
⎿  Wrote 8 lines to package.json
1 {
2 "name": "vibitiles",
3 "version": "1.0.0",
4 "scripts": {
5 "server": "bun run server.ts"
6 }
7 }
⏺ Bash(bun run server)
⎿  Running in the background (↓ to manage)
⏺ Read agent output b246293
⎿  Read 3 lines
⏺ Bash(curl -s -o /dev/null -w "%{http_code}"
http://localhost:4100/)
⎿  200
⏺ The tile slicer tool is now running at
http://localhost:4100
Here's what I built:
Files created:
- server.ts - Bun server that kills existing process on
port 4100 and serves static files
- public/index.html - Main HTML with left sidebar menu
and canvas area
- public/main.js - All the JavaScript logic
- package.json - With bun run server script
Features:
- Load image: Click "Choose Image" button or
drag-and-drop onto the canvas
- Slice dimensions: Enter values like 1x1, 5x3 in the
textbox (width x height in 16px tiles)
- Zoom: Click 1x/2x/4x/8x/16x buttons (default is 2x)
- Pan: Click and drag to move the image around
- Preview: Hover over the image to see a red rectangle
(70% opacity) with a 16x16 grid showing what will be
sliced
- Slice: Press SPACE while hovering to:
a. Enter a name (e.g., "grass")
b. Download a zip containing all 16x16 tiles named
<name>_XX_YY.png
To restart the server:
bun run server
✻ Sautéed for 2m 4s
⏺ Background command "Start the tile slicer server"
failed with exit code 144
⏺ Read agent output b246293
⎿  Read 3 lines
⏺ The server was running but got terminated. Let me
restart it.
⏺ Bash(bun run server)
⎿  Running in the background (↓ to manage)
⏺ Bash(curl -s -o /dev/null -w "%{http_code}"
http://localhost:4100/)
⎿  200
⏺ Server is back up and running at http://localhost:4100.
The previous termination was just from the background
process being cleaned up - the server itself works fine.
❯ this isn't working fully as intended. when I move
the mouse with a 3x3 slice selected, it literally
jumps 3 blocks to the right / down, instead of
jumping tile by tile. fix so it displays the
intended behavior
⏺ I understand - you want the selection to move
tile-by-tile (16px increments), not jump by the entire
region size. Let me fix that.
⏺ Read(public/main.js)
⎿  Read 361 lines
⏺ I see the issue. The selection snaps to region-sized
increments instead of tile-by-tile (16px). I'll fix both
the overlay rendering and the slicing logic.
⏺ Update(public/main.js)
⎿  Added 3 lines, removed 3 lines
291 const regionWidth = tilesX * tileSize;
292 const regionHeight = tilesY * tileSize
;
293
294 - // Calculate which region the mouse is
- over
294 + // Calculate which tile the mouse is o
+ver (snap to 16px grid, not region size)
295 const imgX = (state.mousePos.x - imgPo
s.x) / state.zoom;
296 const imgY = (state.mousePos.y - imgPo
s.y) / state.zoom;
297
298 - const gridX = Math.floor(imgX / region
-Width) * regionWidth;
299 - const gridY = Math.floor(imgY / region
-Height) * regionHeight;
298 + const gridX = Math.floor(imgX / tileSi
+ze) * tileSize;
299 + const gridY = Math.floor(imgY / tileSi
+ze) * tileSize;
300
301 // Convert back to screen coordinates
302 const screenX = imgPos.x + gridX * sta
te.zoom;
⏺ Update(public/main.js)
⎿  Added 3 lines, removed 3 lines
210 const imgX = (state.mousePos.x - imgPo
s.x) / state.zoom;
211 const imgY = (state.mousePos.y - imgPo
s.y) / state.zoom;
212
213 - // Snap to grid
214 - const startX = Math.floor(imgX / regio
-nWidth) * regionWidth;
215 - const startY = Math.floor(imgY / regio
-nHeight) * regionHeight;
213 + // Snap to 16px tile grid (not region
+size)
214 + const startX = Math.floor(imgX / tileS
+ize) * tileSize;
215 + const startY = Math.floor(imgY / tileS
+ize) * tileSize;
216
217 // Create zip
218 const zip = new JSZip();
⏺ Fixed. Now the selection snaps to 16px tile boundaries
instead of jumping by the full region size. Refresh the
page at http://localhost:4100 to see the change - with a
3x3 selection, the preview will now move smoothly
tile-by-tile as you move your mouse.
──────────────────────────────────────────────────────────
❯ try it now ↵ send
──────────────────────────────────────────────────────────
⏵⏵ bypass permissions o · 1 background tas · ↓ to view
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment