diff --git a/docs/plans/2026-01-06-implementation.md b/docs/plans/2026-01-06-implementation.md
new file mode 100644
index 0000000..c3059c6
--- /dev/null
+++ b/docs/plans/2026-01-06-implementation.md
@@ -0,0 +1,486 @@
+# Tiny Sprites Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Build a CSS-based sprite library with Hono JSX component and dev tuning server.
+
+**Architecture:** A `` component renders a div with inline styles and a scoped `@keyframes` animation. The dev server provides a UI using Hono's client-side JSX to load spritesheets, adjust parameters, and copy generated code.
+
+**Tech Stack:** Bun, Hono, Hono JSX (server + client), Pico CSS
+
+---
+
+### Task 1: Project Setup
+
+**Files:**
+- Create: `package.json`
+- Create: `tsconfig.json`
+
+**Step 1: Create package.json**
+
+```json
+{
+ "name": "tiny-sprites",
+ "type": "module",
+ "exports": {
+ ".": "./src/sprite.tsx"
+ },
+ "bin": {
+ "tiny-sprites": "src/dev/server.tsx"
+ },
+ "scripts": {
+ "dev": "bun --hot src/dev/server.tsx",
+ "test": "bun test"
+ },
+ "dependencies": {
+ "hono": "^4"
+ },
+ "devDependencies": {
+ "bun-types": "latest"
+ }
+}
+```
+
+**Step 2: Create tsconfig.json**
+
+```json
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "jsxImportSource": "hono/jsx",
+ "strict": true,
+ "skipLibCheck": true,
+ "types": ["bun-types"]
+ },
+ "include": ["src/**/*"]
+}
+```
+
+**Step 3: Install dependencies**
+
+Run: `bun install`
+Expected: Resolves hono and bun-types
+
+**Step 4: Commit**
+
+```bash
+git add package.json tsconfig.json bun.lockb
+git commit -m "chore: project setup with bun and hono"
+```
+
+---
+
+### Task 2: Download Pico CSS
+
+**Files:**
+- Create: `public/pico.min.css`
+
+**Step 1: Download pico.min.css**
+
+Run: `mkdir -p public && curl -o public/pico.min.css https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css`
+Expected: File downloaded to public/pico.min.css
+
+**Step 2: Commit**
+
+```bash
+git add public/pico.min.css
+git commit -m "chore: add pico css"
+```
+
+---
+
+### Task 3: Sprite Component
+
+**Files:**
+- Create: `src/sprite.tsx`
+- Create: `src/sprite.test.tsx`
+
+**Step 1: Write the failing test**
+
+```tsx
+import { test, expect } from "bun:test"
+import { Sprite } from "./sprite"
+
+test("Sprite renders div with correct dimensions", () => {
+ const html = ().toString()
+ expect(html).toContain('width:32px')
+ expect(html).toContain('height:32px')
+})
+
+test("Sprite sets background-image", () => {
+ const html = ().toString()
+ expect(html).toContain("background-image:url('/warrior.png')")
+})
+
+test("Sprite calculates background-size for horizontal strip", () => {
+ const html = ().toString()
+ expect(html).toContain('background-size:128px 32px')
+})
+
+test("Sprite generates keyframes animation", () => {
+ const html = ().toString()
+ expect(html).toContain('@keyframes sprite-')
+ expect(html).toContain('steps(4)')
+ expect(html).toContain('400ms')
+})
+
+test("Sprite keyframes animate background-position", () => {
+ const html = ().toString()
+ expect(html).toContain('from{background-position:0 0}')
+ expect(html).toContain('to{background-position:-128px 0}')
+})
+
+test("Sprite with columns calculates grid background-size", () => {
+ const html = ().toString()
+ expect(html).toContain('background-size:128px 64px')
+})
+
+test("Sprite with columns generates grid keyframes", () => {
+ const html = ().toString()
+ expect(html).toContain('0%{background-position:0px 0px}')
+ expect(html).toContain('25%{background-position:-32px 0px}')
+ expect(html).toContain('50%{background-position:0px -32px}')
+ expect(html).toContain('75%{background-position:-32px -32px}')
+})
+
+test("Sprite passes through class", () => {
+ const html = ().toString()
+ expect(html).toContain('class="my-sprite"')
+})
+
+test("Sprite passes through style", () => {
+ const html = ().toString()
+ expect(html).toContain('opacity:0.5')
+})
+
+test("Sprite pauses when playing is false", () => {
+ const html = ().toString()
+ expect(html).toContain('animation-play-state:paused')
+})
+```
+
+**Step 2: Run test to verify it fails**
+
+Run: `bun test src/sprite.test.tsx`
+Expected: FAIL with "Cannot find module"
+
+**Step 3: Write implementation**
+
+```tsx
+type SpriteProps = {
+ src: string
+ width: number
+ height: number
+ frames: number
+ frameDuration: number
+ columns?: number
+ class?: string
+ style?: string
+ playing?: boolean
+}
+
+export const Sprite = ({
+ src,
+ width,
+ height,
+ frames,
+ frameDuration,
+ columns,
+ class: className,
+ style,
+ playing = true,
+}: SpriteProps) => {
+ const isGrid = columns !== undefined
+ const rows = isGrid ? Math.ceil(frames / columns!) : 1
+ const cols = isGrid ? columns! : frames
+
+ const sheetWidth = cols * width
+ const sheetHeight = rows * height
+ const totalDuration = frames * frameDuration
+
+ const keyframeId = `sprite-${Bun.hash(`${src}-${width}-${height}-${frames}-${frameDuration}-${columns}`).toString(36)}`
+
+ const keyframes = isGrid
+ ? generateGridKeyframes(keyframeId, width, height, frames, columns!)
+ : generateStripKeyframes(keyframeId, sheetWidth)
+
+ const divStyle = [
+ `width:${width}px`,
+ `height:${height}px`,
+ `background-image:url('${src}')`,
+ `background-size:${sheetWidth}px ${sheetHeight}px`,
+ `animation:${keyframeId} ${totalDuration}ms steps(${frames}) infinite`,
+ playing ? '' : 'animation-play-state:paused',
+ style,
+ ].filter(Boolean).join(';')
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+const generateStripKeyframes = (id: string, sheetWidth: number): string =>
+ `@keyframes ${id}{from{background-position:0 0}to{background-position:-${sheetWidth}px 0}}`
+
+const generateGridKeyframes = (id: string, width: number, height: number, frames: number, columns: number): string => {
+ const steps: string[] = []
+ for (let i = 0; i < frames; i++) {
+ const col = i % columns
+ const row = Math.floor(i / columns)
+ const x = -col * width
+ const y = -row * height
+ const percent = (i / frames) * 100
+ steps.push(`${percent}%{background-position:${x}px ${y}px}`)
+ }
+ return `@keyframes ${id}{${steps.join('')}}`
+}
+```
+
+**Step 4: Run test to verify it passes**
+
+Run: `bun test src/sprite.test.tsx`
+Expected: 10 passing tests
+
+**Step 5: Commit**
+
+```bash
+git add src/sprite.tsx src/sprite.test.tsx
+git commit -m "feat: add Sprite component"
+```
+
+---
+
+### Task 4: Dev Server
+
+**Files:**
+- Create: `src/dev/server.tsx`
+- Create: `src/dev/index.html`
+
+**Step 1: Create dev server**
+
+```tsx
+#!/usr/bin/env bun
+import { Hono } from "hono"
+import { serveStatic } from "hono/bun"
+import index from "./index.html"
+
+const app = new Hono()
+
+app.use("/public/*", serveStatic({ root: "./" }))
+app.get("/", (c) => c.html(index))
+
+export default {
+ port: 3000,
+ fetch: app.fetch,
+}
+```
+
+**Step 2: Create index.html**
+
+```html
+
+
+
+
+
+ Tiny Sprites
+
+
+
+
+
+ Tiny Sprites
+
+
+
+
+
+```
+
+**Step 3: Commit**
+
+```bash
+git add src/dev/server.tsx src/dev/index.html
+git commit -m "feat: add dev server"
+```
+
+---
+
+### Task 5: Dev Tool Client Component
+
+**Files:**
+- Create: `src/dev/app.tsx`
+
+**Step 1: Create client component using Hono JSX**
+
+```tsx
+import { useState } from "hono/jsx"
+import { render } from "hono/jsx/dom"
+
+const App = () => {
+ const [spriteUrl, setSpriteUrl] = useState('')
+ const [width, setWidth] = useState(32)
+ const [height, setHeight] = useState(32)
+ const [frames, setFrames] = useState(4)
+ const [columns, setColumns] = useState()
+ const [frameDuration, setFrameDuration] = useState(100)
+
+ const handleFileChange = (e: Event) => {
+ const file = (e.target as HTMLInputElement).files?.[0]
+ if (!file) return
+ setSpriteUrl(URL.createObjectURL(file))
+ }
+
+ const isGrid = columns !== undefined
+ const cols = isGrid ? columns : frames
+ const rows = isGrid ? Math.ceil(frames / columns!) : 1
+ const sheetWidth = cols * width
+ const sheetHeight = rows * height
+ const totalDuration = frames * frameDuration
+
+ const keyframeId = 'sprite-preview'
+ let keyframes: string
+
+ if (isGrid) {
+ const steps: string[] = []
+ for (let i = 0; i < frames; i++) {
+ const col = i % columns!
+ const row = Math.floor(i / columns!)
+ const x = -col * width
+ const y = -row * height
+ const percent = (i / frames) * 100
+ steps.push(`${percent}%{background-position:${x}px ${y}px}`)
+ }
+ keyframes = `@keyframes ${keyframeId}{${steps.join('')}}`
+ } else {
+ keyframes = `@keyframes ${keyframeId}{from{background-position:0 0}to{background-position:-${sheetWidth}px 0}}`
+ }
+
+ const previewStyle = {
+ width: `${width}px`,
+ height: `${height}px`,
+ backgroundImage: `url('${spriteUrl}')`,
+ backgroundSize: `${sheetWidth}px ${sheetHeight}px`,
+ animation: `${keyframeId} ${totalDuration}ms steps(${frames}) infinite`,
+ }
+
+ const columnsAttr = columns ? `\n columns={${columns}}` : ''
+ const code = ``
+
+ const copyCode = () => navigator.clipboard.writeText(code)
+
+ return (
+
+ )
+}
+
+render(, document.getElementById('app')!)
+```
+
+**Step 2: Run to verify it works**
+
+Run: `bun run dev`
+Expected: Visit http://localhost:3000, upload a spritesheet, see it animate, adjust values, copy code
+
+**Step 3: Commit**
+
+```bash
+git add src/dev/app.tsx
+git commit -m "feat: add dev tool client component with hono jsx"
+```
+
+---
+
+### Task 6: Final Verification
+
+**Step 1: Run all tests**
+
+Run: `bun test`
+Expected: All tests pass
+
+**Step 2: Start dev server and verify**
+
+Run: `bun run dev`
+Expected: Server starts, UI loads, upload works, preview animates, code generates
+
+**Step 3: Create final commit**
+
+```bash
+git add -A
+git commit -m "chore: tiny-sprites v1 complete"
+```