# Frameleaf Image Studio 1.0

An original offline image editor and a reusable browser component. Crop and resize an image, rotate/flip it, adjust its colors, add text, save an editable project and export actual PNG/JPEG/WebP pixels. No runtime framework, account, API key, CDN, upload server or build step.

**Status:** locally verified delivery, 16 October 2026. 48 automated state/raster/synthetic editor tests and seven actual CUA in-app-browser cases passed. Genuine download/chooser/full-JSON roundtrip, PNG/JPEG/WebP output viewing, embed instance isolation and 1440 × 900 / 390 × 844 viewport reviews are recorded in `evidence/qa/browser/`. The source-bound aggregate acceptance report stays in `evidence/qa/report.json` outside the ZIP. Physical touch devices, other browser engines, deployment and sales are unverified.

## Use the editor

Unzip the archive and open `app/index.html` in a modern JavaScript-enabled browser using your approved local access method. Keep the `app` files together. No installation is needed to run the editor. The included Cedar Tea illustration is original synthetic artwork, not a real product photograph or advertisement.

1. Choose **Upload image** for PNG, JPEG or WebP (up to 8 MB, 16 MP, maximum 12000 px per side). Invalid/corrupt images or a project/image-dimension mismatch leave the prior project intact.
2. Choose **Draw crop**, drag on the original source image, then **Apply crop**. You can also use exact X/Y/Width/Height source-image fields. Crop is non-destructive: the original image stays in your JSON. Escape cancels the pending crop.
3. Set output Width or Height, choose whether to Keep aspect ratio, then **Apply size**. **Rotate 90°**, **Flip horizontal** and **Flip vertical** update the image pixels and output dimensions. Cropping operates before rotation and resize.
4. Move Brightness, Contrast, Saturation, Grayscale or Sepia sliders. These operations apply to actual pixel data. The preview is capped at 1400 px on its longest side; export uses the full output dimensions.
5. Choose **+ Add** under Text layers. Edit multiline text, size, color, alignment and X/Y percentages, then **Apply text**. Select a layer in the list or click its rendered text to drag it. Font size is in final-output pixels; normalized positions survive resize. Some text can extend beyond the canvas edges, so inspect your output.
6. Use **Undo/Redo** (40 snapshots). **Reset edits** restores the original image and removes text; Undo can restore the prior state. History shares the immutable background image instead of duplicating its bytes per change.
7. Choose **Save project** for a `.frameleaf.json`. **Open project** restores all image/edits/layers; unsaved replacement requires confirmation. The browser determines the actual download destination. Verify the saved file in your Downloads folder. Project files contain the full original embedded image, so share them only intentionally.
8. Pick PNG, JPEG or WebP in Export settings. PNG preserves transparency. JPEG puts a white background behind transparent pixels. JPEG/WebP quality is adjustable; PNG is lossless. **Export image** requests a real encoded file. Open the downloaded file to verify its dimensions and appearance. Unsupported browser encoders show an error instead of silently changing the file type.

Keyboard shortcuts work while focused inside this editor: Ctrl/⌘ S saves JSON; Ctrl/⌘ Z undoes; Shift + Ctrl/⌘ Z or Ctrl Y redoes; Escape cancels crop/drag; Delete/Backspace removes selected text except while typing. Text fields preserve normal typing behavior. Pointer Events support drawing/dragging, while panels scroll normally. A narrow-viewport visual check does not prove all physical-device touch behavior.

## Embed the same working component

```html
<link rel="stylesheet" href="frameleaf/styles.css">
<div id="image-editor"></div>
<script src="frameleaf/core.js"></script>
<script src="frameleaf/renderer.js"></script>
<script src="frameleaf/frameleaf.js"></script>
<script>
const editor = Frameleaf.mount(document.getElementById('image-editor'), {
  project: myFrameleafProject, // versioned project containing an embedded image
  onChange(project) { /* keep a local draft in your own app if desired */ },
  onExport(result) { console.log(result.width, result.height, result.type); }
});
awaitReady(editor);
async function awaitReady(editor) {
  await editor.ready;
  const output = await editor.exportImage('image/png');
  // output.blob is the actual encoded file; your host controls its delivery.
}
</script>
```

