Undo / redo (Phases A-C per docs/UNDO_REDO_FEATURE.md): - Memory-only stack of structuredClone'd state snapshots, capped at 50 entries. UNDO_SNAPSHOT_FIELDS lists exactly which editor-state fields participate (appMode, templateMode, savedMetaByMode, meta, cover dimensions + artwork, discs / discMeta / spine labels, jewel, customArt, discDesign, layers, selectedLayerId, editing-in-place context, display toggles). Community / cache / clipboard state is intentionally excluded. - pushHistory captures pre-change state; pushHistoryDebounced collapses rapid edits (slider drags, typing, color-picker scrubs) into a single history entry per debounce window. _isRestoring guard prevents render-driven re-pushes during restore. - Restore re-runs the sync/render pipeline so input fields, mode tabs, and the editing banner all reflect the rolled-back state. - Undo / Redo buttons in the topbar with disabled state, snapshot label hints in the title attribute, and Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y keyboard shortcuts that defer to the browser's native text-input undo whenever an editable element has focus. - Push triggers wired across layer lifecycle (add / delete / duplicate / cut / paste / paste-all), drag-end on pointerup, every shape paint property (fill, gradient stops / angle / center / radius, stroke type / color / width / dash, kind-specific params, per-element SVG fill / stroke / stroke-width), clip kind / inline-shape params / layer-ref selection, custom-path / SVG upload modal confirm in create / edit / edit-clip modes, mode switches (Template <-> Disc Designer plus cover / disc / jewel / customart), close project / clear images / load project / fork / enter and exit edit mode, slot artwork loads + transform inputs (cover slots, combined wrap, disc design, jewel inserts), spine labels, metadata text fields (debounced) and category / language selects, Custom Art canvas preset / size / background, dimension inputs (panelW / trimH / spineW / bleed / safe), preset selector, layer reorder (forward / back), and the display-options toggles. Canvas zoom + scroll toolbar: - New always-visible toolbar below the preview area with zoom-out, zoom slider (20-200%), zoom-in, percent readout, Fit, and 1:1. Sits as a non-scrolling sibling at the bottom of main's flex column so it stays on screen regardless of canvas scroll. - Default zoomAuto = true. computeFitZoom reads previewShell client size and picks a percent that lands the sheet inside the viewport with ~32 px breathing room. Window resize recomputes if auto-fit is still active. Touching any zoom control turns auto-fit off; hitting Fit turns it back on. - main is now a flex column with max-height: 100vh; previewShell flex: 1 + overflow: auto + min-height: 0. The previous min-height: calc(100vh - 112px) is gone since flex sizes correctly. Off-canvas veil: - A transparent .canvasVeil child inside .sheet with inset:0 and a giant 9999px box-shadow at 62% preview-gray fills everything outside the artboard bounds. Layer content extending past the edge sits visually under the shadow so it looks faded, making the canvas edges crystal clear. Dark mode uses a dark-tint shadow. Print / JPEG export / export-print mode all hide the veil so saved output preserves bleed and off-canvas content. Selected-layer scroll-into-view: - Clicking a layer on the canvas now scrolls the sidebar's "Selected layer" panel into view (block: 'nearest', so it no-ops when already visible). Sidebar list clicks don't trigger the scroll since the panel is already in the user's view. Co-Authored-By: Claude Opus 4.7 <[email protected]>
CaseWrap Studio
CaseWrap Studio is a static, browser-only design tool for printable physical media packaging — and increasingly, a general-purpose Nostr-native graphics editor. It started as a DVD/Blu-ray case wrap maker and now also covers Avery 8960/8944 disc labels, Avery 8693/8943 CD jewel-case inserts, single-disc designs, and free-form custom art canvases. Designs publish to and load from Nostr, are forkable and editable in place, and can be tipped via Lightning.
Modes
The editor has two tools at the top level:
- Template — designs that map to a real printed surface.
- Case cover — DVD / Blu-ray wraps with separate front / spine / back artwork or a single combined wrap image.
- Disc labels — Avery 8960 / 8944 sheets with two disc positions.
- CD jewel inserts — Avery 8693 / 8943 sheets with a front insert + tray insert.
- Custom art — free-form canvas with size presets (1920×1080, square sizes, etc.) or custom dimensions. Treats the project as generic art rather than packaging.
- Disc Designer — make a reusable single-disc design, publish it on Nostr, and import it later into either disc position of a disc-label sheet.
Each mode keeps its own metadata (title / identifiers / category / language) so loading a template in the Disc Designer doesn't overwrite the Designer's work in progress, and vice versa.
Layers
Layers compose on top of the per-mode artwork:
- Image — remote URL, with object-fit / object-position controls.
- Text — rich editable text with font-family picker (web-safe families), size, color, bold/italic, alignment.
- Color block — solid-color rectangle for tints and backgrounds.
- Shape — vector primitives (rectangle / rounded rectangle / circle / ellipse / triangle / polygon / star / line) with solid, linear-gradient, or radial-gradient fills and optional solid strokes (color / width / solid / dashed / dotted).
- Custom path / SVG upload — upload an
.svgfile. Single-path SVGs land as editable shapes; multi-element SVGs preserve their internal fills and stacking, with a per-element editor that lets you change each<rect>/<path>/<circle>etc. fill and stroke individually.
Every layer supports drag / resize / rotate / opacity / z-order, an aspect-ratio lock toggle on the W/H inputs (default on), and a lock toggle that prevents accidental drags.
Layer clipping / masking
Any layer (image, text, color, shape) can be clipped to a shape:
- Inline shape clip — pick a primitive (rect / circle / star / etc.) to mask the layer to that shape.
- From another layer — point at another shape layer in the same target and use its geometry as the mask.
Renders consistently in the editor, the preview surfaces, and the canvas / PDF / JPEG export.
Layer clipboard
Cross-target and cross-tool copy/paste — Copy / Cut on a selected layer, Copy all / Paste for the whole panel. Paste retargets to the current sub-mode and clamps positions so layers copied from a wide cover don't land off-canvas on a narrow disc.
Nostr integration
Every design surface participates in the same Nostr-backed community feed.
- Identity via NIP-07 (Alby, nos2x, etc.).
- Templates and designs publish as kind-30078 parameterized replaceable events tagged
casewrap-<mode>, with thed-tag mode-prefixed so cover/disc/jewel/custom-art/disc-design designs for the same title don't collide. - Community modal browses New / Popular / Zapped / Search / My Designs with category and language filters, plus optional WoT trust filters when signed in.
- Profile pages at
/u/<npub>show the author's banner, avatar, NIP-05, npub (copyable), about, Lightning address, and the grid of their published designs. Profile state is cached per session. - Replies (kind-1), reactions (kind-7), reposts (kind-6), and follows (kind-3) all work from inside the app.
- Soft moderation — kind-10000 mute lists and kind-1984 reports from your hop-1 follows blur cards by default, with a one-click reveal.
- Block list — local first, syncs to your kind-10000 on the next login.
- Edit published designs — owners can republish a kind-30078 with the same d-tag to supersede the original on relays via NIP-33 semantics. The publish-confirm modal explains the trade-off (reactions and Lightning zaps on the old version stay on relays but orphan to the new event id).
- Fork / remix — every published design has a Fork button that loads it into the editor as a starting point under your own pubkey.
- Share links —
/share/<naddr>or/share/<nevent>URLs resolve cold.
Lightning
Two flavors of Lightning are wired in:
- Tips — pure LNURL-pay, no NWC required. Pick an amount (21 / 100 / 500 / 1k / 5k / 21k / Custom), the recipient's
lud16orlud06resolves to an LNURL-pay endpoint, and you get a bolt11 invoice + QR + lightning: deep-link. If the recipient's wallet supports zap requests (allowsNostr: true), we also sign a NIP-57 kind-9734 zap request and pass it along, so a kind-9735 receipt lands on relays after settlement. - Tracked zaps — kind-9735 receipts feed a per-event aggregate, surfaced as
⚡ <total>on feed cards / previews / profile pages, and as a sortable Zapped tab in the Community modal. - WebLN auto-pay is offered as a one-click button when an extension is detected.
- Payment detection — after a tracked invoice is issued, a poll watches for the receipt and auto-closes the tip modal when payment is confirmed.
Save, share, restore
- Save project writes the design (with metadata) to a JSON file.
- Load from file detects whether the JSON is a template or a disc design and switches the active tool automatically.
- Publish to Nostr opens a preview/metadata confirmation modal before signing.
- Copy share link copies an
naddr1…(replaceable-event address) ornevent1…(specific event id) URL. - Copy event ID is available once a project has been published.
- Export JPEG — round-trips through the browser's PDF export and PDF.js to produce a flattened image. Useful when you want a single shareable asset.
Print / Save PDFproduces print-ready output. Keep browser zoom at 100% and disable headers/footers for predictable margins.- Print-time toggles (
Show bleed,Show crops,Show guides) control which guides are inked.
Workflow notes
- All image references are remote URLs. Paste an
http/httpsimage URL into any slot or layer — the app never embeds binary image data into a project (keeps payloads small and Nostr-friendly). - If a URL goes 404 later, the design still loads — the image just doesn't render. Pick stable hosts. We persist a broken-image set to
localStorageso once we've detected a 404, it stays sunk to the bottom of ranked feeds across reloads. - Saved project JSON is portable. The same JSON is what publishes to Nostr (after stripping local-only fields).
Files
src/index.html— the complete single-file app. Open it directly in a browser, or serve it from any static host. Vercel rewrites invercel.jsonmap/share/:idand/u/:npubto the SPA entry.src/vendor/qrcode.min.js— vendored QR encoder (used for Lightning tip invoices).docs/— feature plans for unbuilt work (badges, collections, QR layer, TMDB autofill, undo/redo, NWC zaps, zap-gated templates).
Schema
- Current project schema version: 5.
- Minimum supported version: 4. Older payloads load but won't round-trip cleanly.
- Template-mode discriminator in payloads:
cover,disc,jewel,customart,disc-design. - Layer types:
image,text,color,shape. Shape kinds:rect,rounded-rect,circle,ellipse,triangle,polygon,star,line,path,svg. Any layer may carry aclipfield for masking.
Defaults and stack
- No backend. Fully static. No build step.
- NIP-07 browser extension required for any signing-side action (publish, reply, react, follow, block-sync, zap request, edit, delete).
- Lightning is optional. Without a signed-in NIP-07 wallet, tips fall back to plain LNURL-pay and aren't tracked as zaps.