Artstr 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, free-form custom art canvases, 16:9 slides assembled into presentable slide decks, and palette-indexed animated pixel art. Designs publish to and load from Nostr, are forkable and editable in place, and can be tipped via Lightning.
The editor has two tabs at the top level, each with its own Layout panel:
- Template — designs that map to a real printed surface, plus the deck container.
- 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 (chosen via the Template preset).
- Slide deck — an ordered, self-contained presentation deck that embeds its slides inline (see below).
- Designer — free-form and reusable designs.
- Custom art (the Designer tab's default) — a free-form canvas with size presets (1920×1080, square sizes, etc.) or custom dimensions. Treats the project as generic art rather than packaging.
- Slide — a 16:9 design surface (a Custom Art canvas locked to 16:9) with a speaker-notes field. A standalone, publishable design that can be imported into a slide deck.
- Disc design — a reusable single-disc design you can publish on Nostr and import later into either disc position of a disc-label sheet.
- Pixel art — a dedicated palette-indexed grid editor (single static sprite up to 128×128, or a multi-frame animation). Publishes as
casewrap-pixelart; exports to PNG, sprite sheet, and animated GIF.
Each layout keeps its own metadata (title / identifiers / category / language) so switching layouts — or loading a project — doesn't overwrite a design already in progress.
A left-edge tool palette drives canvas interaction; a matching right-edge panel holds the active tool's options. Both are docked flush with the canvas.
- Select (
V) — select, move, resize, and rotate layers. - Hand (
H) — drag to pan the canvas. - Pen (
P) — click to place Bézier anchors, click-drag to pull curve handles; closing a path fills it. Selecting an existing pen path — or a primitive / single-path SVG via Make editable path — exposes draggable anchors and handles. - Pencil (
B) — freehand strokes, committed as vector paths at a constant on-screen thickness. - Shape (
S) — drag out a rectangle / rounded-rect / circle / ellipse / triangle / polygon / star / line. - Text (
T) — click to drop a text box, then type.
The Pen / Shape / Text panels also edit the selected shape's fill and stroke.
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. - QR code — encode any URL or text as a scannable QR layer. Adjustable error-correction level (L/M/Q/H), quiet zone, module / background colors, and an optional transparent background for placing over artwork.
- Pixel art — a palette-indexed editable grid up to 128×128 cells, with full pencil / eraser / fill / eyedropper / line / rect / ellipse tools, mirror X/Y, flip/rotate/resize transforms, multi-frame animation with playback and onion-skin, an in-app HSV picker, an inline screen-eyedropper, and PNG / sprite-sheet / animated-GIF export. Importable from a published design (Browse community), a local JSON file, any image file (image → pixelize with median-cut palette quantization), or an animated GIF, which an inline GIF89a decoder turns into one editor frame per GIF frame — sharing a single palette across the animation and adopting the source's own playback rate.
Every layer supports drag / resize / rotate / opacity / z-order, an aspect-ratio lock toggle on the W/H inputs (default on), a lock toggle that prevents accidental drags, and a visibility (eye) toggle in the layer list.
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.
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.
Artstr Studio doubles as a Nostr-native presentation tool.
- Slide — a 16:9 design surface on the Designer tab. Mechanically a Custom Art canvas locked to 16:9, plus a speaker-notes field. A slide is a first-class design: publish it, fork it, load it.
- Slide deck — a Template-tab container holding an ordered, variable-length list of slides. The deck embeds every slide's full data inline, so a published deck is a single self-contained event — no external lookups.
- Deck Builder — a reorderable grid of slide thumbnails. Add a blank slide, import a slide from Nostr or a file (non-16:9 imports are scaled-to-fit and letterboxed), duplicate, delete, and drag to reorder. Click a thumbnail to edit that slide in the canvas editor; edits write back into the deck.
- Deck theme — a deck-level font and background that restyle every slide at render time without altering the slides themselves; any slide can opt out.
- Presenter Mode — a full-screen runtime that plays a deck: the themed current slide, speaker notes, a next-slide thumbnail, and a slide counter. Navigate with on-screen controls, click-to-advance, or the keyboard (arrows / space / Page keys / Home / End / Esc). A Presenter ⇄ Audience view toggle switches between the notes-and-thumbnail layout and a clean slide-only full-screen view. Launch it from the ▶ button in the Deck Builder tool palette, or from Start Presentation on a deck's preview page in the community browser.
A dedicated palette-indexed editor for sprites and animations.
- Two faces — drop a
pixelartlayer into any other design (a cover, a slide, a custom-art canvas, etc.) or run Pixel Art as a standalone Designer-tab layout where the editor view is the whole canvas. - Tools — pencil / eraser / fill bucket / colour eyedropper / line / rect / ellipse (with a Fill toggle), mirror X/Y, grid toggle, zoom; an in-app HSV picker (drag the saturation/value square + hue strip, then + Add to push the colour into the palette).
- Transforms — flip H/V and rotate 90° CW/CCW apply across every frame; grid resize 4–128 per side, top-left anchored, transparent fill.
- Animation — a vertical timeline strip on the left holds the frames: add / duplicate / delete / drag-reorder / click to switch. Left / Right arrow keys cycle frames; Space plays / stops. Per-design FPS (1–60) and Loop, plus an Aseprite-style red-prev / blue-next onion-skin toggle.
- Export — crisp PNG at 1× / 2× / 4× / 8× / 16× / 32× (transparency, optional grid lines), sprite-sheet PNG (Grid or Row layout) with all frames tiled, and animated GIF via an inline GIF89a encoder (no vendored dependency — pixel-indexed input is what GIF expects natively).
- Import — Browse community opens the Nostr browser filtered to pixel-art designs and drops the chosen one in as a new layer; Import from file takes a local JSON design; From image / GIF… takes any image file, then a live side-by-side preview tunes target W/H, colour count, and fit mode (Cover / Fit / Stretch) before applying. Both preview panes are framed identically, so the fit modes are directly comparable. Apply to picks the destination — Current frame / New frame keep the sprite's existing grid (so W/H are locked to it; every frame shares one grid), while Whole sprite rebuilds it and is the only target that can resize.
- GIF import — an inline GIF89a decoder (the mirror of the exporter, still no vendored dependency) converts every frame of an animated GIF into a frame in the editor. A browser
<img>only ever hands canvas the first frame, so decoding is the only route to the rest; it covers LZW, interlacing, local colour tables, transparency, and all three disposal methods. The whole animation is quantized against one shared palette so colours don't shimmer between frames, playback FPS comes from the GIF's own frame delays, and a Frames control samples a long animation down evenly. A GIF that is already at grid size with a limited palette imports pixel-exact. Selecting several stills at once imports them as consecutive frames. - Undo / redo — fully integrated with the top-level toolbar; while the editor is open, Ctrl+Z / Ctrl+Shift+Z drive the pixel editor's local stack, restoring all frames plus the current-frame index together. Standalone-mode edits also push debounced entries to the global design history.
- Animated previews — multi-frame designs animate in the publish-confirm modal, the community browser, feed-card thumbnails, and profile pages. The RAF loop self-stops when the preview leaves the DOM, so closing a modal or scrolling a card out of view doesn't leak loops.
The canvas is a unified, Illustrator-style workspace: the tool palette, the scrolling canvas, the contextual options panel, and the zoom bar are docked flush around the artboard. Sidebar panels are collapsible (click the heading); they start collapsed except the Layout panel, and selecting a layer opens the Layers panel automatically. The whole side panel can also be collapsed via a chunky vertical handle on the seam — handy when the canvas needs the full width for a complex pixel sprite or a wide custom-art design. State persists across reloads.
- Undo / redo — Ctrl+Z / Ctrl+Shift+Z (and Ctrl+Y), or the topbar buttons. A 50-deep in-memory history covering layer edits, mode switches, artwork loads, metadata, and more. Defers to the browser's native text-input undo while an input is focused.
- Fit-to-view by default — the canvas auto-scales so the whole artboard fits the preview area; it re-fits on window resize. The bottom zoom bar has zoom in/out, a 20–200% slider, Fit, and 1:1.
- Off-canvas veil — layer content that extends past the artboard edge renders under a translucent veil, so the printable area is always visually clear. The veil is dropped from PDF / JPEG export.
- Keyboard —
Delete/Backspaceremoves the selected layer; arrow keys nudge it (Shiftfor a larger step);[/]send it back / forward;Ctrl+Dduplicates;Ctrl+C/X/Vcopy / cut / paste a layer.
Every design surface participates in the same Nostr-backed community feed.
- Identity via NIP-07 (Alby, nos2x, etc.). The Login button opens a
picker — NIP-07 today, NIP-46 remote signer and raw
nsecpaste reserved as future options. Once signed in, the Nostr widget becomes a dropdown with View Profile, Settings, and Logout. - Settings modal — the per-relay list, the WoT trust filters, the NWC wallet connection, and the block-list management all live here.
- 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.
- Archive & rebroadcast (Settings) — a relay-migration tool. Sweeps every Artstr design still readable across your relays and the old defaults (deliberately including relays currently marked dead, since a relay unreachable from one network may answer from another), saves the signed events to a JSON bundle, then re-publishes them to your current relays. Signed in, it also picks up your own private and premium designs, which carry randomised
d-tags and are findable only by their author — the pubkey is requested from the signer directly rather than read from app state, because a connected extension alone does not populate it and getting that wrong silently skips every private design. Retracted designs are never resurrected (kind-5 deletions are honoured and relayed alongside, scoped to the archive's own authors), and only the newest revision of each addressable design is sent. Publishing uses one socket per relay with a bounded in-flight window, per-event acknowledgement, and a single retry on rate-limit, so every event is accounted as accepted / rejected / unanswered. - 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.
Three 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. - NWC (NIP-47) wallet — paste a
nostr+walletconnect://…URI under Settings and Artstr can sign + sendpay_invoicerequests directly to your wallet (CoinOS-first onboarding, but any NWC-compatible wallet works). NWC powers one-click tips, premium unlocks, and the split-zap legs. The URI is synced across logins as an encrypted kind-30078 note keyed to your pubkey, so a new browser only needs your NIP-07 extension to reconnect the wallet. - 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.
Creators can publish paid templates. The publish modal exposes a Premium encrypted toggle that requires a sat amount and a short "soft-gate, not strong DRM" acknowledgment.
- Static soft-gate. The payload is AES-256-GCM encrypted in the browser with a key derived via HKDF-SHA256 from public event material plus an obfuscated per-epoch pepper baked into the static client. There is no server and no creator-online step. Honest framing: this raises friction against drive-by scraping but a determined reverse-engineer can recover the client pepper. Stronger DRM can swap in later without changing the buyer-side vault.
- Watermarked preview. A low-resolution JPEG of the design is rendered pre-encrypt and embedded in the envelope, so the community browser still has something to show.
- Split-zap platform fee. Every unlock is a Lightning split — creator 70 %, platform 30 % — both legs fired in parallel over NWC, with the platform leg's
lud16resolved dynamically from the platform pubkey's kind-0 so it can change without a code release. - Purchase vault. Once both receipts confirm, Artstr derives the key locally, decrypts in place, and records the unlock in a NIP-44 self-encrypted kind-30078 vault (
d=artstr:purchase-vault:v1). The vault auto-splits into per-item events when over 40 KB. A fresh browser hydrates the vault on Nostr login (toast: "Restored N unlocks ✓"). Edit-in-place is address-tolerant, so re-publishing a premium design doesn't invalidate prior unlocks. - Premium browser tab. New ⚡ Premium tab in the community modal lists encrypted designs; cards get a gradient stroke + PREMIUM ribbon. The My Designs → Premium sub-tab lists everything you've unlocked.
Spec lives in docs/PREMIUM_DESIGNS_FEATURE.md.
- Save project writes the design (with metadata) to a JSON file.
- Load from file detects which layout the JSON belongs to (cover / disc / jewel / custom art / disc design) and switches to it 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.
- 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).
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). The GIF89a encoder for pixel-art animation export is implemented inline inindex.html.src/transparency-iframe.html— minimal transparent stub used by the embed transparency baseline tests.docs/— feature specs.- Shipped:
PEN_TOOL_FEATURE.md(pen / pencil / vector tooling),SLIDE_DECK_FEATURE.md(slide / deck / presenter, Phases A–D; dual-window presenter still future),PIXEL_ART_FEATURE.md(palette-indexed pixel art, Phases A–D),PREMIUM_DESIGNS_FEATURE.md(NWC split-zap + soft-gate + purchase vault),LINKED_DESIGNS_FEATURE.md(cross-reference imports + auto-update + attribution chain),PPTX_IMPORT_FEATURE.md(Phases 0–7 of PowerPoint deck import),CHART_IMPORT_FEATURE.md(Phases 1–4 of native OOXML chart rendering),EXTERNAL_EMBED_FEATURE.md(iframe-able read-only viewer with code generator). - Partial:
PRIVATE_PUBLISHING_FEATURE.md(Phases 1+2 shipped; share-with-pubkeys + gift-wrap metadata privacy deferred). - Drafted-but-unbuilt:
BOOK_DESIGNER_FEATURE.md(unified book mode — cover spread + interior pages + masters + PDF export),STACKS_FEATURE.md,BADGES_FEATURE.md,COLLECTIONS_FEATURE.md.
- Shipped:
TODO.md— running list of deferred work and cleanup items.
- 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,slide,deck,pixelart. Aslidepayload carries aslideobject (canvas dims + speakernotes); adeckpayload carries adeckobject (theme+ an inline orderedslidesarray); apixelartpayload carries apixelArtobject (width/height/palette[]of#rrggbbaastrings +frames[]of{ id, pixels: <RLE> }+ optionalfpsandloop). - Layer types:
image,text,color,shape,qr,pixelart. Shape kinds:rect,rounded-rect,circle,ellipse,triangle,polygon,star,line,path,svg. Any layer may carry aclipfield for masking. Pixel-art layers carry their ownpixelArtobject inline (same shape as the standalone payload).
- 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.