# File & Export Formats

> How nittygrid saves your work in the browser today, how to back up projects (.ngproj) and recipes (.ngrecipe), and how rendered exports (.ngrender.zip, .ngrender.json, MP4) work.

Before the file types, one important thing: **where does your work live while you create?**

## How Project Persistence Works Today (Browser-Only)

nittygrid has no accounts or cloud storage yet. Everything is saved **in your browser, on this device**.

- **Autosave.** Every change (animation nodes, wires, chat history, background alignment) is written to your browser's `localStorage` 300 ms after your last edit. Matrix setup presets live in `IndexedDB`.
- **Tied to this browser.** A different browser, a private window, or another computer starts empty. Clearing your site data erases projects you have not downloaded.
- **Videos can expire.** A video you loaded but did not bake only exists until you close the tab. Click **Bake to pixel animation**, or download a **`.ngproj`** while the tab is open, to keep it.

> [!NOTE] **Coming later: Optional Accounts & Cloud Sync.** Optional accounts and cloud storage are planned. Downloadable `.ngproj` files and offline exports will keep working, so you never depend on our servers.

---

## Two kinds of files

- **Editable files (`.ngproj`, `.ngrecipe`).** They keep your animations as recipes, so you can open and change them later. Use them for backups, moving computers, or sharing.
- **Rendered files (`.ngrender.zip`, `.ngrender.json`, `.mp4`).** They contain the finished result as colors per frame, or as a video. Use them to play the animation on hardware or in other software, without nittygrid.

## Which file do I need?

