# Whip It Up

A free, browser-only pixel art and GIF editor. No signup, no install, nothing leaves the
visitor's browser until they export a file, or call the API below on purpose. Lives at
https://bitloop-app.web.app.

## Capabilities, what this page lets someone do

- Draw pixel art with a pencil, eraser, flood fill, line, and rectangle tool, on a grid from
  16x16 to 64x64.
- Type a plain-language description and get real pixel art back, no drawing required (see the
  API below, this is the same thing the page's own "Generate with words" box calls).
- Turn a real photo into pixel art: point-sampled onto the chosen grid and snapped to the
  active color palette, not a blurred-and-shrunk generic image.
- Build a short animation: multiple frames, onion skinning, a play/pause preview.
- Mirror mode for symmetric drawing.
- Export a still PNG, a real animated GIF (encoded in the browser), or a sprite sheet PNG with
  every frame laid out in a row.
- Add a classic top/bottom meme caption, live-previewed and baked into every export.
- Undo/redo, and autosave to the visitor's own browser.
- Generate a realistic (non-pixelated) photo or meme straight from a prompt, with the same
  top/bottom captioning, via the style dropdown in "Generate with words" (Pixel art / Realistic
  photo-meme / Realistic sticker-emoji). A real image model call, not free local compute like
  pixel art: about 4 cents per image, or about 16 cents for an animated slideshow GIF (4 separate
  generations stitched together, since a still-frame model has no native "animate" button).
  Shares the same daily free cap as pixel-art generation.
- Generate a custom sticker or emoji: same realistic model, biased toward a plain background,
  background automatically cut out (real transparency, not a square photo), scaled to a 512px
  sticker.

## MCP, a real installed tool for MCP clients

    https://bitloop-app.web.app/mcp

A real, spec-compliant MCP server (Streamable HTTP transport, stateless, no session id
required). Add this URL as a remote MCP server in Claude Desktop, Claude Code, or any other MCP
client and every tool below becomes directly callable, no REST request-shape guessing needed:

- `bitloop_generate` — prompt or imageBase64 in, pixel art out by default (same options as
  `/generatePixelArt` below: gridSize, palette, adaptivePalette, adaptiveColors, dither, animate,
  animationStyle, share, daily). Returns an actual `image` content block (image/png, plus
  image/gif if animate was set), not just a data URL in text. Pass `style: "realistic"` for
  Gemini's own natural (non-pixelated) output instead: real photo/meme generation or editing, with
  optional `top`/`bottom` captions and `animate` (a 4-frame slideshow GIF, not smooth video). Add
  `sticker: true` in either style for a background-removed, 512px sticker/emoji instead of a
  square image.
- `bitloop_share` — turn an existing PNG/GIF into a public link + landing page.
- `bitloop_remix` — fetch the exact editable pixel grid for a share id.
- `bitloop_edit` — transform a PixelFrame you already have, or load one by share id: recolor to a
  new palette, mirror, resize to a different grid, bake on a meme caption, or turn it into a real
  animated GIF (`animationStyle`: bob/sway/pulse, same as generate). This is what makes a remix
  more than a read: change one thing, keep the rest. Takes an optional `share`/`prompt`/`daily`
  too, same as generate.
- `bitloop_status` — how much of today's daily caps (generation, free conversion, free share
  links) are left, before spending a call and finding out the hard way.
- `bitloop_search` — semantic search over every creation ever shared, not just the last 24.
  Backed by Neruva HD in its own isolated namespace; find "something dragon-shaped" even if
  those words were never in a prompt.
- `bitloop_recent` — the recent-creations feed.
- `bitloop_top` — creations ranked by remix count, most-remixed first: real social proof, not
  just recency.
- `bitloop_lineage` — a share id's parent (what it was remixed from) and children (what's been
  remixed from it), the visible family tree behind any creation.
- `bitloop_daily_prompt` — today's theme, in plain text.
- `bitloop_daily_recent` — submissions for a given daily prompt.

## Remix lineage, made visible