See `samples/embed.html` for a complete original demonstration with an external host button and two independent mountable components. Multiple instances use scoped controls and keyboard events. Styling uses the distinctive `.fl-*` classes and `.frameleaf` root; standalone page background styles belong to index.html, not the component CSS.

API:

- `ready`: Promise that resolves after the initial project's real image decode/render setup; rejects on invalid artwork.
- `getProject()`: a validated state copy. Editing it does not mutate the editor.
- `loadProject(project)`: Promise, validates and decodes before changing the editor. API-driven loads are controlled by the host; interactive loads ask about unsaved replacement.
- `exportImage(type, quality)`: Promise `{blob, width, height, type}`. Types: image/png, image/jpeg, image/webp. Quality: 0.1–1 (PNG ignores quality).
- `destroy()`: removes component content, pending draft/render tasks and window unload handler. Destroy instances before removing their containers.
- Options: `project`, `onChange`, `onExport`, optional `autosaveKey`, optional `warnOnUnload`. A unique autosaveKey is necessary if you explicitly enable drafts on multiple instances. The standalone app enables a local draft and unsaved-unload warning; embeds opt in.

## Privacy and backup

The editor performs no network requests. Images stay in local memory and, only when configured, browser-local draft storage. A saved JSON contains the original image and all editing metadata. Do not use real patient or sensitive personal data as test artwork. Browser file-origin storage and private browsing vary; quota failure is visible. **Downloaded JSON is the durable backup**, not a guarantee that localStorage survives browser restarts. Use Recover browser draft if available.

Text is rasterized or inserted into controls with textContent, not interpreted as HTML. Project schema rejects executable/external image URLs, duplicate layer IDs, invalid dimensions and unbounded output. Raster decode must match declared dimensions. A legitimate image can still be visually misleading; you are responsible for reviewing published material.

## Developer commands and artifacts

Runtime users need no npm install. For development, use Node 20+ and Python 3.10+: `npm ci --ignore-scripts`; `npm test`; `python3 scripts/package.py`. Pinned jsdom and @napi-rs/canvas are development-only tools. Tests cover state, geometry, actual raster pixels/encoders and synthetic DOM integration. They do not operate a real browser. `scripts/sample.cjs` rebuilds original sample artwork/project/expected raster; a changed sample requires affected QA to rerun.

`samples/cedar-tea.png`, `cedar-tea.frameleaf.json` and `cedar-tea-edited.png` are the original artwork, editable filled example and actual renderer output. `qa.config.json` binds the product to the seven required real-browser cases. The ZIP is checked for CRC and exact byte equivalence; `evidence/package-verification.json` remains outside the archive. Aggregate QA reports are also outside the archive to avoid self-referential package hashes.

## Limits and release responsibilities

No native source-SVG editing, PSD layers, PDF import, freehand painting, AI transformations, hosted accounts or native CMS installer. Browser memory limits, encoder support and font rasterization vary; sans-serif text can look slightly different across platforms. Physical touch/pinch, other browser engines and large-device performance require separate evidence. Installation/opening, actual downloads, complete project reopen, output inspection and desktop/mobile visual checks passed in the supported Codex in-app browser. Publication and deployment remain subject to the release process.

## Free-product use terms

The approved **Nishvault Free Product Use Terms — v1** in `LICENSE.txt` govern use of the free editor. They permit personal and commercial use and export, sharing, embedding or sale of your created outputs; uploaded artwork remains subject to its rights holders. They do not permit redistribution or resale of the product itself, its complete source/package or a competing rebranded copy. No paid checkout, recurring charge, guaranteed support, service-level agreement or update schedule is included. Optional custom integration requires a separate agreed scope before any payment or work commitment. Third-party dependency notices remain in `THIRD_PARTY.md`.