- [Save or share a whole project → `.ngproj`](#1-editable-project-bundles-ngproj) — The full canvas, matrix setup, background photo, and every node's history in one ZIP.
- [Reuse one animation elsewhere → `.ngrecipe`](#2-reusable-recipe-bundles-ngrecipe) — One editable animation with its source matrix and media.
- [Drive LEDs or archive for the long term → `.ngrender.zip`](#3-rendered-rgba-exports-nittygrid-animation-v1-0) — Exact colors per LED, timing, the SVG layout, schemas, and a written spec.
- [Share a preview → `.mp4`](#4-mp4-video-export-mp4) — A normal video in the LED look, optionally on top of your product photo.

---

## 1. Editable Project Bundles (`.ngproj`)

Download a project from the **Projects** overlay to get a **`.ngproj`** file. Older `.at3proj` files still import.

> [!NOTE] A `.ngproj` is an ordinary **ZIP archive**. Rename it to `.zip` to look inside.

```text
<project-slug>.ngproj (ZIP archive)
├── manifest.json                   Format ID ("at3proj"), schemaVersion (2), and asset inventory
├── project.json                    Matrix setup, animation recipes, canvas graph & chat history
├── notes.md                        Human-readable project summary
├── thumbnail.png|jpg|webp          Project preview thumbnail (optional)
├── background/image.png|jpg|webp   Aligned product/vehicle background photo (optional)
├── matrix/layout.svg               Standalone sanitized SVG hardware layout (when using an SVG matrix)
└── assets/session-1.mp4|webm|...   Embedded video/image assets used by animations (optional)
```

**Good to know**

- **No broken links.** Temporary browser links (`blob:`, `data:`) are replaced by real files inside the ZIP, referenced like this:
  ```json
  { "__at3AssetRef": "background/image.png", "mimeType": "image/png" }
  ```
- **Canvas and chat are kept (schema v2).** `project.json` stores node positions, the `Start` → `End` wires, and each node's chat and dated versions. Older schema v1 projects import as unconnected nodes, because nittygrid will not guess a playback order.
- **SVG in both forms.** `project.json` keeps the complete matrix, including its SVG code. `matrix/layout.svg` is an identical standalone copy for other SVG tools. Import reads and cleans the JSON copy; changing only the standalone file does not change the project.
- **Safe import.** Every restored SVG is cleaned the same way as an uploaded one. Missing referenced background images, thumbnails, or session media stop import instead of silently losing content.

---

## 2. Reusable Recipe Bundles (`.ngrecipe`)

Export a single animation as a **`.ngrecipe`** to reuse it in another project. Older `.at3anim` files still import.

```text
<animation-slug>.ngrecipe (ZIP archive)
├── manifest.json                   format: "at3anim", schemaVersion: 1, entry: "animation.json"
├── animation.json                  Label, timestamp, AgentAnimation recipe & source matrixConfig
├── notes.md
├── preview.png|jpg|webp            Still preview when available (optional)
├── matrix/layout.svg               Source SVG layout snapshot when saved from an SVG matrix (optional)
└── assets/session-1.*              Embedded session media (optional)
```

A recipe stores the generator or `render_code`, not fixed-size pixels, so most recipes adapt to the new project's width `W` and height `H`. If the recipe includes its source matrix (`matrixContext: "saved-with-recipe"`), nittygrid compares it with your current project on import and warns about differences. It never overwrites your project's matrix.

SVG matrices use the same storage rule as projects: `animation.json` keeps the SVG code and LED metadata, and `matrix/layout.svg` is an identical copy for use elsewhere. Import reads the matrix from the JSON. Older recipes with only the JSON copy still import.

---

## 3. Rendered RGBA Exports (`nittygrid-animation` v1.0)

A hardware controller or script should not need to run JavaScript or know nittygrid's generators. It only needs to know **which LEDs exist, how fast frames advance, and the color of each LED in each frame**. That is exactly what this format contains.

> [!IMPORTANT] The `nittygrid-animation` (`"version": "1.0"`) format and the self-contained **`.ngrender.zip`** (or a lone **`.ngrender.json`**) are designed to be readable offline, even if nittygrid no longer exists. The full specification is also at [`/formats/animation/1.0/FORMAT.md`](/formats/animation/1.0/FORMAT.md).

1. **One color model**
   Every LED in every frame is 4 bytes: `R, G, B, A`, each `0`–`255`. Monochrome uses the same layout with `R = G = B`. Bytes are written exactly as rendered, with no gamma change and no premultiplied alpha (`rgba8`, `srgb`, `straight`).
2. **Two ways to name LEDs**
   - **Regular grid (`layout.type: "grid"`):** just `rows`, `cols`, and `order: "row-major"`. LED IDs (`r0c0`, `r0c1`, ...) are derived from the top-left, so the file stays small.
   - **Irregular or SVG layout (`layout.type: "explicit"`):** only the real LEDs are listed in `layout.pixels`, such as `[{ "id": "a", "row": 0, "col": 0 }, ...]`. Gaps never create fake black pixels.
3. **Shapes stay in `layout.svg`**
   The JSON holds colors and timing only. Outlines live in the companion `layout.svg`, linked by `layout.reference.sha256`.
4. **Frames are Base64**
   Each entry in `frames.data` is one full frame in standard RFC 4648 Base64. Its decoded length is always `ledCount * 4` bytes.

### What is inside a `.ngrender.zip`

Unzip it with any tool. The JSON is self-describing, and `README.txt` and `FORMAT.md` include a short Python 3 example for reading it.

```text
<name>.ngrender.zip
├── README.txt                      Plain-text offline decoding guide + short Python 3 teaching example
├── manifest.json                   Package format ("nittygrid-animation-package" v1.0) & SHA-256 checksums
├── animation.nganim.json           The rendered RGBA frames, timing, and LED mapping
├── preview.apng                    Full-color LED-look animated PNG preview for instant viewing
├── FORMAT.md                       Bundled offline copy of the v1.0 format specification
├── animation.schema.json           JSON Schema for animation.nganim.json
├── manifest.schema.json            JSON Schema for manifest.json
├── LICENSE-MIT.txt & LICENSE-CC0.txt Free-license notices for the bundled schemas and specification
└── layout.svg                      Included for SVG matrices; matches layout.reference.sha256
```

### Example: regular 2×2 grid (`layout.type: "grid"`)

Two frames at 20 FPS (50 ms each, 100 ms loop). Each Base64 string decodes to `2 × 2 × 4 = 16` bytes in `r0c0`, `r0c1`, `r1c0`, `r1c1` order:

```json
{
  "format": "nittygrid-animation",
  "version": "1.0",
  "meta": {
    "name": "Four LEDs",
    "exporter": { "name": "nittygrid", "version": "0.10.0-alpha.1" },
    "exportedAt": "2026-10-17T10:00:00Z"
  },
  "layout": {
    "type": "grid",
    "rows": 2,
    "cols": 2,
    "order": "row-major"
  },
  "timing": {
    "mode": "fixed",
    "fps": 20,
    "loop": true
  },
  "frames": {
    "pixelFormat": "rgba8",
    "colorSpace": "srgb",
    "alphaMode": "straight",
    "encoding": "base64",
    "data": [
      "/wAA/wD/AP8AAP///////w==",
      "/////wAA//8A/wD//wAA/w=="
    ]
  }
}
```

### Example: irregular or SVG layout (`layout.type: "explicit"`)

Three real LEDs (`a`, `b`, `c`) over two rows, with a gap in the middle of row 0. Each frame decodes to `3 × 4 = 12` bytes. No fake LED is written for the gap:

```json
{
  "format": "nittygrid-animation",
  "version": "1.0",
  "layout": {
    "type": "explicit",
    "reference": {
      "path": "layout.svg",
      "sha256": "ede530d5b43f5d15bdde4b7b99af5559a9c753971ae98048db88effbba1a4ba4"
    },
    "pixels": [
      { "id": "a", "row": 0, "col": 0 },
      { "id": "b", "row": 0, "col": 2 },
      { "id": "c", "row": 1, "col": 1 }
    ]
  },
  "timing": {
    "mode": "fixed",
    "fps": 20,
    "loop": true
  },
  "frames": {
    "pixelFormat": "rgba8",
    "colorSpace": "srgb",
    "alphaMode": "straight",
    "encoding": "base64",
    "data": [
      "/wAA/wD/AP8AAP//",
      "/////wAA//8A/wD/"
    ]
  }
}
```

---

## 4. MP4 Video Export (`.mp4`)

The video looks like the studio preview, including round or square LEDs, SVG shapes, and optional monochrome.

On browsers with a supported video encoder, frames are calculated and encoded one at a time with fixed timestamps. A slow export takes longer to finish, but the resulting video's duration still follows the selected FPS. H.264 MP4 is preferred; when only VP9 is available, the download uses `.webm`. Older browsers use the existing real-time recorder with a limited frame buffer, so timing can vary slightly.

> [!TIP] Turn on **Include preview image** in the Export panel to place your aligned background photo under the glowing LEDs. The video uses the photo's size, rounded to even numbers as H.264 requires.

---

## Export memory and cancellation

Portable JSON and ZIP exports process each frame immediately and retain only the actual LEDs' encoded color values. They do not collect a complete rectangular image for every frame. The file data and ZIP/APNG work still need memory.

Use **Cancel export** to stop a running export. If an export exceeds a resource budget, nittygrid shows a message instead of continuing to allocate larger buffers. Use a shorter clip, fewer frames per second, smaller sampling addresses, or a smaller video output as appropriate.

Limits protect nittygrid's own buffers; they cannot measure or reserve the system's free RAM. Browser/encoder resources and animation-owned state use additional memory, and an exhausted system can still terminate a tab. Rendering and export currently run locally; optional server export is planned.

---

## Technical reference: all formats and limits

Generated from the app's own export code, so it always matches what nittygrid writes and reads. This table is for technicians and AI agents.

| File / Export | Format Identifier | Purpose | Key Rules & Budgets |
| :--- | :--- | :--- | :--- |
| `.ngproj` (legacy `.at3proj`) | ZIP archive (`at3proj`, schema v2; reads v1) | Editable project snapshot: matrix setup, `matrix/layout.svg`, background alignment, animation recipes, canvas graph (`nodes`/`edges`), and per-node chat/version history. | Embedded binary assets under `background/`, `assets/`, and `thumbnail.*` referenced via `__at3AssetRef`. SVG code stays in the JSON; `matrix/layout.svg` is a standalone copy. |
| `.ngrecipe` (legacy `.at3anim`) | ZIP archive (`at3anim`, schema v1) | Reusable single-animation recipe (`animation.json`) with source `matrixConfig` (`matrixContext: "saved-with-recipe"`), optional `matrix/layout.svg` snapshot, still thumbnail, and embedded session media. | Compares saved matrix context on import without replacing project settings; media hydrated from `__at3AssetRef`. SVG code stays in the JSON with an optional standalone copy; still previews use `preview.*`. |
| `.ngrender.zip` & `.ngrender.json` *(v1.0 spec)* | ZIP (`nittygrid-animation-package` `"1.0"`) + JSON (`nittygrid-animation` `"1.0"`) | Self-contained rendered RGBA animation for regular grids (`grid`) and irregular/SVG matrices (`explicit` LED ID order). | Required ZIP entries: `README.txt`, `animation.nganim.json`, `preview.apng`, `FORMAT.md`, `animation.schema.json`, `manifest.schema.json`, `LICENSE-MIT.txt`, `LICENSE-CC0.txt` (+ `manifest.json` and `layout.svg` for SVG matrices). Budgets: ≤ 1,000,000 LEDs, ≤ 36,000 frames, ≤ 64 MiB RGBA, ≤ 96 MiB JSON, ≤ 8 MiB SVG, ≤ 128 MiB expanded ZIP. |
| `.mp4` Video | H.264 / MP4 (`video/mp4`) | Rendered matrix video in the configured LED look (regular grid or physical SVG shapes). | Fixed-timestamp, frame-by-frame encoding where supported (H.264 MP4 preferred, VP9 WebM fallback); older browsers use a real-time recorder. Video canvases: ≤ 8192 px per side and ≤ 8,294,400 pixels each. Encoded output: ≤ 64 MiB. Optional **Include preview image** composites the aligned background. |
| Browser export working buffers | Local resource budgets; independent of file format versions | Render/encode one frame before producing more; keep only encoded LED data for portable outputs. | Up to four full-raster render buffers: ≤ 64 MiB combined. Legacy JSON and older-browser video frame retention: ≤ 64 MiB. Final file data, encoder/browser resources and generator-owned state use additional memory; free system RAM cannot be guaranteed. |