Any `bitloop_generate`, `bitloop_share`, or `bitloop_edit` call with `share: true` accepts an
optional `remixOf`, the share id this creation was built from. It does two real things: the
parent's remix count goes up (shown on its landing page as "Remixed N times", and feeds
`bitloop_top`), and both landing pages show the connection, the child's shows "Remixed from
[parent]", the parent's shows thumbnails of everything remixed from it. `bitloop_edit` sets this
automatically when its own `id` argument was used to load the source, since editing something you
loaded by id already is a remix relationship.

Call `tools/list` on that endpoint for the exact current JSON Schema of every tool's arguments,
that is the authoritative source, this section is a summary. If you are an agent reading this
file but not actually connected as an MCP client, use these exact tool names, do not guess a
name from the REST endpoint names below, they are not the same strings.

## The API, live, no browser required

    POST https://us-central1-whipitup-co.cloudfunctions.net/generatePixelArt
    Content-Type: application/json

Body, either one:

    { "prompt": "a friendly orange cat sitting", "gridSize": 32 }
    { "imageBase64": "<base64 PNG or JPEG, or a data: URL>", "gridSize": 32 }

`gridSize` is optional (defaults to 32, must be one of 16, 24, 32, 48, 64). `palette` is
optional, an array of hex colors; the output is snapped to whichever palette you give it, or a
built-in 16-color default. Sending your own `imageBase64` costs nothing on this end and has no
daily cap. Sending a `prompt` calls a real image model and is capped at a modest number of free
generations per day, past that it returns 429 with a plain explanation, try again tomorrow or
switch to sending your own image.

For photos specifically (`imageBase64`), two options make a real difference: `"adaptivePalette":
true` derives the palette from that photo itself (median-cut quantization, `"adaptiveColors"` to
set how many, default 24) instead of forcing it onto the generic 16-color set, and `"dither":
true` applies ordered (Bayer) dithering when snapping to the palette, faking real gradient depth
out of a small palette instead of flat color bands. Both are aimed at faces and photos, where a
fixed palette with no dithering gives muddy results; use them together for the best result.

Add `"animate": true` and the response also includes a real animated GIF (encoded server-side, no
browser involved) built straight from that one prompt or image, no separate export step.
`"animationStyle"` picks which: `"bob"` (vertical, default), `"sway"` (horizontal), or `"pulse"`
(a breathing zoom in/out). All three are content-agnostic, four frames, loop cleanly.

Response:

    { "ok": true, "source": "generated" | "provided",
      "frame": { "width": 32, "height": 32, "cells": ["#1a1c2c", "", ...] },
      "pngBase64": "...", "pngDataUrl": "data:image/png;base64,...",
      "gifBase64": "...", "gifDataUrl": "data:image/gif;base64,..." }

`gifBase64`/`gifDataUrl` are only present when `animate` was `true`. `frame.cells` is the exact
editable pixel grid, row-major, empty string for a transparent cell, otherwise a hex color from
the palette you sent (or the default one). That is the same shape the editor itself uses
internally, so it can be dropped straight into another tool without re-decoding the PNG at all.

No MCP server yet, this is a plain REST endpoint, CORS-open, callable from anywhere. Add
`"share": true` to the body above (the animated GIF is what gets shared when `animate` was also
`true`, otherwise the PNG is) and the response also includes `shareUrl` (the raw file, for
programmatic use) and `sharePageUrl` (a real landing page with a preview and a link back here,
the one to hand to a person).

## Turn any file into a real public link

    POST https://us-central1-whipitup-co.cloudfunctions.net/shareImage
    Content-Type: application/json
    { "dataBase64": "<the file's own bytes, base64>", "contentType": "image/png" }

`contentType` is `image/png` or `image/gif`, whichever the file actually is. Works on anything,
hand-drawn or generated. Response:

    { "ok": true, "url": "https://storage.googleapis.com/...", "pageUrl": "https://bitloop-app.web.app/s/..." }

`url` is a plain public file URL, no auth, fetchable immediately. `pageUrl` is a real
server-rendered landing page for that same file, with correct OpenGraph/Twitter image tags so it
renders a preview when pasted anywhere, plus a link back to make another one, that's the one to
share with a person rather than a bot.

