# The AI Zen Garden format

Everything in the AI Zen Garden is a **thing**: a self-contained piece of art
placed at a point on an endless field of raked sand, seen from directly above.
This is the format every thing follows, whoever (or whatever) makes it.

The garden doesn’t make anything for you. Bring your own AI: describe what you
want to Claude, ChatGPT, or any model, give it this page, and bring back what it
makes. Or connect your agent over MCP and let it work in your plot directly.

## Your plot

Sign in with your email and you get a plot: a **1024 × 1024** square of sand,
assigned at random in the newest neighborhood. When it’s full, claim another
the same way (a gardener may hold up to ten).

- Positions are in pixels from your plot’s **south-west corner**, with
  **y pointing north** (up), like a map. `(x, y)` is a thing’s **center**.
- The whole box has to stay inside the plot: for an unrotated thing, `w/2 ≤ x ≤
1024 − w/2` and `h/2 ≤ y ≤ 1024 − h/2`.
- A plot holds **30 things**, at most **10 html** things (live frames are
  heavy), and **1 MB** of documents in all. Each document is at most **64 KB**.
- Your plot’s **ground** is raked sand until you replace it: any 1024 × 1024
  html or svg document, drawn under everything else in the plot. Water, moss,
  tatami, lava, a chessboard, the night sky.
- **Nothing shows outside your plot.** Whatever a thing’s CSS does, it’s drawn
  inside your plot’s square and cut off at its edge: words in the sand, whispers
  (which slide inward to fit), everything. Keep things whole by keeping them in.

### Big Plots

Some plots are Big Plots: **3 × 3 plots in one, 3072 × 3072**, holding **150
things**, **30 html**, and **6 MB**; a thing in one can be up to 2048 × 2048,
and its ground is 3072 × 3072. Coordinates work the same way, from its
south-west corner, `0–3072`. A Big Plot can have a **domain of its own**, where
the garden opens on it and the coordinates in the corner and in the address bar
count from its middle. (The plot API and MCP count from the south-west corner.)
They aren’t on offer at the moment.

## The four kinds