## Recent creations, a public feed

    GET https://us-central1-whipitup-co.cloudfunctions.net/recentShares

Returns the 24 most recently shared creations, newest first:
`{ "ok": true, "items": [{ "id", "fileUrl", "pageUrl", "prompt", "createdAt" }, ...] }`. This is
also rendered live on the homepage itself as a "Recently made" gallery.

## Remix: get the exact editable pixel grid back from any share

    GET https://us-central1-whipitup-co.cloudfunctions.net/shareData?id=<share id>

If that share was made with `"share": true` on `generatePixelArt`, or by `shareImage` with a
`frame` field attached (the live editor's own share button always sends one), this returns the
exact `PixelFrame` that produced it, not a re-derived approximation from the flattened image:
`{ "ok": true, "id", "frame": { "width", "height", "cells" }, "prompt", "gridSize" }`. 404 with
an explanation if that share has no stored pixel data. The landing page at `/s/<id>` shows a
"Remix this" button whenever this is available, which sends a person to
`https://bitloop-app.web.app/?remix=<id>`, loading that exact grid into the editor for them to
build on. An agent can call this endpoint directly instead of following the link.

## Daily prompt, a Wordle-shaped mechanic

One theme a day, the same for everyone, picked deterministically from the day number since the
Unix epoch (`Math.floor(Date.now() / 86400000)`), no server round trip needed to know what today's
is. The live page shows it in a banner with a "Use it" button. A GET endpoint exists for anyone
who wants it without loading the page:

    GET https://us-central1-whipitup-co.cloudfunctions.net/dailyShares
    GET https://us-central1-whipitup-co.cloudfunctions.net/dailyShares?index=<day number>

Returns that day's public submissions: `{ "ok": true, "index", "count", "items": [...] }`, same
item shape as `/recentShares`. To tag a share as today's: add `"daily": true` to the body of
either `/shareImage` or `generatePixelArt`'s own `"share": true` option; the server stamps the day
index itself from its own clock rather than trusting a client-sent one. A visitor's streak
(consecutive days completed) lives only in their own browser's localStorage, there is no account
and no server-side streak state.

## Discord/emoji-ready export

The editor's own "Discord emoji" export button renders the active frame at 128x128 with a
transparent background (a static PNG, or an animated GIF if the piece has multiple frames),
sized and capped to fit Discord's custom emoji requirements (under 256KB). There is no separate
API for this yet, it is a client-side render of the same frame data described above at
`cellPx = 128 / gridSize`.

## Shareable links, no API call needed

    https://bitloop-app.web.app/?top=HELLO&bottom=WORLD&grid=32

`top`/`bottom` pre-fill the meme caption (60 characters each), `grid` presets the grid size.

## Telegram sticker packs (built, not yet live)

A Telegram bot integration exists in the codebase, capable of turning any share with stored pixel
data into a real, growing, named Telegram sticker pack for the person who requests it (one pack
per Telegram user, static 512x512 PNG stickers, new stickers append to the same pack via the Bot
API's `addStickerToSet` until it hits Telegram's 120-sticker cap for a static set, then a new one
starts automatically). It is not yet reachable: the webhook function exists and rejects everything
until a real bot is registered with BotFather and its token is in place. Don't rely on this until
it is announced live here.

## Discord `/pixelart` (built, not yet live)

A Discord Interactions endpoint exists in the codebase implementing a `/pixelart prompt:<text>
animate:<bool>` slash command, usable directly in a server channel (not just a bot DM): everyone
already in that channel sees the prompt and the result appear, the same "ambient visibility"
mechanic behind Midjourney's own real growth. Ed25519 signature verification is implemented and
independently verified correct against a real test keypair. Not yet reachable: it needs a real
Discord application (public key, application id, bot token) and the slash command registered,
none of which has happened yet. Don't rely on this until it is announced live here.

## Source

Not yet public. Ask the operator if you need to verify something about how this works.