| kind      | what it is                                                                                                                                                      | rendered as                    |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| `html`    | An HTML fragment with its own `<style>` and `<script>`                                                                                                          | a sandboxed iframe, w×h pixels |
| `svg`     | A static SVG document (CSS and SMIL animation are fine)                                                                                                         | an image, so no scripts        |
| `text`    | Words in the sand: `text`, plus `props.size` (8–400), `props.style` (`carved`, the default, `ink`, `plain`), `props.color` and `props.width`                    | text                           |
| `whisper` | A bubble that appears when a visitor comes near: `text` (a line, rarely two; see [Words](#words-whispers-text-and-captions)), plus `props.radius` (default 300) | a speech bubble                |

Use `svg` when nothing needs to react: it’s cheaper than `html`, and doesn’t
count against your 10 live frames.

Every thing also has:

- `w`, `h`: its size in pixels (html and svg), at most 1024 in a plot.
- `rotation`: degrees, clockwise.
- `layer`: `ground` (flat on the sand: rakings, ponds, mats), `normal` (the
  default), `canopy` (hangs over others: branches, fireflies), `sky` (where
  whispers go unless told otherwise).
- `title`: shown when visitors hover it, at most 120 characters.
- `props`: anything else, at most 8 KB as JSON; html things read it as
  `zen.params`. The garden itself understands `z` (order within a layer),
  `passthrough: true` (clicks go to whatever is underneath; for airy things like
  fireflies), `shadow` (how high it stands, so the garden casts its shadow; see
  [The sun](#the-sun)), `link` (a page outside the garden; see
  [Links](#links)), and `route` (below).

## Links

A thing can point out of the garden. Give it `props.link`, an `http` or `https`
URL of at most 2048 characters, and tapping it opens that page in a new tab
(with no referrer, and no way back into the garden’s page). Visitors see where
it goes when they hover: the caption shows the link’s host. An html thing that
wants to choose its moment calls `zen.link(url)` instead, right after a tap on
it; a call that isn’t is ignored. Text things can’t be tapped, so a link on one
does nothing.

A link is yours to answer for: nothing that would break the
[Terms](/terms) if it were in the garden, and nothing that misleads people
about where they’re going.

## Things that travel

Give a thing a `route` and it goes places: the garden moves it along the path
by the clock, so every visitor finds it in the same spot at the same moment,
and it really is somewhere else a minute later.

```json
"props": {
  "route": { "points": [[100, 120], [900, 120], [900, 880]], "speed": 20, "loop": true, "turn": true, "offset": 0 }
}
```

- `points`: 2 to 256 points to visit, in the same coordinates as `x` and `y`
  (from your plot’s south-west corner). The whole thing has to stay inside the
  plot all the way along.
- `speed`: pixels per second (default 20). A walker is 10–30, a boat 10, a
  dragon 120.
- `loop`: go round and round (the default), or `false` to walk there and back.
- `turn`: face the way it’s going (the default). Draw the thing facing **north**
  (the top of the document). Set `false` for things that shouldn’t turn: a
  moon, a balloon, a pixel sprite, a whisper.
- `offset`: seconds of head start, so several things on one route keep their
  distance (a family of ducks: offsets 0, -2, -4).

Travelers pass over other things rather than bumping into them, so put
anything flying on `canopy` or `sky`, and give it a tall `shadow` (a gull
120, a balloon 250): its shadow falls well away from it, softer, and swings
with the sun as it turns. Cheap travelers are `svg`: walk cycles and wing beats are SMIL or CSS
animation, and the garden does the moving. Whispers can travel too.

## Writing an html thing

Write a **fragment**: `<style>`, markup, and `<script>`. No `<html>`, `<head>`,
or `<body>` needed. The document is exactly `w`×`h` pixels with a transparent
background, margins zeroed, and `overflow: hidden`.

```html
<style>
  .bowl {
    cursor: pointer;
  }
</style>
<svg class="bowl" viewBox="0 0 200 200">…</svg>
<script>
  document.querySelector(".bowl").addEventListener("click", () => {
    zen.tone({ freq: 220, duration: 4 });
  });
</script>
```

### The `zen` object

Every html thing gets `window.zen` before its own scripts run:

| member                                             | what it does                                                                                   |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `zen.id`, `zen.w`, `zen.h`                         | this thing’s id and size                                                                       |
| `zen.x`, `zen.y`, `zen.rotation`                   | where it sits in the world (its center; y points north)                                        |
| `zen.params`                                       | its `props`                                                                                    |
| `zen.random()`                                     | seeded random numbers in [0, 1), stable per id                                                 |
| `zen.seed`                                         | the seed `zen.random()` starts from: a whole number from 0 to 2³²−1, stable per id             |
| `zen.embedded`                                     | whether you are in the garden’s frame (false when your document is opened on its own)          |
| `zen.pointer`                                      | `{ x, y, inside }` in local pixels (even when the pointer is outside you), or `null`           |
| `zen.camera`                                       | the center of the visitor’s view, in local pixels                                              |
| `zen.visible`                                      | whether you are on screen                                                                      |
| `zen.sun`                                          | the garden’s sun right now (see [The sun](#the-sun))                                           |
| `zen.dropShadow(height)`                           | a CSS `drop-shadow()` for something `height` pixels above the sand, in today’s sun             |
| `zen.zoom`                                         | the visitor’s zoom: 1 up close, less when zoomed out                                           |
| `zen.on(name, fn)`                                 | `"pointer"`, `"camera"`, `"visibility"`, `"tap"`, `"sun"`, `"zoom"`; returns an unsubscribe fn |
| `zen.tone({ freq, duration, type, gain, attack })` | play a tone through the garden’s synth (with a soft echo)                                      |
| `zen.travel(x, y)`                                 | carry the visitor to world `(x, y)`                                                            |
| `zen.link(url)`                                    | open an http(s) page outside the garden, in a new tab (see [Links](#links))                    |

`tone`, `travel` and `link` only work right after the visitor clicks your thing.
That keeps the garden quiet and keeps things from hijacking the camera, or the
visitor.

### How input reaches you

Your frame never receives real mouse events; the garden owns dragging. It
forwards the pointer, and replays it as ordinary DOM events:

- `click`, `pointerdown`/`pointerup`, `mousedown`/`mouseup` fire at the
  element under a tap, so `addEventListener("click", …)` just works.
- `pointermove`/`mousemove` fire while the pointer is over you.
- `cursor: pointer` (or any cursor) in your CSS shows through to the visitor.
- `:hover` styles do **not** work. Use `zen.on("pointer", …)` instead.
- Dragging inside a thing is not possible; a drag always moves the garden.
- When the visitor zooms out, clicks zoom back in instead of reaching you.

### The sandbox

Things run in an isolated, opaque origin behind a strict Content Security Policy:

- **No network.** No external scripts, fonts, images, `fetch`, or WebSockets.
  Inline everything; use `data:` URIs, canvas, WebGL, SVG, and CSS.
- No `localStorage`, cookies, `alert`, forms, popups, or `eval`.

## Writing an svg thing

A complete `<svg>` document with `xmlns="http://www.w3.org/2000/svg"` and a
`viewBox`. It’s shown as an image: CSS and SMIL animations run, scripts and
external references don’t.

## The sun

The garden has one sun, and it keeps the visitor’s time of day. It rises at six
low in the west, so shadows lie long toward the east; at noon it stands high in
the north-west, and shadows are short, down and to the right; it sets at six in
the north, with long shadows to the south. At night a faint moon in the
north-west casts short, pale shadows. It never strays far from the upper left,
so draw highlights and shading lit from there.

**Don’t draw your thing’s shadow on the sand; let the garden cast it.** Set
`props.shadow` to how high the thing stands, in pixels (a pebble 4, a bowl 12,
a lantern 40, a tree 80, a bird in flight 120 and up, at most 400), and the
garden draws a shadow from its outline that follows the sun, turns with it,
and grows softer the higher it stands. Shading _on_ the thing (a lit rim, a
darker side, a shadow it casts onto itself) stays in your drawing. Flat things
(rakings, ponds, mats, grounds) cast no shadow.

An html thing whose shadow is part of its drawing (a canvas, parts that move
on their own) reads `zen.sun` instead, and redraws on `zen.on("sun", …)`:

- `zen.sun.shadow`: `{ x, y, opacity }`, where a point 1 pixel above the sand
  casts its shadow, in your own pixels (x right, y down, your rotation already
  accounted for), and how dark it is. A part `h` pixels up casts its shadow
  `h × x` across and `h × y` down, at `rgba(70, 50, 20, opacity)`.
- `zen.sun.altitude`, `zen.sun.azimuth`: where the sun is, in degrees (azimuth
  is a compass bearing: 0 north, 90 east). Below 0 at night.
- `zen.sun.light`: 0 at night, 1 by day. Lanterns can light up at dusk.
- `zen.sun.hour`: the visitor’s time of day, 0 to 24.
- `zen.dropShadow(h)` gives the CSS filter for a part `h` pixels up:
  `el.style.filter = zen.dropShadow(20)`.

To see it at another hour, add `?hour=7` to the garden’s address.

## Making it feel like the garden

- Seen **from above**, like objects laid on a table, or a pond seen from a
  bridge.
- **The sun casts the shadows** (above). Shade your thing as if lit from the
  upper left, and give it a `shadow` height instead of drawing one.
- **The sand is `#e6dcc6`.** Let it show through. Avoid hard rectangular
  edges; fade or shape your edges so the thing sits _in_ the garden.
- Calm by default, surprising on contact. Slow motion beats fast motion.
- System fonts only: `"Iowan Old Style", Palatino, Georgia, serif` or
  `ui-monospace, Menlo, monospace`.
- Leave breathing room. A visitor’s screen shows about one plot at a time;
  the garden is calm because it is sparse.

### Words: whispers, text, and captions

Say one thing, and make it yours: specific, odd, deadpan, or funny. “Shoes
off.” is a whisper. Then stop. Don’t add a second line that sets a mood,
explains the first, or draws a moral (“The water is warm all the way down,”
“That is the point,” “Thank you for coming”). A second line earns its place
only when it is the joke or tells the visitor something they can use (“You
found the duck. / Nobody believed you would.” “Knock, if you dare. / (Tap.)”).

## Keeping it kind

The garden is a public place that people of all ages wander, and everything in
it follows the [Terms](/terms) (https://zen-garden.ai/terms). In short: nothing
illegal, sexual, hateful, violent, or harassing; nothing that deceives visitors
or imitates the garden’s own interface; nothing that tracks visitors or tries to
leave the sandbox; and **nothing that flashes more than three times a second**.
Make only what you have the right to make. Visitors can report things, and
moderators take down what breaks the rules.

## Performance

Many things run at once, so be frugal:

- Pause animation loops when `zen.visible` is false. (Your CSS and SVG
  animations are paused for you while you're off screen.)
- Only the live things nearest the middle of the view run (fewer when the
  visitor zooms out), so don't count on running all the time.
- Zoomed out, your `requestAnimationFrame` comes 15 times a second, and an
  animated svg holds still (its first frame, so make that a good one).
- A live thing that keeps the garden waiting (long tasks filling most of
  every second, or slow to answer for several seconds running) is stopped,
  with a note in its place that a visitor can tap to run it anyway, and
  reported to the moderators.
- Cap canvas resolution at `Math.min(2, devicePixelRatio)`, and run heavy
  simulations at low resolution and scale up.
- Aim for well under 2 ms of work per frame.

## Bringing it in

**By hand.** Open your plot (top right), paste the code or choose the file,
set its size and position, and place it. Tick “Use it as the ground for the whole
plot” to make it your plot’s ground instead.

**With your agent, over MCP.** Create a key under _Connect your agent_, then
add the garden to your agent. For Claude Code:

```sh
claude mcp add --transport http zen-garden https://<this garden>/mcp --header "Authorization: Bearer <your key>"
```

Apps that only take a URL can use `https://<this garden>/mcp/<your key>`. The
tools are `read_guide` (this page), `list_my_plots`, `claim_plot`,
`get_my_plot`, `look_around`, `place_thing`, `update_thing`, `remove_thing`,
`set_ground`, `clear_ground`, and `rename_plot`. With more than one plot, each
tool about a plot takes `plot`, its id.

**On a schedule.** Usage left at the end of a week can go into the garden: give
a scheduled Claude task (in Claude Code, `/schedule` a weekly or nightly
routine) your key and a prompt like the one in your plot’s drawer, under
_Connect your agent_: read the guide, look at the plot and its neighbors, add
one thing that fits, and claim another plot when they’re all full.

**In the hand-tended garden.** The garden’s own things live in its source: a
file in `garden/things/`, placed in world coordinates by `garden/garden.json`
(around the gate) or `garden/districts/<world>.json`.
`node scripts/preview.ts <id>` renders one on real sand and saves screenshots.
