diff --git a/TODO.md b/TODO.md index 009b3e6..566a5ca 100644 --- a/TODO.md +++ b/TODO.md @@ -86,6 +86,56 @@ landed with these intentional gaps: - **Pixel-art frame timeline.** Onion skin + frame list + FPS control. Largest open arc; transformative if pixel art is a focus. +### Industry-convention parity gaps (surfaced 2026-06) + +The Pen tool's Illustrator-parity arc landed in full, but several +universal design-tool conventions are missing from the rest of the +vector toolset. Each is small — typically one branch in the +existing drag handler. Surfaced while auditing the in-app docs +(authors expected these to work because every other design tool +has them). + +- **Select tool — Shift-constrain-axis on drag.** Hold Shift while + moving a layer to lock movement to horizontal / vertical / 45°. + Branch the drag handler on `e.shiftKey`. Universal in + Illustrator / Figma / Sketch. +- **Select tool — Alt-drag duplicate.** Hold Alt while starting a + drag to clone the layer at the pointerdown position and drag the + clone. Universal. +- **Select tool — Shift-aspect-lock on corner scale.** Hold Shift + during a corner-handle drag to keep the layer's aspect ratio. + Universal. +- **Select tool — Shift-rotation 15° snap.** Hold Shift while + rotating to snap to 15° increments. Universal. +- **Cmd/Ctrl + A select-all.** Selects every layer on the current + surface. Wire next to the existing Cmd+G / Cmd+] bindings. +- **Spacebar temp-pan from any tool.** Track keydown / keyup; flip + the active tool to `hand` while held, restore on release. Must + release cleanly mid-stroke (Pen tool already does its own + spacebar-reposition; carve around that). +- **Cmd/Ctrl + 0** to fit-zoom the artboard. Trivial — bind to + `resetZoomAuto`. The button already exists in the canvas toolbar. +- **Cmd/Ctrl + + / - / wheel** for zoom. Trickier because browsers + usually intercept Cmd+wheel for page zoom. Cmd+= / Cmd+- (no + wheel) is the safer compromise. +- **Shape tool — Shift-constrain.** Hold Shift while dragging a + shape primitive to constrain to a perfect square / circle / 45° + line. Universal in every drawing app. +- **Shape tool — Alt-from-center draw.** Hold Alt to draw from the + centre outward instead of corner-to-corner. Universal. +- **Text tool — drag-to-size.** Drag instead of click to size the + new text box (Illustrator's "type-on-area" vs "type-on-point"). + Today it's always click-to-place at a fixed size. +- **Pencil tool — smoothing slider.** Rolling average + Bézier fit + at commit time. Pencil today writes a literal polyline (M / L + segments); the spec gestures at smoothing in a "later phase". +- **Pencil tool — Alt-drag append.** Extend the currently-selected + pen / pencil path from its nearest endpoint instead of starting a + new layer. Illustrator behaviour. +- **Pencil tool — auto-close near start.** If the release point is + within ~10 px of the stroke's start point, close the path. Tiny + flourish that turns pencil into a viable closed-shape tool. + ## QR code layer — remaining phases Phases A and B shipped (the `qr` layer type: data, error-correction level, diff --git a/docs/BOOK_DESIGNER_TUTORIAL.md b/docs/BOOK_DESIGNER_TUTORIAL.md new file mode 100644 index 0000000..269eeae --- /dev/null +++ b/docs/BOOK_DESIGNER_TUTORIAL.md @@ -0,0 +1,376 @@ +# Book Designer — End-to-End Tutorial + +This is the hands-on walkthrough for authoring, publishing, sharing, +and exporting a book in Artstr Studio. For the full spec see +`BOOK_DESIGNER_FEATURE.md`. + +--- + +## 0. Setup (one-time) + +- Sign in with a Nostr extension (top-right). For private-publish testing the + extension needs NIP-44 or NIP-04 (most modern extensions do). +- In **Project Metadata** (left sidebar, any mode) set a **Title** and + **Category**. The book uses these for the manifest's d-tag and tag set. + +--- + +## 1. Start a new book + +In the **Template** tab on the left, pick the **Book** entry. The canvas +switches to the Pages overview. + +Three ways to start: + +- **Starter template** — when Pages overview is empty, four cards appear + (US Trade 6×9 / A5 / A4 / Square 8×8). Click one. The book is seeded with + a default master + text-frame, a starter chapter, and a "Title page" + designed entry. +- **Convert from deck** — if you have an existing deck (Deck mode → + slides), click **→ Convert to book** in the deck-sorter toolbar. *Or* a + "Convert deck to book" card appears on the empty Pages overview if a + deck exists. Each slide becomes one designed page (letterbox-fit into the + trim). +- **From scratch** — skip the starter cards. Use `+ Add page`, + `+ Add chapter`, or open `Document settings…`. + +--- + +## 2. Document settings + +Open via the book sidebar → **Document settings…**. + +- **Book size**: preset (US Trade / US Letter / A5 / A4 / Square) or + **Custom** to enter arbitrary trim/bleed/paper-thickness. +- **Spine width**: derived from page count × paper thickness by default, + or toggle off to enter manually. +- **Cover layout**: include spine? include back? +- **Page numbering**: enabled / startAt / firstNumberedPage / format + (use `{n}` or `Page {n}` etc). +- **Typography** (expand the `
` block): override fonts / sizes / + weights / colours per element — body, h1–h6, blockquote, codeBlock, + inlineCode, link, list, hr. **Reset to defaults** restores the v1 + stylesheet. + +--- + +## 3. Masters + +Open via the book sidebar → **Manage masters…**. + +A master is a reusable underlay applied beneath every page that uses it. +The starter template gives you one ("Body pages") with a text-frame +already added. + +- Click a master row's **Edit** → master-edit mode. The canvas now binds + to `book-master:`. Add headers, page-numbers, etc. +- Use the **+ Text frame** button (visible only in master-edit) to add + the reflow target if missing. +- Header / page-number text layers can use **tokens** — literal in the + master, resolved at render time per page: + - `{page-number}`, `{page-count}`, `{physical-page-count}` + - `{book-title}`, `{book-author}` + - `{chapter-title}`, `{chapter-number}` +- Click **← Back to masters** to leave the master. + +--- + +## 4. Pages overview + +Each tile is one manifest entry — a designed page or a reflow chapter +segment. + +Tile hover reveals actions (top-right): +- 📄 proof PDF (just this entry) +- ⎘ duplicate +- × delete +- master-picker dropdown (per-tile override) + +Toolbar buttons: +- `+ Add page` — blank designed page. +- `+ Add 10 pages` — bulk. +- `+ Add chapter` — reflow entry; opens the chapter editor immediately. +- `+ Import page` — paste an `naddr` / `nevent` to another author's + `casewrap-page` event. Imports as a `ref:`-only entry; layers paint + via the resolver. +- `▶ Read` — Reader Mode. +- `Export PDF…` — export modal. + +Drag tiles to reorder. + +--- + +## 5. Designed page editor + +Click a designed-page tile → opens the canvas. Top bar shows +**← Back to Pages**, **← / →** prev/next, **Publish**, **📄 Proof PDF**, +**⚡ Free / N sats**. + +- Add layers like any other Artstr canvas — text, shape, image, QR, + pixel-art, linked design. +- Master layers render underneath at 55 % opacity (non-interactive while + page-editing). +- Tokens in *master* layers resolve to this page's context; the same + tokens in this page's own text layers stay literal. + +--- + +## 6. Chapter editor (reflow) + +Click a chapter tile → markdown-on-left, preview-on-right. + +The textarea upgrades to **CodeMirror** with markdown syntax +highlighting + smart Enter (auto-continues list bullets, breaks out of +the list on an empty item). + +### Try every feature in one chapter + +```markdown +# Chapter Title + +A paragraph. **Bold**, *italic*, `inline code`. + +## Sub-heading + +Cross-ref: see [the intro](#chapter-1) and [the index](#index). + +A ^[GFM table] example: + +| Col 1 | Col 2 | +|-------|-------| +| a | b | + +A task list: + +- [ ] todo +- [x] done + - nested item + +A footnote ref[^1] in the middle of a sentence. + +[^1]: This is a footnote definition. + +{toc} + +{index} +``` + +What this exercises: +- `{toc}` → renders as a heading list with page numbers. +- `{index}` → sorted list of every `^[term]` marker in the book. +- `^[term]` → invisible in body; registers in the index. +- `[^N]` + `[^N]: def` → superscripted ref + "Footnotes" section + appended to the chapter. +- `[text](#chapter-3)` / `[text](#some-heading)` → gets `(p. 12)` + appended. +- GFM tables, task lists, nested lists all paginate. + +### Chapter source-bar buttons + +- **Import from Nostr** — paste an `naddr` / `nevent` / `note` / hex + pointing at a NIP-23 (or encrypted) article; pulls into + `chapterMarkdown`. +- **Publish chapter** — mints a `kind-30023` (public) or `kind-30078` + encrypted (private) article event under `chapter:` or + `book-chapter:`. Re-publishes replace in place. +- **↻ Refresh** — re-fetches a linked chapter (live or pinned). +- **Pin to current** / **Set to live** — freezes a specific event id + vs follows the d-tag. +- **Unlink** — disconnects the textRef. +- **📄 Proof PDF** — exports just this chapter's paginated pages. + +### Readouts + +Top right of the chapter editor shows word count + paginated page count ++ first warning ("Block too tall for the text-frame", "Long word +doesn't fit", etc). If you see a warning, your text-frame is too narrow +or your prose has a word longer than a column. + +--- + +## 7. Cover view + +Pages overview → **Cover** tab. + +- Spec summary + spread preview (back / spine / front panels). +- **Edit cover** → canvas widens to the full spread. Layers land on + `book-cover-front` by default; the layer-target dropdown switches + between **Front cover / Spine / Back cover**. +- **Publish cover** (top-bar) → `kind-30078` with `role:'cover-spread'`. + +--- + +## 8. Visibility chip + +Bottom of the book sidebar. Two states: + +- **Public** (default) — every event publishes plain-text. +- **🔒 Private** — manifest + every sidecar publish goes through the + encrypted envelope (AES-256-GCM, with the AES key NIP-44/NIP-04-wrapped + to *your own* pubkey only). The chip is disabled if your extension + lacks encryption capability. Publish-buttons show 🔒 to make this + visible from inside any editor. + +--- + +## 9. Publishing flow + +Per-surface publishes are **independent** — you can publish a single +page without touching the manifest. To make the whole book discoverable +as one event, also publish the manifest. + +**Recommended order:** + +1. Open each chapter → **Publish chapter**. +2. Open each designed page → **Publish page**. +3. Open the cover → **Publish cover**. +4. Manage masters → publish each master via **Edit** → top-bar + **Publish master**. +5. **Publish** in the top app bar → publishes the `casewrap-book` + manifest with `link` tags pointing at every sidecar. + +The book sidebar shows **⚠ Manifest has unsaved changes** when any +spec / order / list change diverges from the last published manifest. + +--- + +## 10. Resolver (on re-open) + +When you reload the page or open the book on another device: + +- Autosave restores `state.book` from local storage. +- The resolver fetches every referenced page / master / cover / chapter + from your relays. +- Cached events come from `localStorage` first (warm re-opens paint + instantly; cap 2 MB total content, LRU evicted). +- A background probe checks each ref for a newer upstream version → + tiles get a yellow **↑ Update** chip; click to apply. +- Force-refresh: book sidebar → **↻ Refresh from Nostr**. + +--- + +## 11. Reader Mode + +Pages overview → **▶ Read**. + +- Click left half of the page = back, right half = forward. +- Keyboard: + - **← / →** flip + - **Space / PageDown** advance + - **PageUp** back + - **Home / End** first / last + - **Esc** exits +- **☰ TOC** toggles the chapter-heading sidebar. +- Page counter + progress bar in the footer. +- Pages cache as you flip; forward flips prefetch the next two. + +--- + +## 12. Embed (share publicly) + +Once the manifest is publicly published, the resulting `naddr` works in +the embed iframe: + +```html + +``` + +URL params: +- `?page=N` — deep-link to a starting page (1-based) +- `?controls=0` — hide prev/next/fullscreen +- `?attr=0` — hide the author chip +- `?open=0` — hide the "Open in Artstr ↗" link + +The embed renders a cut-down Reader Mode (no TOC). + +--- + +## 13. PDF export + +Pages overview → **Export PDF…** opens the modal: + +- **Pages**: All, custom range (from–to), or current. +- **Include cover spread**: yes/no. +- **Include bleed**: extends each PDF page to trim + 2×bleed (centered). +- **Crop marks**: L-shaped marks at each trim corner (printer guidance). +- **Output**: single file vs `-interior.pdf` + `-cover.pdf` + split. +- **Filename pattern**. + +Reflowed prose using **Georgia / Times / Helvetica / Arial / Courier / +monospace** families emits as real PDF text (vector, selectable, +searchable). Other fonts fall back to vector raster, and the warning +summary lists which families fell back. + +**Proof one page** (📄 button on any tile or in any editor toolbar) → +one-click PDF of just that entry. No modal. + +--- + +## 14. Tokens reference + +Resolve at render time using the manifest's context. + +| Token | Where it resolves | +|-------------------------|-----------------------------------------------------------| +| `{page-number}` | This page's number under `spec.pageNumbering` rules. | +| `{page-count}` | Total numbered pages in the book. | +| `{physical-page-count}` | Total physical pages (including skipped-number entries). | +| `{book-title}` | `state.meta.title`. | +| `{book-author}` | Cached profile display name, falling back to short npub. | +| `{chapter-title}` | The chapter this page belongs to. | +| `{chapter-number}` | The chapter's ordinal. | +| `{toc}` *(standalone)* | Auto-generated TOC with page numbers. | +| `{index}` *(standalone)*| Auto-generated alphabetical index with page numbers. | + +Inline markers (in chapter markdown): +- `[^N]` … `[^N]: definition` → footnote ref + chapter-end definition. +- `^[term]` → invisible index marker; appears in `{index}`. +- `[text](#anchor)` → cross-reference; rewritten to `[text (p. N)]`. + +--- + +## 15. Edge cases worth probing + +- **Private round-trip** — switch to 🔒 Private, publish a chapter, + reload the page. The resolver should decrypt + repaint. +- **Cross-device** — publish the manifest publicly, paste the `naddr` + into the embed route on a different browser → it should resolve and + render. +- **Spec change → spine shift** — open Document settings, change paper + thickness or page count, look at the cover panel — spine width + should re-derive immediately. +- **TOC accuracy** — TOC page numbers are computed pre-expansion + (chapter-start granularity). Expect a 1–2 page offset for the TOC + chapter itself once expanded. +- **Reflow overflow** — drop a `# Heading` near the end of a chapter + that doesn't have room. The `keepWithNext` rule should push it to + the next page. +- **Imported chapter from someone else** — import an existing NIP-23 + article, then click **Publish chapter** — should confirm before + clobbering and mint your own d-tag. +- **Token resolution** — put `{page-number} of {page-count}` in a + master text layer; verify the rendered numbers update as you add / + remove pages. + +--- + +## 16. Where to find things in the codebase + +Single file: `src/index.html`. + +- Pagination engine: `function paginateChapter` +- Reader Mode: `function openBookReader` +- PDF export: `async function exportBookToPdf` +- Resolver: `async function resolveBookSidecars` +- Disk cache: `BOOK_SIDECAR_CACHE_KEY` +- Manifest payload: `function bookManifestPayload` +- Token substitution: `function substituteBookTokens` +- TOC builder: `function _computeBookTocEntries` +- Index builder: `function _computeBookIndexEntries` +- Cross-refs: `function _computeBookSlugToPageMap` +- Footnotes: `function _processBookFootnotes` + +Spec: `docs/BOOK_DESIGNER_FEATURE.md`. diff --git a/src/help/align-distribute.md b/src/help/align-distribute.md new file mode 100644 index 0000000..89a1efc --- /dev/null +++ b/src/help/align-distribute.md @@ -0,0 +1,60 @@ +# Align & Distribute + +Lines up multiple selected layers along a shared axis; spaces them +evenly. Lives in the right-side **Align** panel, available whenever +≥2 layers are selected. + +## Align ops + +Six alignment ops, organised in two rows. Each acts on every layer +in the current selection: + +### Horizontal align (uses each layer's left / center / right edge) + +- **Align left** — every layer's left edge sets to the leftmost + selected layer's left. +- **Align horizontal center** — every layer's horizontal center + sets to the midpoint of the selection's combined bounding box. +- **Align right** — every layer's right edge sets to the + rightmost selected layer's right. + +### Vertical align (uses each layer's top / middle / bottom edge) + +- **Align top** — every layer's top sets to the topmost selected + layer's top. +- **Align vertical middle** — every layer's vertical middle sets + to the midpoint of the selection's combined bounding box. +- **Align bottom** — every layer's bottom sets to the bottommost + selected layer's bottom. + +## Distribute ops + +Spread three or more selected layers so their spacing is even. +The outermost two layers stay put; intermediate layers redistribute. + +- **Distribute horizontal centers** — equal horizontal-center + spacing. +- **Distribute vertical centers** — equal vertical-center spacing. +- **Distribute horizontal gap** — equal *gap* between layer + bounding boxes horizontally (different from center-spacing when + layers have different widths). +- **Distribute vertical gap** — equal vertical gap. + +## Behaviour notes + +- A locked layer in the selection is **ignored** by both align and + distribute — it stays put while everything else rearranges + around it. +- The selection's combined bounding box is the reference for the + "center" ops. If you want to align to the canvas centre, select + every layer + the canvas margin first (or use a background-spanning + color layer). + +## Tips + +- For evenly-spaced rows of cards / chapters / icons: layout the + first and last positions, then **Distribute horizontal gap** with + the row of layers selected — the middles snap into place. +- Combine with **Free Transform** to scale + align in two steps: + free-transform-scale to size everything to a common dimension, + then align. diff --git a/src/help/book.md b/src/help/book.md new file mode 100644 index 0000000..ae69f21 --- /dev/null +++ b/src/help/book.md @@ -0,0 +1,111 @@ +# Book + +Multi-page reflowable book with cover, designed pages, reflow +chapters, masters, PDF export, and full-screen Reader Mode. + +> Hands-on walkthrough: see [`BOOK_DESIGNER_TUTORIAL.md`](https://github.com/PR0M3TH3AN/Artstr-Studio/blob/main/docs/BOOK_DESIGNER_TUTORIAL.md). +> Spec: see [`BOOK_DESIGNER_FEATURE.md`](https://github.com/PR0M3TH3AN/Artstr-Studio/blob/main/docs/BOOK_DESIGNER_FEATURE.md). + +## Starting + +When you enter Book mode with no pages, four starter cards appear +(US Trade / A5 / A4 / Square). Clicking one seeds a default master +with a text-frame, a starter chapter, and a Title page. + +A 5th card appears when you have a slide deck loaded — **Convert +deck to book** turns each slide into a designed page (letterbox-fit +into the trim). + +## The two main views + +- **Pages overview** — every manifest entry as a tile (designed + pages + reflow chapters). Drag to reorder. Hover for per-tile + actions (proof PDF, duplicate, delete, master picker). +- **Cover** — wraparound spread editor. Front / spine / back panels + selected via the layer-target dropdown. + +Plus two **per-entry** views: + +- **Designed page editor** — same flat-canvas tools as Custom Art, + scoped to one `book-page:` target. Master layers render + underneath at 55% opacity. +- **Chapter editor** — markdown-on-left, paginated preview on right. + CodeMirror with markdown syntax + smart Enter for list + continuation. + +## Masters + +Reusable underlays. A master with a **text-frame** layer becomes a +valid reflow target — the chapter's prose flows into that frame. +Header / footer text on the master can use **tokens** that resolve +per page: + +- `{page-number}`, `{page-count}`, `{physical-page-count}` +- `{book-title}`, `{book-author}` +- `{chapter-title}`, `{chapter-number}` + +## Markdown features in chapters + +- GFM headings / lists / blockquotes / code / tables / task lists. +- Nested lists (up to depth 6). +- `[^N]` footnote refs + `[^N]: definition` for footnote bodies + (renders as Unicode superscript + chapter-end Footnotes section). +- `^[term]` invisible index markers. +- `[link text](#chapter-3)` cross-references — auto-appended with + ` (p. N)` at render time. +- Magic tokens on their own line: + - `{toc}` — auto-generated table of contents with page numbers. + - `{index}` — alphabetical index of every `^[term]` marker. + +## Publishing + +Sidecar-architecture: each surface publishes as its own Nostr event. + +| Surface | Event | d-tag | +|---------|-------|-------| +| Designed page | kind-30078 `casewrap-page` (role: 'interior') | `book-page:` | +| Master | kind-30078 `casewrap-page` (role: 'master') | `book-master:` | +| Cover | kind-30078 `casewrap-page` (role: 'cover-spread') | `book-cover:` | +| Chapter (public) | kind-30023 NIP-23 article | `chapter:` | +| Chapter (private) | kind-30078 encrypted envelope | `book-chapter:` | +| Manifest | kind-30078 `casewrap-book` | `book:` | + +The manifest references its sidecars via `{kind, pubkey, dTag}` +coords + emits matching `link` tags so external clients can +resolve them. + +Publish each surface first (via the per-editor Publish buttons), +then the manifest (top-bar Publish). + +## Private path + +Toggle the sidebar **🔒 Private** chip. Every subsequent publish +(manifest + sidecars) goes through the encrypted envelope path +(AES-256-GCM, key wrapped to your own pubkey via NIP-44 / NIP-04). +Re-opens decrypt automatically. + +## Reader Mode + embed + +- **▶ Read** opens a full-screen page-flip viewer with TOC sidebar, + keyboard nav, click-to-flip, and prefetch. +- The published manifest's `naddr` works in the embed iframe via + `?book=` (cut-down Reader Mode without TOC). + +## PDF export + +The **Export PDF…** modal exposes: + +- Page range (All / from–to). +- Include cover spread. +- Include bleed margin. +- Crop marks (L-marks at trim corners). +- Single vs split output (interior + cover separate). +- Filename pattern. + +Reflowed prose using Times / Helvetica / Courier-family fonts emits +as real PDF text (vector, selectable, searchable). Other fonts fall +back to vector raster + warning. + +**📄 Proof PDF** in any editor toolbar exports just that single +page / chapter — useful for a printer paper-and-colour proof +before committing to the full book. diff --git a/src/help/cover.md b/src/help/cover.md new file mode 100644 index 0000000..d9b8157 --- /dev/null +++ b/src/help/cover.md @@ -0,0 +1,49 @@ +# Case cover wrap + +A printable wraparound for DVD, Blu-ray, softcover, or any +back + spine + front case format. + +## Geometry + +- **Trim** — outer cut line. +- **Bleed** — extends past the trim on each outer edge so the + printer's cut tolerance doesn't reveal white. +- **Spine** — middle strip whose width depends on the case style + (DVD = 14 mm, Blu-ray = 11 mm, etc). +- **Safe area** — interior margin you should keep text + key art + inside. + +Pick a preset in the sidebar (DVD / Blu-ray / U-Card / Custom) or +enter your own panel width + trim height + spine + bleed under the +**Custom** preset. + +## Artwork modes + +- **Combined wraparound** — one canvas spanning the full + back+spine+front. Best for continuous artwork. +- **Slots** — separate back / spine / front layers. Best for posters + + spine text + back-cover layout. + +## Slots panel + +When you're in slot mode, each panel exposes: + +- A drop-zone for an image or a published cover design from Nostr. +- Fit (`cover` / `contain` / `fill` / `none`), zoom, position offsets, + rotation. +- Show / hide for testing different layouts. + +## Spine text + +The **Spine label** input adds a vertical text caption along the +spine. Family + weight + colour controls in the right options pane. + +## Common gotchas + +- The **show guides** checkbox toggles trim + bleed lines on the + canvas; **show crops** adds the cut marks. Both stay off in the + exported file unless you also enable the matching **print** options. +- Switching presets resets panel widths to that preset's defaults — + if you've customised, save first. +- Publishing posts a `casewrap-cover` event with the same flat + payload anyone can fork. diff --git a/src/help/customart.md b/src/help/customart.md new file mode 100644 index 0000000..9dd306c --- /dev/null +++ b/src/help/customart.md @@ -0,0 +1,57 @@ +# Custom art + +A freeform flat canvas you can size to anything from a Twitter banner +(1500×500) to a poster. The most general-purpose Artstr surface — if +your design doesn't fit one of the other modes, start here. + +## Sizing the canvas + +In the sidebar: + +- **Preset** — common social / print sizes. +- **W × H (px)** — manual override. +- **Background** — solid colour or `#ffffff` for transparent-on-export. + +The canvas is rendered at 96 DPI; the PDF/PNG export upscales to your +chosen DPI on the way out. + +## Layer types + +Custom Art accepts every layer kind in the app: + +- **Text** — full HTML rich text with bold / italic / underline / + strikethrough / link, inline-marks-aware. +- **Image** — drag-drop or pick; fit (cover / contain / fill / none), + rotate, opacity, optional clip. +- **Shape** — rectangle / ellipse / polygon / star + Pen-tool path + (full SVG-path support with anchors + handles). +- **Color block** — solid fill. +- **QR code** — input a string, choose ECC + style. +- **Pixel art** — sub-grid pixel art layer, edited via the Pixel + editor. +- **Linked design** — embeds another author's published Artstr design + as a live-resolving layer (Linked Designs feature). Right-click the + layer to expand into the linked event's full layer list and edit + pieces inline. + +## Tools + +The vertical tool palette has: +- **Select (V)** — move / scale / rotate whole layers. +- **Direct Selection (A)** — edit anchors + handles on a path. +- **Pen (P)** — Illustrator-parity pen tool (auto-add/delete anchors, + spacebar reposition, alt-drag asymmetric handles, Cmd+J join, + Cmd+Alt+J average). +- **Eyedropper (I)** — uses `window.EyeDropper` to sample any colour + on screen. +- **Pathfinder** — Unite / Subtract / Intersect / Exclude on selected + vector shapes. +- **Align / Distribute** — left/center/right + top/middle/bottom + + even-distribute. +- **Free Transform** — handles around the selection bbox for scale + + rotate. + +## Publish + +Posts a `casewrap-customart` event. Public, private (encrypted), or +premium (zap-gated) — selected in the publish-confirm modal. diff --git a/src/help/deck.md b/src/help/deck.md new file mode 100644 index 0000000..e83940c --- /dev/null +++ b/src/help/deck.md @@ -0,0 +1,64 @@ +# Slide deck + +An ordered list of slides published as a single event. Every slide's +full layer data is embedded inline — Presenter Mode + previews never +depend on additional relay fetches. + +## Sorter view + +The deck-sorter grid shows every slide as a thumbnail: + +- **+ Add slide** — append a blank slide. +- **Import PowerPoint (.pptx)** — imports `.pptx` files via a + vendored parser. Shapes, text runs, images, charts, tables, theme + fills, and slide notes all convert. +- **→ Convert to book** — turn the deck into a Book Designer book + (each slide → one designed page, letterbox-fit into the trim). +- Click a thumbnail to edit that slide; the canvas opens with the + slide's layers as canvas layers. +- Drag thumbnails to reorder. Right-click for duplicate / delete. + +## Theme + +Deck-wide theme overrides under **Deck theme** in the sidebar: + +- **Font family** — applied to every slide's text layers (unless + the slide explicitly opts out via "Ignore deck theme"). +- **Background colour** — applied to every slide's canvas + background. Same opt-out applies. + +A slide can pin its own font + background by toggling **Ignore deck +theme** in the slide-editor sidebar. + +## Presenter Mode + +Click **Present** to launch a full-screen reader: + +- Arrow keys flip slides. +- **S** toggles speaker-notes overlay. +- **F** toggles fullscreen. +- **Esc** exits. +- Footer counter shows `N / total`. + +## Embed + +A published deck's `naddr` works in the embed iframe: + +```html + +``` + +The embed renders a paginated player with prev/next + a slide +counter, plus fullscreen + reduced-motion handling. + +## Publish + +Posts a `casewrap-deck` event with every slide inlined. Forks land +with the full deck intact; remixers can swap slides + republish. + +## Tips + +- For just one slide, the [Slide](#/help/slide) mode is lighter — + standalone slides import into decks as forkable units later. +- The deck's `deck.theme` overrides apply at render time only — a + slide's own `fontFamily` / `background` stay in the event payload. diff --git a/src/help/designer.md b/src/help/designer.md new file mode 100644 index 0000000..f8e8805 --- /dev/null +++ b/src/help/designer.md @@ -0,0 +1,43 @@ +# Designer tab + +The **Designer** tab opens a dedicated single-disc canvas — one +13.5 cm circular disc face, optimised for designing the kind of +reusable disc artwork you'd drop into multiple disc-label sheets. + +## When to use the Designer tab + +- You want one disc artwork to publish + reuse across many disc-label + sheets (Top / Bottom of a single sheet, or shared across multiple + releases). +- The geometry is a single circle, not a sheet. Bleed + safe-area + rings render dedicated to the circular trim instead of the + rectangular cover/jewel layout. + +## Compared to Template > Disc-label sheet + +- **Template / Disc** = a sheet with *two* disc slots side-by-side, + printed on full-page label stock. +- **Designer** = one disc design, publishable as `disc-design`. Import + it later into a sheet via the Disc-label slot picker. + +## Layers + +Layers target `designer-disc`. The single disc surface scales them in +its native pixel space (the same `discTemplate.discD` constant used +by sheet rendering), so a disc design opened standalone vs dropped +into a sheet slot renders identically. + +## Publish + +- Title + Category in **Project Metadata** are required. +- The publish event type is `casewrap-disc-design`. Other authors can + fork it or drop it into their own disc-label sheets via the sheet's + slot picker → *Browse community*. + +## Tips + +- The **Spine label** input in the sidebar applies to Disc-label + sheets, not Designer mode — leave it alone here. +- Use **Print / Save as PDF** to get a circular vector cut sheet for + test prints. Most consumer printers won't print full bleed on a + disc; check your printer's safe area before mass printing. diff --git a/src/help/disc.md b/src/help/disc.md new file mode 100644 index 0000000..83537d5 --- /dev/null +++ b/src/help/disc.md @@ -0,0 +1,43 @@ +# Disc-label sheet + +Two-up disc-label sheet, sized for full-page label stock with two disc +circles arranged vertically (Disc 1 / Disc 2). + +## Geometry + +- **Sheet** — full-page (Letter / A4) with two disc cut-outs. +- **Disc diameter** — standard CD/DVD ~118 mm with a centre hole. +- **Safe area** — interior ring that printers and label stock + tolerances tend to respect. + +## Slots: Disc 1 + Disc 2 + +Each slot accepts: + +- A local image (drop or pick). +- A previously-published **disc design** from Nostr (the standalone + `disc-design` from the Designer tab). Click **Browse community →** + inside the slot. +- Disc-level meta: title, IMDb id, language. Used for both publish + tagging and the layout's text decoration. + +You can also leave a slot empty — only the populated disc(s) emit +output. + +## Spine labels + +The **Spine labels** inputs apply to a CD/DVD spine running between +the two discs (some label stocks include a spine strip). Top label ++ bottom label render in the small strip between the two discs. + +## Publishing + +Posts a `casewrap-disc` event with both disc slot images + +spine-label text. Forks land with the full sheet preserved. + +## Tips + +- For ONE disc only, use the Designer tab instead — that's the + standalone-publishable single-disc surface. +- The Crop marks toggle in the Export fieldset adds printer cut + marks at the trim corners of each disc. diff --git a/src/help/free-transform.md b/src/help/free-transform.md new file mode 100644 index 0000000..f811a76 --- /dev/null +++ b/src/help/free-transform.md @@ -0,0 +1,47 @@ +# Free Transform + +Rotation + scale handles wrapped around the combined bounding box of +the current selection. Lets you transform multiple layers as a group +without flipping tools. + +## When the handles appear + +- Select any layer with the [Select tool](#/help/tool-select) — the + eight bounding-box handles + the small rotation handle appear + automatically. +- Multi-select two or more layers — the handles wrap the combined + bounding box. +- Press the **Free Transform** button (right-side toolbox) explicitly + to refresh the handles after a manual property edit. + +## Handles + +- **Eight bbox handles** — corners scale on both axes; sides scale + on one axis. Hold **Shift** to keep the aspect ratio on a corner + drag. +- **Rotation handle** — small dot above the top edge. Drag to + rotate around the bbox centre. Hold **Shift** to snap to 15° + increments. + +## Multi-layer behaviour + +When two or more layers are selected: + +- Scaling proportionally rescales each layer's `x`, `y`, `w`, `h` + relative to the combined bounding box. Layers far from the + pivoted handle move and resize more than layers near it (the + intuitive result). +- Rotating rotates each layer around the **group** centre. Each + layer's individual `rotate` value increments by the rotation + amount, and its `x/y` recompute to walk the orbit. + +## Tips + +- Free Transform composes well with [Align & Distribute](#/help/align-distribute) — + resize a row of layers to a common size with the scale handles + first, then align them. +- For a single layer, the same handles + behaviour are available + via plain Select. Free Transform's value is the multi-layer + group transform. +- Locked layers in the selection are ignored — they stay put while + the rest of the group transforms around them. diff --git a/src/help/index.md b/src/help/index.md new file mode 100644 index 0000000..fd71325 --- /dev/null +++ b/src/help/index.md @@ -0,0 +1,49 @@ +# Artstr Studio docs + +Welcome. Artstr Studio is a single-page design tool that publishes +everything to Nostr instead of a private server. Pick a topic below. + +## The two app modes + +- **[Template](#/help/template)** — print + screen artwork (cover wraps, disc + labels, jewel inserts, custom-art canvases, slides, decks, pixel art, + books). Every artboard publishes as one or more Nostr events that anyone + can fork or remix. +- **[Designer](#/help/designer)** — a reusable single-disc design surface. + Disc designs publish independently, then drop into any disc slot of a + disc-label sheet. + +## Template-mode layouts + +- **[Case cover wrap](#/help/cover)** — front + spine + back wraparound + for DVD / Blu-ray / softcover printable cases. +- **[Disc-label sheet](#/help/disc)** — two-up CD/DVD label sheet. +- **[Jewel-case insert](#/help/jewel)** — front + tray jewel inserts. +- **[Custom art](#/help/customart)** — any-aspect flat canvas. +- **[Slide](#/help/slide)** — 16:9 standalone slide with speaker notes. +- **[Slide deck](#/help/deck)** — ordered collection of slides. +- **[Pixel art](#/help/pixelart)** — palette-indexed grid editor with + multi-frame animation. +- **[Book](#/help/book)** — multi-page reflowable book with cover, + chapters, masters, PDF export, and Reader Mode. + +## Working in Nostr + +- Sign in with a browser extension (Alby, nos2x, etc) using the top- + right widget. Without one you can still author and export locally; + publishing + browsing the community requires a key. +- Every Artstr event is replaceable (NIP-33). Re-publishing under the + same d-tag updates the design in place. +- Private publishing uses NIP-44 / NIP-04 to encrypt the payload to + your own key — only you can decrypt on a fresh device. + +## Tips + +- Most modes autosave to localStorage as you edit, so you can close + the tab and come back later. Publish to Nostr to share or to back + up across devices. +- Linked Designs let you reference another author's published design + as a layer in yours. Their event is fetched and rendered live; the + manifest p-tags the contributors for attribution. +- The community browser (top-right Nostr widget) shows feeds you can + filter and import from. diff --git a/src/help/jewel.md b/src/help/jewel.md new file mode 100644 index 0000000..cadb2f6 --- /dev/null +++ b/src/help/jewel.md @@ -0,0 +1,39 @@ +# Jewel-case insert + +Front-cover + tray inserts for a standard CD jewel case. + +## Geometry + +- **Jewel front** — 4.72 × 4.72 in trim, sits behind the clear plastic + front cover. +- **Jewel tray** — 5.91 × 4.72 in trim, sits under the disc tray (with + vertical spine edges that wrap into the case sides). +- **Bleed** + **safe area** mirror the standard CD/DVD design. + +## Slots + +Both inserts pick up artwork the same way: + +- Drop in a local image, or +- **Browse community →** to pick a published design from Nostr. + +Per slot you can set fit / zoom / x / y / rotate independently. + +## Spine labels + +Left + right spine inputs feed the strips that wrap around the +tray-card sides — typically your album / movie title text. + +## Publishing + +Posts a `casewrap-jewel` event. Both inserts' images + the spine +labels live in the same event so a forker gets the complete jewel +package. + +## Tips + +- Test print at 100% on plain paper first; trim by hand and check the + fit before printing on label stock. +- For double-sided printing, the tray usually needs a flipped layout + on the back — print the same event twice with the front oriented + correctly and you'll match most consumer printers. diff --git a/src/help/nwc.md b/src/help/nwc.md new file mode 100644 index 0000000..990e5d7 --- /dev/null +++ b/src/help/nwc.md @@ -0,0 +1,98 @@ +# NWC — Nostr Wallet Connect setup + +NWC (NIP-47) lets Artstr send Lightning payments through your +wallet without leaving the app. Required for one-click zapping + +unlocking premium designs. + +## Quick start (CoinOS, recommended) + +CoinOS is a free web-based wallet that supports NWC. Easiest path +if you don't already have a Lightning wallet: + +1. Sign up at . +2. Open **Settings → Connect** in the CoinOS app. +3. Create a new Nostr Wallet Connect link. Set a **budget** that + comfortably covers the zaps + unlocks you expect (a few + thousand sats is plenty to start). +4. Copy the connection string (starts with `nostr+walletconnect://`). +5. In Artstr, open **Settings → Lightning wallet**. +6. Paste the connection string + click **Connect**. +7. The status line confirms the connection + lists the wallet's + advertised capabilities (`pay_invoice` is the required one). + +## Other supported wallets + +Any NIP-47 wallet works: + +- **Alby Hub** — self-hosted; nostr-native; great for power users. +- **Mutiny** — browser-based, open-source. +- **Phoenix** — mobile-first, advertises NWC. +- **Cashu.me** — ecash custodial. +- **LNbits** — self-hosted; the NWC extension exposes a + connection string per wallet. + +In every case the pattern is the same: the wallet generates a +`nostr+walletconnect://...` URI that you paste into Artstr. + +## Multi-device sync + +The **Sync this wallet to my Nostr account** checkbox (appears +once connected) publishes your NWC URI as a kind-30078 event, +encrypted to your own pubkey via NIP-44. On any other device where +you sign in with the same Nostr key, Artstr finds the synced event, +decrypts it, and auto-reconnects without you pasting the URI +again. + +Privacy: the synced event is NIP-44-encrypted to **only your own +pubkey**. Relays + other clients can't read the URI — only an +extension holding your nsec can decrypt it. + +## Sending a zap with NWC connected + +1. Click the **⚡ Zap** button on any design / page / chapter (or + the **Unlock with ⚡ zap** on a premium row). +2. Amount picker opens with presets. +3. Artstr builds a zap request, gets the BOLT11 invoice from the + recipient's LSP, and sends `pay_invoice` through your NWC + connection. +4. The wallet pays; the LSP issues the zap receipt; the UI + refreshes. + +End-to-end usually completes in under 3 seconds. + +## Test connection + +The **Test connection** button (next to Disconnect) runs a +zero-amount pay_invoice round-trip to verify the wallet is +reachable + capable. Useful before assuming the integration is +broken. + +## Capability warnings + +If the wallet doesn't advertise `pay_invoice`, the UI shows an +⚠ banner — unlocks will fail. Reconnect via a different wallet's +URI or enable `pay_invoice` in the source wallet (most wallets +ship it on; some self-hosted setups disable it by default). + +## Security model + +- The connection string contains the wallet's relay URL + a + client-side secret key Artstr uses to sign NIP-04 messages to + the wallet. **Treat it like a password** — anyone with the URI + can send `pay_invoice` up to the configured budget. +- Set a **budget** on the wallet side that caps total spending. + Most NWC wallets let you renew the budget periodically. +- Setting a low per-payment maximum (e.g., 1 000 sats) is a good + default for unlocking designs. +- Revoking NWC is a one-click action on the wallet side — the URI + becomes inert immediately. + +## Caveats + +- NWC over relays adds latency (signed event → relay → wallet → + signed receipt → relay → client). Expect 1-3 seconds per zap on + a healthy connection. +- If the wallet's relay is down, zaps queue but won't complete. + The wallet usually has multiple relays as fallbacks. +- Mobile browsers sometimes throttle background WebSockets; if + zaps time out, foreground the Artstr tab + retry. diff --git a/src/help/panel-credits.md b/src/help/panel-credits.md new file mode 100644 index 0000000..0714810 --- /dev/null +++ b/src/help/panel-credits.md @@ -0,0 +1,46 @@ +# Credits panel + +Surfaces every contributor whose published design you've imported +into the current project — typically via a **Linked Designs** layer. + +## When the panel appears + +- Hidden by default. +- Appears under Project Metadata once you add a layer whose source + is another author's Nostr event. +- Hides again if you remove every linked layer. + +## What it shows + +For each contributor: + +- **Avatar + display name** — pulled from their kind-0 profile. +- **One-line link count** — how many distinct linked designs of + theirs you've used. +- **Tip button** — opens a lightning-tip flow if the author has + a `lud16` on their profile. + +## How it feeds publish + +At publish time the resolver walks the linked tree once and adds +two tag families to your event: + +- `['p', , , 'design-contributor']` +- `['link', '::', ]` + +External clients reading your event can resolve those links and +credit the right authors. + +## Private designs + +Private publishes intentionally **don't** emit the link / p tags +(it would leak the link graph through unencrypted tags). The +panel still shows you the credits in-app for awareness. + +## Tips + +- If a contributor's avatar shows as a placeholder, their profile + hasn't been cached yet — wait a few seconds for the profile + request to resolve. +- Premium-locked linked sources are credited the same way once + you unlock them. diff --git a/src/help/panel-export.md b/src/help/panel-export.md new file mode 100644 index 0000000..34900f3 --- /dev/null +++ b/src/help/panel-export.md @@ -0,0 +1,47 @@ +# Export options panel + +Lives below the metadata + above the print/save panel. Controls +what print marks render on the canvas + what gets included in the +exported file. + +## Show / hide toggles + +These affect the **on-screen** display only — they're separate +from the `print*` toggles below. + +- **Show guides** — trim lines around the artwork. +- **Show bleed** — outer bleed boundary line. +- **Show safe area** — interior safe zone. +- **Show crops** — corner cut marks outside the trim. +- **Show fold** — for cover wrap, the back↔spine + spine↔front + fold lines. + +## Print toggles + +These affect what gets included in the **exported** file +(JPEG / PNG / PDF / print-dialog output): + +- **Print guides** — embed the trim guides in the output. +- **Print crops** — embed corner cut marks in the output. +- **Print bleed** — extend the output to include the bleed area. + +Printers usually want crop marks ON + bleed ON; production +PDF + JPEG usually wants both OFF. + +## Mode interaction + +- The toggles are global per project; they apply to whichever mode + is active. +- Pixel-art mode hides the export options entirely (pixel-art has + its own PNG / sprite-sheet / GIF exporters in the pixel toolbox). +- Book mode hides them too while editing chapter markdown (chapter + pagination has its own controls); they reappear in the per-page + canvas editor + cover editor. + +## Tips + +- Toggle "Show crops" ON while designing to leave room around the + trim edges; toggle "Print crops" ON only when exporting for a + printer. +- For social-media exports, leave every print* off — the output is + pixel-perfect to the trim. diff --git a/src/help/panel-layers.md b/src/help/panel-layers.md new file mode 100644 index 0000000..667270f --- /dev/null +++ b/src/help/panel-layers.md @@ -0,0 +1,83 @@ +# Layers panel + +The persistent layer list on the left sidebar. Lists every layer +on the **current surface** — switches automatically based on the +active mode + which sub-surface you're editing. + +## How the layer list scopes per mode + +| Mode | Surfaces | +|------|----------| +| **Cover** | Back / Spine / Front (or one combined wrap). | +| **Disc-label sheet** | Disc 1 / Disc 2. | +| **Jewel inserts** | Jewel front / Jewel tray. | +| **Custom Art / Slide** | One Canvas. | +| **Deck (editing a slide)** | The slide's canvas. | +| **Pixel art** | The pixel grid. | +| **Book — page editor** | The current book page (master layers shown underneath as a translucent underlay). | +| **Book — master editor** | The current master. | +| **Book — cover editor** | Cover front / Spine / Back, switchable via the dropdown at the top of the panel. | +| **Designer tab** | The single disc surface. | + +The **Target** dropdown at the top of the layer list picks which +sub-surface a *new layer* gets added to. Adding from the canvas +(drop image, draw shape, etc) automatically tags the new layer +with the active surface. + +## Per-row controls + +Each row in the layer list shows: + +- **Visibility (eye)** — hide / show without removing. +- **Lock** — refuse pointer input on the canvas. The row still + highlights but drag / select skip it. +- **Name** — auto-named on creation; double-click to rename. +- **Type chip** — small label showing image / text / shape / QR / + pixel-art / linked-design. + +## Reorder + group + +- **Drag** rows to reorder. Z-order on the canvas follows the + list order (top of the list = top of the stack). +- **Cmd/Ctrl + ]** / **Cmd/Ctrl + [** sends selected layers + forward / backward. Add **Shift** for bring-to-front / send-to- + back. +- **Shift+click** (or Cmd / Ctrl + click) to multi-select rows. +- **Group** — select multiple rows + click the Group button (or + press **Cmd/Ctrl + G**) to collapse them under a single + expandable row. Groups have their own visibility / lock toggles + that cascade. +- **Ungroup** — click an active group's Ungroup button. + +## Layer options (the per-layer right-side pane) + +Clicking a layer in the list (or selecting it on the canvas) +populates a contextual options block in the right-side floating +pane. Common controls: + +- **Position** — x / y / w / h in canvas units (inches for print + modes; pixels for Custom Art / Slide / Deck). The 🔒 button + toggles aspect-ratio lock so changing w updates h proportionally. +- **Rotation** — 0–360°. +- **Opacity** — 0–1. +- **Fit** — for image layers: cover / contain / fill / none. +- **Clip** — clip-to-shape pulls another layer's path as the clip + source. Useful for clipping an image to an arbitrary outline. +- **Color** — solid colour with the eyedropper inline. +- **Premium gating** — per-layer paywall hint (gating intent only + in v1; full encrypted-publish path lives elsewhere). + +The exact fields differ by layer type — text layers show font / +size / alignment, image layers show fit + src, shape layers show +fill / stroke / per-anchor counts, etc. + +## Tips + +- Locking layers is the easiest way to prevent accidental moves + while you adjust adjacent ones. +- The Layers panel hides in pixel-edit mode (the pixel editor has + its own frames + onion-skin controls) and in Book Pages overview + (no active surface). +- For complex multi-layer art, group early — moving a group of + five layers as one is much faster than clicking them one at a + time. diff --git a/src/help/panel-metadata.md b/src/help/panel-metadata.md new file mode 100644 index 0000000..ceb04d4 --- /dev/null +++ b/src/help/panel-metadata.md @@ -0,0 +1,49 @@ +# Project Metadata panel + +Lives near the top of the left sidebar. The fields here feed both +the local project file (when you Save as JSON) and the published +Nostr event's discovery tags. + +## Fields + +- **Title** *(required for publish)* — the human-readable name of + your design. Used as the event's `title` tag + as part of the + d-tag for replaceable events. For books, also drives + `{book-title}` token resolution. +- **Category** *(required for publish)* — discovery facet. The + picker offers common categories (movie / comic / book / album / + game / etc); custom values land as `category:custom`. +- **IMDb ID** — for movies. Emits an `i` tag with `imdb:` + (NIP-73). Lets you deduplicate against other community designs + for the same title. +- **UPC / TVDB ID / MusicBrainz Disc ID** — same idea for other + media types. Each emits an `i` tag with its scheme prefix. +- **Custom tag** — free-form discovery `t` tag. Lowercases + + hyphenates on emit. +- **Language** — affects feed filters in the community browser. + Defaults to `en`. + +## How the fields feed publish + +When you click **Publish to Nostr**, the publish-confirm modal +shows every metadata field with a side-by-side preview. Anything +empty stays out of the event's tags. The event's d-tag is built +from `:`; once published, re-publishing +under the same d-tag replaces the event in place (NIP-33). + +## Designer-mode equivalent + +The Designer tab has its own metadata fieldset +(`designerMetadataFieldset`) with the same fields but scoped to the +single-disc design event (`casewrap-disc-design`). Switching tabs +preserves both sets independently. + +## Tips + +- Setting an IMDb / UPC ID lets the community browser group your + design with other releases of the same title — useful for finding + forks + remixes. +- Leave Category set sensibly: the default feed filters use it. +- For private publishes, the metadata fields still emit but the + event's content is encrypted — tags stay public, only the design + payload is hidden. diff --git a/src/help/panel-print-save.md b/src/help/panel-print-save.md new file mode 100644 index 0000000..20ee43c --- /dev/null +++ b/src/help/panel-print-save.md @@ -0,0 +1,83 @@ +# Save / Print / Export panel + +The bottom panel in the left sidebar. Local-only output — +publishing to Nostr lives in the top-right Publish flow, not here. + +## Save / Open (project JSON) + +- **Save project** — downloads a `.json` file containing the + entire project state (every layer, every mode's data, the + metadata, the visibility setting). One file per active design. +- **Open project** — pick a saved JSON file. Loads it into the + current session, replacing the active project. Use case: round- + trip a design via email / a cloud-drive sync without going + through Nostr. + +## Print + +- **Print / Save as PDF** — opens the browser print dialog. Pick + **Save as PDF** in the destination dropdown to export to PDF. +- The print toggles in the Export options panel above control + what gets included (bleed, crop marks, guides). + +### Print-dialog tips + +- Set **scale to 100%** — anything else changes the trim + dimensions. +- Disable **headers and footers** — your browser may add page + numbers / URLs otherwise. +- For double-sided cover wraps, print the same event twice + with a blank back the second time if your printer doesn't + duplex the spine correctly. + +## Export + +- **Export JPEG** — direct rasterised export at the artboard's + natural pixel size, JPEG-compressed. The resolution dropdown + picks between **Low (96 DPI)**, **Standard (150 DPI)**, and + **Print (300 DPI)**. +- **Export PNG** — same but as PNG (lossless). Sometimes available + only for modes that support transparent backgrounds. +- Pixel-art mode has its own dedicated exports (PNG at multiple + scales, sprite sheet, animated GIF) in the pixel toolbox; the + panel here is hidden. +- Book mode has its own dedicated **Export PDF…** flow (page range, + bleed, crop marks, split output) from the Pages overview toolbar. + +### Why JPEG sometimes routes through PDF + +The direct-JPEG path renders every layer onto a hidden canvas, then +extracts the canvas as a JPEG. That fails in three cases: + +1. **CORS-blocked images** — if any image layer's source server + doesn't send `Access-Control-Allow-Origin: *`, the browser + refuses to embed the pixels in the canvas. The "Render canvas" + path can't bypass this; the print engine can. +2. **Tainted canvas** — the canvas accepted the image but flagged it + "tainted" once it tried to read pixels. Same root cause as #1 + but caught at a different stage. +3. **Unsupported modes** — pixel-art (uses its own dedicated + exporter) and standalone decks (which have a multi-slide + exporter elsewhere) don't go through this path. + +In any of those cases, the JPEG button automatically opens the +**Print → PDF** flow: the browser's print engine has a separate +image pipeline that handles cross-origin embeds the canvas can't +touch. You'll see a toast explaining the switch ("N image(s) +couldn't be embedded directly — switching to the PDF route"). Save +the resulting PDF, then re-export it as JPEG from a PDF viewer if +you specifically need a flat JPEG. + +## Designer tab + +The Designer tab has a separate `designerPrintSaveFieldset` with +the same buttons but scoped to the single-disc surface. + +## Tips + +- The JSON save is the fastest way to back up a draft you don't + want to publish yet — drop the file in a private cloud folder + + reopen anywhere. +- For mass production, use the JPEG export at 300 DPI by tweaking + the artboard's pixel dimensions to ` × 300` before + exporting. diff --git a/src/help/panel-tool-options.md b/src/help/panel-tool-options.md new file mode 100644 index 0000000..bb2749f --- /dev/null +++ b/src/help/panel-tool-options.md @@ -0,0 +1,59 @@ +# Tool Options pane (right side) + +The floating panel next to the canvas. Its contents switch based on +the active tool + the currently-selected layer. Empty until you +pick a tool or a layer. + +## Two modes of content + +The pane swaps between two kinds of content depending on what's +active: + +1. **Tool options** — when no layer is selected, shows the active + tool's drawing parameters (fill / stroke / size / kind etc). + These apply to the *next* layer you draw. +2. **Layer options** — when a layer IS selected, shows that + layer's properties (x / y / w / h / rotation / opacity / fit / + clip / fill / type-specific fields). Edits apply live. + +You can have both modes briefly at once if you draw a new layer +while a different layer is selected — the new layer's options +take over the moment it lands. + +## Per-tool contents + +The per-tool docs cover the specific controls each tool surfaces: + +- [Pen tool](#/help/tool-pen) — Fill toggle + colour, Stroke + colour + width, mid-draw Finish / Cancel, path actions block + (Pointed corner / Curved smooth / Delete anchor / Average / + Join endpoints / Close path / Reverse direction). +- [Pencil tool](#/help/tool-pencil) — Stroke colour + width. +- [Shape tool](#/help/tool-shape) — Kind picker (rect / rounded + rect / circle / ellipse / triangle / polygon / star / line), + Fill toggle + colour, Stroke colour + width. +- [Text tool](#/help/tool-text) — Font / Colour / Point size / + Alignment + Bold / Italic toggles. +- [Direct Selection](#/help/tool-direct-select) — Hint + the + Path Actions block when anchors are selected. +- [Eyedropper](#/help/tool-eyedropper) — no panel; uses the + active swatch. +- [Select](#/help/tool-select) / [Hand](#/help/tool-hand) — no + panel of their own; they show the *selected layer's* options. + +## Per-layer contents + +When a layer is selected, the pane shows that layer's edit +controls. See the [Layers panel](#/help/panel-layers) page for the +full layer-options field list — the floating pane is the live +edit surface for those fields. + +## Tips + +- The pane is intentionally floating + slim so it stays out of the + way on small viewports. On wider viewports it docks against the + right side of the canvas. +- If you can't find a field you expect to see, check: (a) is the + right tool active? (b) is a layer of the right type selected? +- Hiding the layers panel (sidebar collapse) does not hide the + Tool Options pane. diff --git a/src/help/panels.md b/src/help/panels.md new file mode 100644 index 0000000..2fee026 --- /dev/null +++ b/src/help/panels.md @@ -0,0 +1,29 @@ +# Sidebar panels — overview + +Artstr Studio has two stacks of panels around the canvas: + +- **Left sidebar** — persistent. Holds project metadata, the layout + picker, mode-specific settings, the layers list, and the export + + save/print buttons. +- **Right pane** (the floating **Tool Options**) — contextual. + Changes based on the active tool + the selected layer. + +## Left-sidebar groups + +| Panel | What it covers | +|-------|----------------| +| [Project Metadata](#/help/panel-metadata) | Title, category, IDs, language, custom tag. | +| Template type picker | Switches between Cover / Disc / Jewel / Custom Art / Slide / Deck / Pixel Art / Book — see the per-mode pages under *Template-mode layouts*. | +| Mode-specific fieldsets | Cover size + artwork mode, Custom Art dimensions, Deck theme + import, Book document settings, etc. (Each mode's page covers its panel.) | +| [Credits](#/help/panel-credits) | Linked-design contributors surfaced for attribution. | +| [Layers](#/help/panel-layers) | The per-mode layer list. Where most of your interaction lives. | +| [Export options](#/help/panel-export) | Show / hide / print guides + bleed + crops. | +| [Save / Print](#/help/panel-print-save) | Project JSON save/open + Print/Save as PDF + Export JPEG/PNG. | + +## Right pane + +- [Tool Options](#/help/panel-tool-options) — overview of the + contextual pane and how it switches with the active tool. + +The per-tool sections under *Vector tools* (Pen, Shape, Text, etc) +cover what each tool's contextual options do. diff --git a/src/help/pathfinder.md b/src/help/pathfinder.md new file mode 100644 index 0000000..6322be9 --- /dev/null +++ b/src/help/pathfinder.md @@ -0,0 +1,45 @@ +# Pathfinder + +Boolean combine operations for vector shapes. Lives in the right- +side **Pathfinder** panel, which becomes available when ≥2 vector +shapes are selected. + +## The four ops + +- **Unite** — merges every selected shape into one combined fill + (boolean OR). Outer outlines join; interior overlaps disappear. +- **Subtract** — removes every shape behind the topmost selected + shape from the topmost. Use this to "punch holes". +- **Intersect** — keeps only the area common to every selected + shape (boolean AND). Hairline overlaps result in a hairline + output. +- **Exclude** — XOR. Areas covered by an odd number of shapes + stay filled; even-coverage areas are removed. + +## How it works + +Powered by the vendored **polygon-clipping** library (~29 KB). +Each Bézier shape gets flattened into polygon segments at a +tolerance that keeps curves visually identical, the boolean op +runs in flat-polygon space, and the result re-encodes as a +single path layer. + +## Layer behaviour + +- The result is a new layer that **replaces** the selected shapes — + the originals are removed (use undo if you regret it). +- The new layer inherits the fill / stroke / opacity of the + **frontmost** selected shape (highest z). +- The new layer's position is the bounding box of all inputs, + so re-selection handles cover the combined region. + +## Tips + +- For complex multi-step ops (e.g., "subtract A from B then unite + with C"), do them one pair at a time — pathfinder operates on + the current selection. +- The result is still an editable path. [Direct Selection](#/help/tool-direct-select) + lets you tweak the resulting anchors after the boolean. +- Pathfinder ops on shapes with very dense curves (hundreds of + control points) can take a noticeable beat — large logos with + fine detail are the slow case. diff --git a/src/help/pixelart.md b/src/help/pixelart.md new file mode 100644 index 0000000..de79cf2 --- /dev/null +++ b/src/help/pixelart.md @@ -0,0 +1,57 @@ +# Pixel art + +Palette-indexed grid editor for sprite work + simple animation. + +## Canvas + +- Grid sizes from 8×8 up to 128×128. +- Palette-indexed (not RGB) — every pixel is an index into the + palette block, which means animations + colour-cycling are cheap. +- Pencil / eraser / fill / eyedropper / line / rect / ellipse tools. +- Mirror X / Y for symmetric sprites. +- Flip / rotate / resize transforms. + +## Animation + +- Multi-frame timeline at the bottom of the canvas. +- Per-frame duration (ms). +- Playback preview with onion-skin of the previous frame. +- Reorder frames by drag. + +## Palette + +- HSV picker inline. +- **Eyedropper (I)** uses `window.EyeDropper` to sample any colour + on screen. +- Up to 256 palette entries; unused entries don't cost anything in + the export. + +## Import + +- **Browse community** — pulls a published pixel-art design from + Nostr as a layer. +- **Import from file** — JSON design export. +- **From image…** — pick any image, then the live side-by-side + preview tunes target W/H, colour count, and fit mode (Cover / + Fit / Stretch) before applying. Uses median-cut quantization. + +## Export + +- **PNG** at 1× / 2× / 4× / 8× / 16× / 32× (transparent or solid bg, + optional grid lines). +- **Sprite sheet PNG** — every frame tiled in a Grid or Row layout. +- **Animated GIF** — inline GIF89a encoder, no vendored dependency + (palette-indexed input is what GIF expects natively). + +## Embed + +Published pixel-art animations play back live in the embed iframe. +Embed URL params: `?play=0` starts paused; `?fps=N` overrides +frame rate; reduced-motion users get static start unless `?play=1` +opts in. + +## Publish + +Posts a `casewrap-pixelart` event with the palette + per-frame +pixel data (run-length encoded). Forks land with the full +animation intact. diff --git a/src/help/publishing-premium.md b/src/help/publishing-premium.md new file mode 100644 index 0000000..6874d0c --- /dev/null +++ b/src/help/publishing-premium.md @@ -0,0 +1,101 @@ +# Premium publishing (zap-gated) + +Sell designs for sats. The published event ships a watermarked +preview anyone can browse + an encrypted payload only paying +viewers can decrypt. Discovery + browsing is public; the design +itself is gated. + +## How it works + +1. The full design payload is encrypted via the same AES-256-GCM + pipeline as a private publish, but with a special **softgate** + key derivation. +2. The key derives from public event material (event id, pubkey, + d-tag) plus an obfuscated per-epoch pepper baked into the + static Artstr client. +3. A watermarked JPEG preview (max 720 px long axis, ~50 KB + budget) ships in the unencrypted envelope so buyers can see + what they'd be unlocking. +4. The event publishes with: + - `['t', 'casewrap']`, `['t', 'casewrap-']` — discovery. + - `['title', …]`, `['category', …]` — public metadata. + - `['premium', 'true']`. + - `['zap-gate', 'true']`, `['zap-min', '']`, `['zap-split', 'required']`. + - `['zap', , , '']` — creator share. + - `['zap', , , '']` — platform fee. +5. When a buyer's zap receipt for this event lands on relays, the + client computes the soft-gate key + decrypts. + +## Soft-gate is "soft" + +Honest framing: this raises friction against drive-by scraping but +a determined reverse-engineer can recover the client pepper and +decrypt without paying. The mode is for low-trust commercial +distribution, not DRM-grade gating. Stronger DRM can swap in later +without changing the buyer side. + +## What you need before publishing premium + +- A **lightning address** (`lud16`) on your kind-0 Nostr profile. + Set this on any Nostr client that edits your profile metadata + (Damus, Amethyst, nos2x, etc). Without it the platform can't + route the buyer's payment to you. +- A non-empty title + category (same as any publish). +- A working artboard preview — the modal renders the watermarked + JPEG live + refuses to publish if rendering fails (CORS images + etc). + +## Publishing flow + +1. Set Title + Category. +2. Click **Publish to Nostr**. +3. In the confirm modal: + - Keep visibility on **Public**. + - Toggle **Zap-gated (premium)** on. + - Set the **Minimum zap** in sats (default 100). + - Read the split note (creator: 70 %, platform: 30 % by default; + see the per-event `zap` tags for the exact split that ships). +4. Click **Publish**. + +## How buyers unlock + +In the community browser's **Premium** tab, a locked design shows +the watermarked preview + an **Unlock with ⚡ zap** button. Clicking +it: + +1. Opens the buyer's connected NWC wallet (see + [NWC setup](#/help/nwc)). +2. Builds a NIP-57 zap request for the minimum amount. +3. Sends `pay_invoice` through the NWC connection. +4. On a successful zap receipt, derives the soft-gate key + decrypts + the design in-place. +5. Caches the decrypted payload in the **My Purchases** vault so + later re-visits skip the re-zap. + +## My Purchases vault + +Your unlocks live in a **vault** encrypted to your own pubkey (so +they sync across devices the same way private publishes do). +Capped at a fixed size; if the vault overflows, the oldest items +get evicted with a visible failure rather than silently lost. + +## Editing a premium event + +The same NIP-33 replaceability applies — re-publishing updates the +event in place. Existing buyers' vault entries continue working +against the new event since the soft-gate key derives from the +d-tag, which stays stable. + +## Caveats + +- Decks ship a per-slide watermarked preview (up to 6 slides) so + buyers can flip through what they'd unlock; non-deck modes get a + single preview JPEG. +- The platform fee is currently a fixed 30/70 split on the event's + zap tags. Future updates may make this configurable. +- The buyer's NWC wallet must support `pay_invoice` (almost all + do). + +## Deeper reference + +- Spec: `docs/PREMIUM_DESIGNS_FEATURE.md` in the GitHub repo. diff --git a/src/help/publishing-private.md b/src/help/publishing-private.md new file mode 100644 index 0000000..4d7012e --- /dev/null +++ b/src/help/publishing-private.md @@ -0,0 +1,71 @@ +# Private publishing + +Encrypted-to-self designs. Same Nostr event format as a public +design, but `content` is an encrypted envelope and the metadata +tags are stripped down so the design doesn't appear in any +community feed. + +## How the encryption works + +1. The full design payload is JSON-serialised. +2. Gzipped (when the browser supports `CompressionStream`). +3. Encrypted with a fresh AES-256-GCM key. +4. The AES key is wrapped to **your own Nostr pubkey** via either + NIP-44 (preferred) or NIP-04 (fallback). +5. The wrapped key + iv + ciphertext go into an `envelope` JSON + object that becomes the event's `content` field. + +Only someone signing with your Nostr key can derive the AES key +and decrypt the payload. The event is technically broadcast to all +relays, but it's mathematically opaque to everyone else. + +## What's still visible + +- Your **pubkey** (every Nostr event is signed and the pubkey is + part of the signature). +- The event's `kind`, `created_at`, `d` tag, and `encrypted` tag. +- The fact that you published *something* at this time. + +What's **not** visible: title, category, IDs, layers, dimensions, +images, or any other payload content. + +## When to use it + +- Working drafts you want backed up across devices but not shared. +- Personal projects you don't want indexed. +- Sensitive content where even the title shouldn't leak. + +## Editing across devices + +Open Artstr on another device, sign in with the same Nostr +extension, and click **Mine** in the community browser. Your +private events appear (decrypted lazily as you click them). The +**Private** tab specifically filters to your encrypted events. + +## Caveats + +- Requires a Nostr extension that supports NIP-44 or NIP-04 + encryption. Most do; alby and nos2x both work. +- The d-tag for a private design is a random opaque string + (`artstr-private-`) so it can't be guessed. Don't lose + the d-tag if you intentionally want to overwrite the event + later — it's stored in your project's autosave under + `state.privateDTag`. +- **Sharing privately with one specific other person** is *not* + supported in v1. The current encryption only wraps the AES key + to your own pubkey. Multi-recipient envelopes are a future arc. + +## Books + +The book sidebar has its own **🔒 Private** chip. Flipping it +encrypts the manifest **and** every sidecar event (pages, +masters, cover, chapters) on subsequent publishes. See +[Book Designer](#/help/book) for the full sidecar flow. + +A private book's chapter events are kind-30078 (encrypted), not +kind-30023 NIP-23 articles, so external NIP-23 readers won't see +them — which is the desired behaviour for a private book. + +## Deeper reference + +- Spec: `docs/PRIVATE_PUBLISHING_FEATURE.md` in the GitHub repo. diff --git a/src/help/publishing-public.md b/src/help/publishing-public.md new file mode 100644 index 0000000..368e04d --- /dev/null +++ b/src/help/publishing-public.md @@ -0,0 +1,79 @@ +# Public publishing + +The default tier. Plain-text event the relays distribute to anyone +who asks for it. Discoverable in the community browser, forkable, +embeddable. + +## What the event looks like + +- `kind: 30078` (or `30023` for book chapters). +- `tags`: + - `['d', ':']` — the addressable identifier. + Replaceable per NIP-33: re-publishing under the same d-tag + replaces the event in place. + - `['t', 'casewrap']` + `['t', '']` (e.g. + `casewrap-cover`, `casewrap-deck`, `casewrap-book`). + - `['title', '']`, `['category', '<…>']`, + `['language', '<…>']`. + - External ID tags when you've filled them in: `['i', 'imdb:…']`, + `['i', 'upc:…']`, `['i', 'tvdb:…']`, etc. + - `['client', 'Artstr Studio']`. + - For books / linked designs: `['p', '', …, + 'design-contributor']` + `['link', '', …]`. +- `content`: JSON-serialised design payload. Every layer, every + mode-specific block, every reference. + +## Discoverability + +- Listed in the **community browser** under the New / Popular / + Zapped tabs, filterable by category + language. +- Searchable via the Search tab + Nostr search relays that index + `casewrap` content. +- Linked from share URLs: `/#/share/` resolves to a + read-only view + Fork button. + +## Publishing flow + +1. Set **Title** + **Category** in Project Metadata. +2. Click the **Publish to Nostr** button (top right). +3. Confirm modal opens with: + - A preview pane. + - Metadata list (Type / Title / Category / Author / etc). + - **Public / Private** radio (defaults to Public). + - **Zap-gated (premium)** toggle (defaults off → public free). +4. Click **Publish**. +5. The event signs through your Nostr extension + broadcasts to + the relay list in **Settings → Relays**. + +## Editing a published design + +Loading a public design from the community browser drops you in +**edit mode** — the publish button now says "Publish update" and +preserves the original d-tag. Republishing replaces the event in +place via NIP-33. + +To **fork** instead of edit (so your version becomes a new event +under your pubkey), use the community browser's **Fork** action on +the design row — it clears the editing state so your next publish +mints a fresh d-tag. + +## Forking + attribution + +Linked-design layers from other authors auto-emit: + +- `p` tags for each contributor (so they get notifications + can + be discovered as design contributors). +- `link` tags so external clients can resolve the sub-designs. + +See the [Credits panel](#/help/panel-credits) for what gets surfaced +in-app + at publish time. + +## Caveats + +- Anything you upload as an image layer is embedded in the event + payload as a `data:` URL — Nostr relays may reject very large + events. For high-res photo layers, prefer publishing the photo + somewhere else and linking via an `https://` URL. +- Even with private toggles off, the metadata tags (title, + category) are public. The encryption only protects the `content` + field. diff --git a/src/help/publishing.md b/src/help/publishing.md new file mode 100644 index 0000000..bc389ed --- /dev/null +++ b/src/help/publishing.md @@ -0,0 +1,66 @@ +# Publishing — overview + +Artstr Studio publishes designs to Nostr instead of a private +server. Every published design becomes a kind-30078 event (or +kind-30023 for book chapters) that anyone can fetch, fork, or +embed. + +There are three visibility tiers: + +| Tier | Who can open it | Use case | +|------|-----------------|----------| +| **[Public](#/help/publishing-public)** | Anyone with the naddr / nevent. | Default — discoverable in the community browser, forkable. | +| **[Private](#/help/publishing-private)** | Only you, via the same Nostr key (any device). | Working drafts, personal projects, archives. End-to-end encrypted. | +| **[Premium](#/help/publishing-premium)** | Anyone who pays a one-time zap (sats). | Paid templates / books. Watermarked preview + an unlock CTA. | + +## Choosing a tier + +The choice is made in the **Publish to Nostr** confirm modal: + +- Top of the modal shows a **Public / Private** radio. +- A **Zap-gated (premium)** toggle below it (hidden when Private is + selected — encrypted designs can't be feed-listed, so gating them + for sale doesn't make sense). +- The book sidebar has its own **🔒 Private** chip that flips + visibility for the whole book + every sidecar event. + +## How the publish event differs per tier + +| | Public | Private | Premium | +|---|---|---|---| +| Event kind | 30078 | 30078 | 30078 | +| `t` tag | `casewrap` + mode-specific | `encrypted` | `casewrap` + `premium` | +| `content` field | JSON payload | encrypted envelope | encrypted envelope | +| Preview JPEG | none | none | watermarked, embedded | +| Discovery | community browser + naddr share | naddr share only | community browser (premium tab) | +| Lightning | `zap` tags for tipping | none | `zap` tags split between creator + platform | + +## Tipping vs unlocking (NIP-57 zaps) + +Zaps are NIP-57 lightning payments. Two flavours: + +- **Tipping** — any user can zap any design / page / chapter. Goes + 100% to the creator's lightning address (no platform share). + Works on public, private (for you only), and premium designs. +- **Unlocking** — a premium design's zap requirement. The buyer zaps + the required minimum; on receipt of a valid zap receipt, the + client decrypts the design. Split: creator share + platform fee. + +See [Zaps](#/help/zaps) for the full mechanics + how to enable +zapping in your profile. + +## Sending zaps + +You need a **NIP-47 wallet** (Nostr Wallet Connect, or NWC) connected +to Artstr so it can sign + send invoice payments without leaving the +app. See [NWC setup](#/help/nwc). + +## Editing + republishing + +All published events are **replaceable** (NIP-33). Re-publishing under +the same d-tag updates the event in place — old `naddr` share links +keep working; `nevent` (snapshot) share links point at the old +version. + +The community browser's **Mine** tab shows every event you've +published with a "Republish" + "Delete" action per row. diff --git a/src/help/slide.md b/src/help/slide.md new file mode 100644 index 0000000..fe4a971 --- /dev/null +++ b/src/help/slide.md @@ -0,0 +1,45 @@ +# Slide + +A 16:9 standalone slide. Same flat-canvas model as Custom Art but +locked to 16:9 + carries a speaker-notes field. + +## When to use + +- A single one-off slide you want to share or embed (16:9 social + card, lecture insert, recap card). +- The slide is the artwork — you don't need a multi-slide deck shell. + +## Geometry + +Default 1920 × 1080 at 96 DPI. The aspect lock keeps you on 16:9; you +can resize via Presets in the sidebar. + +## Speaker notes + +The **Notes** field in the sidebar attaches a freeform string to the +slide event. Shown in: + +- Presenter Mode (Deck mode only). +- The published event's payload — anyone who forks gets your notes. + +## Importing into a deck + +A standalone Slide is also a Deck-importable unit: + +1. In **Deck** mode, click **Import slide** on a slide tile. +2. Pick from Nostr or paste a slide event id. +3. The slide drops in as a new entry; can be edited inline. + +## Publish + +Posts a `casewrap-slide` event. The full layer state ships in +`payload.slide.layers`; the canvas dimensions + background + notes +live in `payload.slide`. + +## Tips + +- For multi-slide presentations, jump to [Slide deck](#/help/deck) + instead. A deck owns its own ordered slide list; standalone + slides import as forkable units. +- Linked-design layers work in slides too — embed someone else's + Custom Art / Pixel Art as a layer. diff --git a/src/help/template.md b/src/help/template.md new file mode 100644 index 0000000..d667186 --- /dev/null +++ b/src/help/template.md @@ -0,0 +1,65 @@ +# Template tab + +The **Template** tab is where you author finished artwork — anything +you'd hand off to a printer, post as a slide, or publish to a community +feed. Each option below the tab switcher picks a different artboard. + +## Choosing a layout + +Click an item in the left sidebar's *Template type* picker. The +canvas reshapes to that mode's geometry and the right-side options +(layers, export) re-scope to it. + +| Mode | When to use | +|------|-------------| +| **[Cover wrap](#/help/cover)** | DVD / Blu-ray / softcover case wraparound. | +| **[Disc-label sheet](#/help/disc)** | Two-up CD/DVD label sheet for printing on full-page label stock. | +| **[Jewel-case insert](#/help/jewel)** | Front + tray inserts for a CD jewel case. | +| **[Custom art](#/help/customart)** | Any-aspect freeform canvas. Posters, social art, anything. | +| **[Slide](#/help/slide)** | 16:9 single slide with speaker-notes field. | +| **[Slide deck](#/help/deck)** | Ordered list of slides; publishes as one event with all slides inlined. | +| **[Pixel art](#/help/pixelart)** | Palette-indexed grid editor up to 128×128 with multi-frame animation. | +| **[Book](#/help/book)** | Multi-page reflowable book with cover, masters, chapters, PDF export. | + +## Project metadata + +Above the type picker is the **Project Metadata** block: + +- **Title** — required for publish. +- **Category** — required for publish; chooses the discovery facet + (`movie`, `comic`, `book`, etc). +- **IMDb / UPC / TVDB / MusicBrainz IDs** — optional external IDs; + used to deduplicate against other community designs. +- **Language** — affects search / browser filters. +- **Custom tag** — free-form discovery tag. + +## Layers panel + +Once you add anything to the canvas, the **Layers** panel surfaces: + +- Per-layer visibility / lock toggles. +- Drag to reorder z. +- Multi-select with shift/cmd, then use the **Align / Distribute** + panel + the **Pathfinder** ops (Unite / Subtract / Intersect / + Exclude) for vector shapes. +- Layer-target dropdown picks which sub-surface a new layer belongs + to (e.g. cover front vs spine vs back; jewel front vs tray). + +## Export + publish + +- **Save / Save as** — local JSON download of the project. +- **Open** — load a previously-saved JSON file. +- **Print / Save as PDF** — opens the browser print dialog with + proper trim + bleed. +- **Export JPEG / PNG** — direct raster export. +- **Publish to Nostr** — opens a confirmation modal. Public, private + (encrypted to self), or premium (zap-gated, watermarked preview). + +## Common gotchas + +- The Layers panel respects the active layer-target. If you don't see + a layer you just added, switch the dropdown to its surface. +- "Custom" book size unlocks the trim inputs in Document settings. +- Mode-specific guides + bleed lines toggle on the canvas via the + show-guides / show-bleed / show-crops checkboxes in the right-side + Export fieldset. diff --git a/src/help/tool-direct-select.md b/src/help/tool-direct-select.md new file mode 100644 index 0000000..444eca2 --- /dev/null +++ b/src/help/tool-direct-select.md @@ -0,0 +1,54 @@ +# Direct Selection tool (A) + +Edits anchors + Bézier handles on a single vector path. The +counterpart to Select, which moves the whole layer. + +## Engaging a path + +- Switch to the tool (press **A**) and click a vector shape — every + anchor + handle appears. +- Or with Select active, **double-click** the shape to dive in. +- Non-pen primitives (rectangle, ellipse, polygon, star) auto-convert + to an editable path when you enter Direct Selection on them. + +## Single-anchor interactions + +- **Click** an anchor to select it (filled square). +- **Drag** to move just that anchor; the segments connected to it + follow. +- **Shift+click** more anchors to multi-select. +- **Drag the small handle dot** off an anchor to adjust its tangent. + By default both handles move symmetrically (smooth point). +- **Alt+drag** a handle to break symmetry — corner point with + independent handles per side. +- **Alt+click an anchor** toggles between smooth point and corner + point. + +## Multi-anchor edits + +- Marquee around the path with empty-space drag — every anchor + inside the marquee selects. +- **Arrow keys** nudge every selected anchor by 1 px. **Shift+Arrow** + by 10 px. +- **Backspace / Delete** removes selected anchors. Adjacent segments + rejoin if the removed anchor was interior; the path opens if the + removed anchor was an endpoint. + +## Segment edits + +- **Drag a segment** between two anchors to bow it (curve-direct- + edit) without moving the anchors themselves. +- **Alt+click a segment** to insert a new anchor at the click point + and drag it. +- To continue drawing from an open endpoint, switch to the **Pen + tool** and click the endpoint — Direct Selection alone won't + extend the path. + +## Tips + +- The tool icon is light blue (vs. the Select tool's black arrow) + so you can tell at a glance which mode you're in. +- Clicking off-canvas (or pressing **Esc**) deselects all anchors + and flips back to Select. +- See also: [Pen tool](#/help/tool-pen) for drawing new paths from + scratch. diff --git a/src/help/tool-eyedropper.md b/src/help/tool-eyedropper.md new file mode 100644 index 0000000..9744ffb --- /dev/null +++ b/src/help/tool-eyedropper.md @@ -0,0 +1,46 @@ +# Eyedropper tool (I) + +Samples a colour from anywhere on screen — Artstr canvas, another +browser tab, your desktop wallpaper, anywhere. Uses the browser's +native `window.EyeDropper` API. + +## Behaviour + +- Click the toolbar button (or press **I**) → the cursor changes to + a crosshair and a magnified pixel-grid follows the pointer. +- Click anywhere on screen to sample that pixel's colour. +- The sampled hex value lands in the **currently-active colour + swatch** — whichever fill / stroke / background slot last had + focus (right-side panel). +- Press **Esc** to cancel without sampling. + +## OS colour picker eyedropper + +The browser's native colour picker (which opens when you click any +colour field in the right-side panels) also includes its own +eyedropper button in Chromium-based browsers — useful when you +want to sample a colour directly into a specific field instead of +the active slot. Behaviour differs by browser: + +- **Chromium-based** (Chrome / Edge / Brave) — the native colour + picker has an eyedropper inside. +- **Safari / Firefox** — no built-in eyedropper. Type a hex value + by hand or use the dedicated toolbar tool to sample into the + active swatch. + +## Browser support + +- Chromium-based browsers (Chrome / Edge / Brave / Arc) — full + support. +- Safari / Firefox — `window.EyeDropper` isn't implemented; the + button is disabled with a tooltip explaining the gap. Fall back + to manually entering a hex value or copying one from another tool. + +## Tips + +- The pixel-grid magnifier shows a 3×3 zoomed view around the + pointer so you can land on the exact pixel — useful when + sampling thin lines or anti-aliased edges. +- The eyedropper is the easiest way to colour-match a logo or + branded element on another tab — just pop the source open + next to Artstr and sample directly. diff --git a/src/help/tool-hand.md b/src/help/tool-hand.md new file mode 100644 index 0000000..a4da63d --- /dev/null +++ b/src/help/tool-hand.md @@ -0,0 +1,32 @@ +# Hand tool (H) + +Pans the canvas viewport. Useful for navigating large artboards +when the canvas extends beyond the visible area. + +## Behaviour + +- **Drag** the canvas to pan in any direction. +- The cursor changes to a closed grab while dragging. +- Doesn't select, modify, or create layers. + +## Pen-mode Spacebar reposition + +A specific case: while mid-draw with the **Pen tool**, holding +**Spacebar** repositions the anchor you're currently placing (and +slides its handles with it). This is a Pen-tool-only behaviour, not +a global "temp-pan". Release to commit the anchor. + +## Zoom controls + +Zoom isn't tied to the Hand tool — it works from any tool via the +canvas toolbar at the top of the canvas: + +- **− / +** buttons step by 10 % each. +- **Zoom dropdown** chooses a preset level. +- **Fit** button auto-fits the artboard to the viewport. + +## Tips + +- A trackpad two-finger swipe also pans without a tool change. +- For pixel-precise alignment, zoom in via the **+** button until + pixel boundaries are visible, then pan with the Hand tool. diff --git a/src/help/tool-pen.md b/src/help/tool-pen.md new file mode 100644 index 0000000..e9543d0 --- /dev/null +++ b/src/help/tool-pen.md @@ -0,0 +1,77 @@ +# Pen tool (P) + +Illustrator-parity Bézier path tool. Click to drop anchors, +click-drag for tangent handles. Outputs a vector `shape` layer +(path kind) — editable later with [Direct Selection](#/help/tool-direct-select). + +## Drawing a new path + +- **Click** to drop a corner anchor. +- **Click-drag** to drop an anchor and pull tangent handles for a + smooth curve. +- **Shift+click** constrains the segment to a 45° angle (horizontal, + vertical, or diagonal). +- **Close the path** by clicking the first anchor (the cursor shows + a small ○ when you're over it). +- **Esc** or switching tools commits the draft as an open path. + +## Modifying mid-draw + +- **Spacebar (hold)** — reposition the anchor you're currently + placing without dropping it. Release to commit. +- **Alt+drag** while pulling a handle — break symmetry so the + outgoing and incoming handles can point in different directions + (corner-with-handles). +- **Click the most recent anchor** before clicking elsewhere to + delete just that handle, switching it from smooth to corner. + +## Editing an existing path + +When the Pen tool's cursor hovers over an existing path of the +selected layer: + +- **Click a segment** between two anchors → adds a new anchor at + that point. +- **Click an anchor** → removes it (auto-rejoin neighbours). +- **Click an endpoint** when the path is open → continues drawing + from that endpoint. + +When hovering an anchor on a path **not** currently selected, the +Pen cursor stays as the normal pen — click only selects the layer. + +## Multi-path operations + +With ≥2 vector shapes selected, the **Pathfinder** panel becomes +available: + +- **Unite** — boolean OR. +- **Subtract** — front minus back. +- **Intersect** — boolean AND. +- **Exclude** — XOR. + +See [Pathfinder](#/help/pathfinder) for the full ops list. + +## Joining and averaging anchors (Cmd+J / Cmd+Alt+J) + +With **multiple anchors selected via the Direct Selection tool**: + +- **Cmd+J** — join the two endpoints into a single anchor (or + bridge two open paths into one). If the two endpoints are + coincident, they merge; otherwise a connecting straight segment + is added. +- **Cmd+Alt+J** — average (move both anchors to the midpoint + between them). Combined with **Cmd+J** afterwards, gives you a + literal Illustrator-style merge. + +## Tips + +- The Pen tool's options pane has its own **Fill colour**, **Stroke + colour**, and **Stroke width** inputs — set them before you start + drawing for immediate visual feedback. They're separate from any + layer's existing stroke settings. +- The **Fill closed path** checkbox in the options pane controls + whether a closed path renders with its fill colour or stays + outline-only. +- Pen and Pencil store the same underlying path data, so a Pencil + sketch can be opened with Direct Selection + edited with the Pen + ops without conversion. diff --git a/src/help/tool-pencil.md b/src/help/tool-pencil.md new file mode 100644 index 0000000..97ce001 --- /dev/null +++ b/src/help/tool-pencil.md @@ -0,0 +1,32 @@ +# Pencil tool (B) + +Freehand drawing. Your pointer's path is sampled as a polyline of +line segments — each stroke becomes its own `shape` (path) layer. + +## Drawing + +- **Click + drag** to draw. Release to commit. +- The resulting layer is a vector `shape` (path kind) — same + underlying data model as a Pen path, so you can edit it with + [Direct Selection](#/help/tool-direct-select) afterwards (drag + individual anchors, bow segments, etc). + +## Tool options + +The right-side **Tool options** pane has two inputs: + +- **Stroke colour** — the line colour. +- **Stroke width** — in pixels. + +Both apply at draw time. They're separate from any selected +layer's stroke settings. + +## Tips + +- Pencil strokes are stored as line-to segments (no smoothing in + v1). For a smoothed curve, draw with the **Pen tool** instead and + pull tangent handles where you want curves. +- Each release creates a new layer — for multi-stroke sketches you + can group the layers later via the Layers panel. +- Trackpad input gives you smoother visual motion than a mouse, but + the underlying path stays a polyline either way. diff --git a/src/help/tool-select.md b/src/help/tool-select.md new file mode 100644 index 0000000..f9c7629 --- /dev/null +++ b/src/help/tool-select.md @@ -0,0 +1,39 @@ +# Select tool (V) + +The default tool. Moves, scales, and rotates whole layers. + +## Single-layer interactions + +- **Click** a layer to select it. Eight handles appear around the + bounding box. +- **Drag** the layer body to move it. +- **Drag a corner handle** to scale on both axes. +- **Drag a side handle** to scale on one axis. +- **Drag the rotate handle** above the top edge to rotate. +- **Cmd/Ctrl + D** duplicates the selected layer. + +## Multi-select + +- **Shift+click** (or Cmd / Ctrl + click) another layer to add it to + the selection. +- **Marquee box-select** — click an empty area of the canvas and drag. + Every layer whose bounding box intersects the marquee joins the + selection. Hold **Shift / Cmd / Ctrl** while marqueeing to add to + the existing selection instead of replacing it. + +With ≥2 layers selected, the **Align & Distribute** panel + the +**Pathfinder** ops (for vector shapes) become available. + +## Dive into a path + +- **Double-click** a vector shape with Select active to flip to + Direct Selection on that path. The path's anchors + handles + appear so you can edit them. +- Clicking outside the shape with Direct Selection active deselects + and pops you back to Select. + +## Tips + +- A locked layer (lock icon in the Layers panel) refuses drag input + on the canvas; the row in the list still highlights on hover. +- Hidden layers (eye icon off) don't intercept canvas clicks at all. diff --git a/src/help/tool-shape.md b/src/help/tool-shape.md new file mode 100644 index 0000000..881b420 --- /dev/null +++ b/src/help/tool-shape.md @@ -0,0 +1,45 @@ +# Shape tool (S) + +Drops vector primitives by drag-creating a bounding box on the +canvas. The right-side **Tool options** pane picks which primitive + +its fill / stroke before you drag. + +## Primitives + +The options pane's **Shape** dropdown: + +- **Rectangle** — drag a bounding box. +- **Rounded rectangle** — same but with a configurable corner radius. +- **Circle** — perfect circle inscribed in the drag bbox. +- **Ellipse** — ellipse inscribing the drag bbox. +- **Triangle** — equilateral-ish triangle inscribed in the bbox. +- **Polygon** — N-sided regular polygon. +- **Star** — M-pointed star. +- **Line** — line from drag start to drag end. + +## Tool options + +- **Shape** — the dropdown above. +- **Fill** checkbox — toggle whether the new shape gets a solid fill. +- **Fill colour** — picker when Fill is on. +- **Stroke colour** + **Stroke width** — applies as the new shape's + stroke. Setting Stroke width to 0 + Fill on = filled-only. + +## Why these are vector + +Every primitive stores an internal `shape.kind` plus parameter data +(point count, inner radius, corner radius, etc). You can tweak those +parameters later from the layer-options pane. Switching to **Direct +Selection** on a primitive auto-converts it into an editable +anchors-plus-handles path, after which the primitive parameters are +replaced by the raw path data. + +## Tips + +- For non-rectangular text containers: draw a shape, then add a text + layer on top + use the text layer's **Clip** option (in the layer- + options pane) to clip the text to the shape. +- The **line** primitive's endpoints are anchors — switching to + Direct Selection lets you move each endpoint independently. +- A drag shorter than ~0.08 in drops a default-sized shape at the + cursor (so a click without dragging still creates something). diff --git a/src/help/tool-text.md b/src/help/tool-text.md new file mode 100644 index 0000000..fa12597 --- /dev/null +++ b/src/help/tool-text.md @@ -0,0 +1,60 @@ +# Text tool (T) + +Drops a text layer. Drag to size the bounding box; the layer is +created with `contentEditable` set so you can type immediately. + +## Creating + +- **Click** the canvas. A default-sized text box drops at the click + point, the tool auto-flips back to Select, and the box's + contentEditable area gains focus so you can start typing + immediately. The placeholder text is pre-selected so your first + keystroke replaces it. + +## Editing + +A text layer's content is HTML (rich text). Inline marks are +applied via the text-layer-options inline buttons (or the browser's +native rich-text shortcuts when the contenteditable area has focus +— browser behaviour differs): + +- **Bold**, *italic*, underline, strikethrough — via the inline + toolbar buttons that appear with a text layer selected. +- Inline link — use the link button in the layer-options pane to + add a URL to selected text. + +Block formatting: + +- Alignment (left / center / right) via the **Alignment** dropdown. +- Font, size, colour via the **Type** options pane. + +## Right-side Type panel + +When a text layer is selected: + +- Font family picker (system fonts + a vendored Google Fonts subset). +- Font size + weight + style. +- Colour (with the [Eyedropper](#/help/tool-eyedropper) inline). +- Letter spacing, line height. +- Horizontal + vertical alignment within the box. +- Padding (inset from the box edge). + +## Behaviour notes + +- Text rasterizes via the browser's font engine — what you see is + what the PNG export captures. PDF export emits real PDF text for + fonts on the embeddable allowlist (Times / Helvetica / Courier + families); other fonts fall back to vector paths in the PDF. +- Linked-design and pixel-art layers can sit on top of text without + the text losing its editability. +- For multi-page **books**, place body-text headings via the chapter + editor's markdown (the reflow engine handles pagination there) — + the Text tool is for designed-page captions / titles, not body. + +## Tips + +- A locked layer (lock icon in the Layers panel) refuses clicks — + unlock first if a text box won't activate. +- Token text (`{page-number}` etc) in a **book master** text layer + resolves per-page. Token text in a designed-page text layer stays + literal. diff --git a/src/help/vector-tools.md b/src/help/vector-tools.md new file mode 100644 index 0000000..9eff8f5 --- /dev/null +++ b/src/help/vector-tools.md @@ -0,0 +1,60 @@ +# Vector tools — overview + +The left-hand toolbar lives on every canvas-style artboard (Custom Art, +Slide, single deck slide, book pages, book masters). It picks the +active tool — what your pointer does when you click + drag the canvas. + +## The toolbar + +| Key | Tool | What it does | +|-----|------|-------------| +| **V** | [Select](#/help/tool-select) | Move / scale / rotate whole layers. The default. | +| **A** | [Direct Selection](#/help/tool-direct-select) | Edit anchors + handles on a single path. | +| **H** | [Hand](#/help/tool-hand) | Pan the canvas (hold spacebar for a quick temp-pan from any other tool). | +| **P** | [Pen](#/help/tool-pen) | Click to drop anchors, click-drag for Bézier handles. Illustrator-parity behaviour. | +| **B** | [Pencil](#/help/tool-pencil) | Freehand drawing. Smoothed into a Bézier path on release. | +| **S** | [Shape](#/help/tool-shape) | Drop primitives — rectangle / ellipse / polygon / star / line. | +| **T** | [Text](#/help/tool-text) | Drop a text layer. Drag to size, then type. | +| **I** | [Eyedropper](#/help/tool-eyedropper) | Sample a colour from anywhere on screen. | + +## Supporting panels + +These aren't toolbar tools — they're floating panels that operate on +whatever you've selected. + +- [**Pathfinder**](#/help/pathfinder) — Unite / Subtract / Intersect / + Exclude on two or more selected shapes. +- [**Align & Distribute**](#/help/align-distribute) — line up multiple + layers; even-space-distribute. +- [**Free Transform**](#/help/free-transform) — rotate / scale handles + around the selection bbox without flipping tools. + +## How the toolbar interacts with the layer list + +- Selecting a layer in the **Layers** panel acts like clicking it with + the Select tool — handles appear, the canvas selects it. +- Double-clicking a vector shape with Select drops you into Direct + Selection on that path (Illustrator-style "dive in"). Click outside + the shape (or hit Esc) to pop back out. +- The active tool persists per session — switching tabs / modes keeps + your last tool active. + +## Keyboard shortcuts (canvas focus) + +- **V / A / H / P / B / S / T / I** — switch tools. +- **Cmd/Ctrl + Z** / **Cmd/Ctrl + Shift + Z** — undo / redo. +- **Cmd/Ctrl + Y** — redo (alternate). +- **Cmd/Ctrl + D** — duplicate selected layer. +- **Cmd/Ctrl + C / X / V** — copy / cut / paste layer(s). +- **Cmd/Ctrl + G** — group selected layers. +- **Cmd/Ctrl + ] / [** — bring forward / send backward; add **Shift** + for bring-to-front / send-to-back. +- **Cmd/Ctrl + J** — close path / join two selected endpoints + (Pen / Direct Selection edit mode only). +- **Cmd/Ctrl + Alt + J** — average selected anchors to their centroid + (Pen / Direct Selection edit mode only). +- **Delete / Backspace** — remove selected layer; remove selected + anchor in Pen / Direct Selection edit mode. +- **Shift + click** (or Cmd / Ctrl + click) — additive multi-select. +- **Shift while drawing with the Pen tool** — constrain the next + anchor to a 45° angle from the previous one. diff --git a/src/help/zaps.md b/src/help/zaps.md new file mode 100644 index 0000000..c9ee5e1 --- /dev/null +++ b/src/help/zaps.md @@ -0,0 +1,93 @@ +# Zaps — NIP-57 lightning payments + +Artstr uses Nostr-native lightning zaps for both tipping creators +and unlocking premium designs. Same protocol either way; the +difference is who the zap goes to and what the client does on +receipt. + +## How zaps work in two paragraphs + +A zap is a kind-9735 receipt event proving that a Lightning +payment happened. The flow: + +1. The user (you) signs a kind-9734 **zap request** event with the + amount + recipient + intent. +2. The recipient's lightning address (`lud16` on their profile) + resolves to a LNURL endpoint; the request becomes a BOLT11 + invoice. +3. You pay the invoice from your wallet. The wallet's LSP issues + a **zap receipt** event back to the relays. +4. Clients that see the receipt update UI (zap-totals, unlock + gated content, etc). + +## Tipping any design / page / chapter + +Every design preview pane has a **⚡ Zap** button (visible when +the author has a lightning address on their profile). Clicking: + +1. Opens an amount picker (default presets 21 / 100 / 1000 sats + + custom). +2. Builds + signs the zap request via your Nostr extension. +3. Pays through your connected **NWC wallet** (see + [NWC setup](#/help/nwc)). If you don't have NWC connected, it + falls back to copying the invoice for you to pay manually in + any Lightning wallet. + +The zap goes 100% to the author. No platform share. + +## Unlocking a premium design + +Same plumbing, different outcome. The locked design row shows an +**Unlock with ⚡ zap** button. Clicking sends the minimum required +amount, split between the creator + the platform per the event's +`zap` tags (currently 70/30). On receipt: + +1. The client verifies the receipt's amount + recipient match the + event's `zap-min` + `zap` tags. +2. Derives the soft-gate key from the event metadata. +3. Decrypts the payload + saves it to your **My Purchases** + vault. + +See [Premium publishing](#/help/publishing-premium) for the publish +side. + +## Receiving zaps on your designs + +To get tipped or sold-to: + +1. Sign up at any LN-address service. **CoinOS** (free, web-based) + is the easiest: . +2. Edit your Nostr kind-0 profile to add `lud16` = your LN address + (e.g. `you@coinos.io`). Any Nostr client that edits profiles + works (Damus, Amethyst, primal.net, nos2x options page). +3. Wait a few minutes for your profile to propagate to the relays + you care about. + +Once `lud16` is set, every published design of yours becomes +zap-able automatically — Artstr discovers the lightning address +from your profile. + +## Sending zaps without an NWC wallet + +If you don't have NWC connected, the zap modal will: + +- Build the BOLT11 invoice. +- Show a QR code + a copy button. +- Wait for the invoice to be paid externally (you can pay from any + Lightning wallet — Phoenix, Wallet of Satoshi, etc). +- Verify the receipt on relays + update the UI. + +This is slower than NWC (you switch apps to pay) but works +universally. + +## Tips + +- The **Zapped** tab in the community browser sorts designs by + total zap volume — a quick way to find what people are paying + for. +- Zaps are public on Nostr (the receipt event is signed by the + LSP and broadcast). If you want anonymous payment, you'll need a + separate Lightning identity. +- Failed zaps (invoice paid but receipt never arrives) can be + diagnosed via the wallet's transaction log. The LSP issued the + receipt; the relays may have dropped it. diff --git a/src/index.html b/src/index.html index 1b49fac..621271d 100644 --- a/src/index.html +++ b/src/index.html @@ -500,6 +500,20 @@ body.template-deck #deckThemeFieldset { display: block; } #deckSlideThemeWrap { display: none; margin-top: 10px; } body.editing-deck-slide #deckSlideThemeWrap { display: block; } + /* Book Designer sidebar fieldset shows only in book mode. */ + #bookFieldset { display: none; } + body.template-book #bookFieldset { display: block; } + /* Hide cover / disc / artwork panels that aren't relevant in book + mode (Phase A2 — book mode shows only its own fieldset + layers + + metadata + save). The Pages overview canvas-host comes in + Phase A4; until then the canvas-host remains blank. */ + body.template-book #coverSizeFieldset, + body.template-book #coverArtworkFieldset, + body.template-book #discArtworkFieldset, + body.template-book #jewelArtworkFieldset, + body.template-book #customArtFieldset, + body.template-book #deckFieldset, + body.template-book #deckThemeFieldset { display: none !important; } #layersFieldset { order: 40; } #designerMetadataFieldset { order: 45; } #exportFieldset { order: 50; } @@ -531,6 +545,153 @@ text-decoration: underline; } + /* Contextual help icon — small ⓘ that opens the in-app docs + modal to a specific topic. Sits inline next to tab labels, + fieldset legends, panel headings, modal titles. */ + .docsHelpIcon { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; + margin-left: 6px; + font-size: .82rem; line-height: 1; + color: var(--muted); + cursor: pointer; + vertical-align: middle; + transition: color .12s, transform .12s; + user-select: none; + } + .docsHelpIcon:hover { color: var(--accent); transform: scale(1.1); } + .docsHelpIcon:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 1px; + border-radius: 50%; + } + /* In-tab context, the icon should be a touch lighter so it + doesn't compete with the tab label itself. */ + .appModeTab .docsHelpIcon { font-size: .9rem; color: rgba(255,255,255,.6); } + .appModeTab.active .docsHelpIcon { color: rgba(255,255,255,.85); } + .appModeTab .docsHelpIcon:hover { color: #fff; } + body:not(.dark) .appModeTab .docsHelpIcon { color: rgba(0,0,0,.4); } + body:not(.dark) .appModeTab.active .docsHelpIcon { color: rgba(255,255,255,.85); } + /* Layout-picker buttons are tight + active state inverts to + white-on-accent, so the icon needs its own light/dark + contrast pairs. */ + .layoutTab .docsHelpIcon { font-size: .76rem; margin-left: 4px; opacity: .55; } + .layoutTab.active .docsHelpIcon { color: rgba(255,255,255,.85); opacity: 1; } + .layoutTab:hover .docsHelpIcon { opacity: 1; } + + /* Docs modal — Read-the-Docs-style two-pane layout. */ + #docsModal .modalInner { + max-width: 1100px; width: 92vw; + max-height: 88vh; height: 720px; + padding: 0; overflow: hidden; + display: flex; flex-direction: column; + } + .docsHeader { + display: flex; align-items: center; gap: 10px; + padding: 10px 14px; border-bottom: 1px solid var(--line); + background: var(--bg); + } + .docsHeader h2 { margin: 0; font-size: 1rem; font-family: inherit; flex: 1; } + .docsHeader .docsBreadcrumb { + font-size: .76rem; color: var(--muted); + } + .docsBody { + flex: 1; display: flex; min-height: 0; + } + .docsSidebar { + width: 240px; flex-shrink: 0; + border-right: 1px solid var(--line); + overflow-y: auto; + background: var(--bg); + } + .docsSidebarGroup { padding: 6px 0; } + .docsSidebarGroup + .docsSidebarGroup { border-top: 1px solid var(--line); } + .docsSidebarLabel { + font-size: .68rem; text-transform: uppercase; + letter-spacing: .08em; color: var(--muted); + padding: 10px 14px 6px; font-weight: 700; + } + .docsSidebarItem { + display: block; padding: 6px 14px; + font-size: .85rem; color: var(--ink); + cursor: pointer; text-decoration: none; + border-left: 3px solid transparent; + } + .docsSidebarItem:hover { background: rgba(0,0,0,.04); } + .docsSidebarItem.current { + background: rgba(0,0,0,.06); + border-left-color: var(--accent); + font-weight: 600; + } + body.dark .docsSidebarItem:hover { background: rgba(255,255,255,.05); } + body.dark .docsSidebarItem.current { background: rgba(255,255,255,.08); } + .docsContent { + flex: 1; overflow-y: auto; + padding: 22px 30px; + font-family: Georgia, serif; + line-height: 1.6; font-size: 15px; + color: var(--ink); background: #fff; + } + body.dark .docsContent { background: #1a1a1a; } + .docsContent h1 { + font-family: inherit; font-size: 1.6rem; + margin: 0 0 .4em; padding-bottom: .3em; + border-bottom: 1px solid var(--line); + } + .docsContent h2 { + font-family: inherit; font-size: 1.2rem; + margin: 1.4em 0 .4em; + } + .docsContent h3 { + font-family: inherit; font-size: 1rem; + margin: 1.1em 0 .3em; + } + .docsContent p { margin: 0 0 .9em; } + .docsContent ul, .docsContent ol { margin: 0 0 .9em; padding-left: 1.4em; } + .docsContent li { margin-bottom: .2em; } + .docsContent a { color: var(--accent); } + .docsContent code { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: .92em; padding: 1px 5px; + background: rgba(0,0,0,.06); border-radius: 3px; + } + body.dark .docsContent code { background: rgba(255,255,255,.08); } + .docsContent pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: .85em; padding: 10px 14px; + background: rgba(0,0,0,.06); border-radius: 4px; + overflow-x: auto; + } + body.dark .docsContent pre { background: rgba(255,255,255,.06); } + .docsContent pre code { background: transparent; padding: 0; } + .docsContent blockquote { + margin: .9em 0; padding: 6px 14px; + border-left: 3px solid var(--accent); + background: rgba(0,0,0,.03); font-style: italic; + } + body.dark .docsContent blockquote { background: rgba(255,255,255,.04); } + .docsContent table { + border-collapse: collapse; margin: 1em 0; + font-size: .9em; + } + .docsContent th, .docsContent td { + border: 1px solid var(--line); padding: 6px 10px; + text-align: left; + } + .docsContent th { background: rgba(0,0,0,.04); font-weight: 700; } + body.dark .docsContent th { background: rgba(255,255,255,.05); } + .docsContent hr { + border: none; border-top: 1px solid var(--line); + margin: 1.4em 0; + } + .docsLoading { + color: var(--muted); font-style: italic; + } + @media (max-width: 720px) { + .docsSidebar { width: 160px; } + .docsContent { padding: 16px 18px; } + } + .appModeTabs { display: grid; grid-template-columns: 1fr 1fr; @@ -768,12 +929,11 @@ cursor: pointer; font-size: .8rem; padding: 2px 6px; line-height: 1.1; } .deckSlideActions button:hover { background: #f1f5f9; } - /* Overlay wrapper occupies the canvas grid column. The deck - edit bar inside is absolutely positioned to its top-center, - so it hovers over the canvas-host (#previewShell) without - being a child of it — panning / zooming the canvas doesn't - drag it along, and it stays centred on the canvas column - rather than the viewport. */ + /* Overlay wrapper occupies the canvas grid column. The bars + inside are absolutely positioned to its top-center, so they + hover over the canvas-host (#previewShell) without being a + child of it — panning / zooming the canvas doesn't drag them + along, and they don't drift over the sidebar or header. */ .canvasOverlayBars { grid-area: canvas; position: relative; @@ -1466,6 +1626,474 @@ body.template-pixelart #layersFieldset, body.template-pixelart #printExportRow { display: none !important; } + /* Book mode: the canvas-host is replaced by the Pages overview + / Cover view (see #bookView below). Hide the sheet, tool + palette, canvas toolbar, layers panel, export fieldset — + UNLESS a specific page is being edited (`editing-book-page` + class), in which case those panels return and #bookView + hides. Same pattern the deck sorter uses for the slide editor. */ + body.template-book:not(.editing-book-page):not(.editing-book-cover) #sheetWrap, + body.template-book:not(.editing-book-page):not(.editing-book-cover) #toolOptions, + body.template-book:not(.editing-book-page):not(.editing-book-cover) #canvasToolbar, + body.template-book:not(.editing-book-page):not(.editing-book-cover) #exportFieldset, + body.template-book:not(.editing-book-page):not(.editing-book-cover) #layersFieldset, + body.template-book:not(.editing-book-page):not(.editing-book-cover) #printExportRow, + body.template-book:not(.editing-book-page):not(.editing-book-cover) .toolPalette, + body.template-book .coverOnly, + body.template-book .diskLayer, + body.template-book .jewelLayer, + body.template-book .bleedFill, + body.template-book .trimArea, + body.template-book .foldLine, + body.template-book .discSpineStrip, + body.template-book .jewelSpineGuide, + body.template-book .spineText { display: none !important; } + /* When editing the cover, hide the Pages-overview / Cover-summary + panels so the wider canvas takes the room. */ + body.template-book.editing-book-cover #bookView { display: none; } + + /* Markdown chapter editor — shown when a reflow entry is open + on the book canvas. Two-pane layout: textarea on the left, + paginated preview on the right. */ + #bookChapterEditor { display: none; } + body.template-book.editing-book-chapter #bookChapterEditor { + display: flex; + flex-direction: column; + width: 100%; gap: 10px; + padding: 14px 18px; + box-sizing: border-box; + min-height: calc(100vh - 160px); + } + .bookChapterEditorToolbar { + display: flex; align-items: center; gap: 10px; flex-wrap: wrap; + padding-bottom: 8px; border-bottom: 1px solid var(--line); + } + .bookChapterEditorToolbar input[type="text"] { + flex: 1; min-width: 160px; + font-size: 1rem; font-weight: 600; + padding: 4px 8px; + border: 1px solid var(--line); + } + .bookChapterEditorPages { + font-size: .78rem; color: var(--muted); white-space: nowrap; + } + .bookChapterEditorSourceBar { + display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + padding: 6px 0; border-bottom: 1px solid var(--line); + font-size: .82rem; + } + .bookChapterEditorSourceBar input[type="text"] { + flex: 1; min-width: 200px; + padding: 4px 8px; border: 1px solid var(--line); + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: .76rem; + } + .bookChapterEditorSourceBar button { + padding: 3px 8px; font-size: .8rem; + border: 1px solid var(--line); background: #fff; cursor: pointer; + } + .bookChapterEditorSourceBar button.primary { + background: var(--accent); color: #fff; border-color: var(--accent); + } + .bookChapterEditorSourceBar button:disabled { opacity: .55; cursor: progress; } + .bookChapterEditorSourceLabel { + font-size: .76rem; color: var(--muted); padding: 0 4px; + border: 1px dashed var(--line); border-radius: 4px; + white-space: nowrap; + } + .bookChapterEditorSourceLabel.linked { color: var(--accent); border-color: var(--accent); border-style: solid; } + .bookChapterEditorSourceLabel.pinned { color: #6b21a8; border-color: #c084fc; border-style: solid; } + .bookChapterEditorSourceMsg { font-size: .74rem; color: var(--muted); } + .bookChapterEditorSourceMsg.error { color: #b91c1c; } + .bookChapterEditorSourceMsg.ok { color: #15803d; } + .bookChapterEditorBody { + flex: 1; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + min-height: 0; + } + #bookChapterEditorTextarea { + width: 100%; height: 100%; + padding: 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; line-height: 1.55; + border: 1px solid var(--line); background: #fff; + resize: none; + } + /* Force the CodeMirror instance to fill the editor pane so it + doesn't render at its 300 px default. .CodeMirror is the + library's outer element, .CodeMirror-scroll the scrollable + inner; both need an explicit height of 100% to match the + grid cell. */ + .bookChapterEditorBody .CodeMirror { + width: 100%; height: 100%; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; line-height: 1.55; + } + .bookChapterEditorBody .CodeMirror-scroll { padding: 8px 4px; } + .bookChapterEditorPreviewWrap { + border: 1px solid var(--line); background: #fff; + overflow: auto; + padding: 16px 20px; + } + #bookChapterEditorPreview { font-family: Georgia, serif; line-height: 1.6; } + #bookChapterEditorPreview h1 { font-family: Helvetica, Arial, sans-serif; font-size: 2rem; margin: 0 0 .6em; } + #bookChapterEditorPreview h2 { font-family: Helvetica, Arial, sans-serif; font-size: 1.5rem; margin: 1.2em 0 .4em; } + #bookChapterEditorPreview h3 { font-family: Helvetica, Arial, sans-serif; font-size: 1.2rem; margin: 1em 0 .4em; } + #bookChapterEditorPreview p { margin: 0 0 .8em; } + #bookChapterEditorPreview blockquote { margin: .8em 1.2em; font-style: italic; color: #4b5563; } + #bookChapterEditorPreview code { background: #f5f5f5; padding: 1px 5px; font-size: .92em; } + #bookChapterEditorPreview pre { background: #f5f5f5; padding: 10px 12px; overflow-x: auto; } + #bookChapterEditorPreview pre code { background: none; padding: 0; } + #bookChapterEditorPreview a { color: #0066cc; } + #bookChapterEditorPreview hr { border: 0; border-top: 1px solid #cccccc; margin: 1.2em 0; } + #bookChapterEditorPreview ul, #bookChapterEditorPreview ol { padding-left: 1.4em; margin: 0 0 .8em; } + #bookChapterEditorPreview img { max-width: 100%; } + + /* Book view shell — mirrors the deck-sorter layout pattern. */ + #bookView { display: none; } + body.template-book:not(.editing-book-page):not(.editing-book-chapter) #bookView { + display: flex; + flex-direction: column; + width: 100%; + gap: 16px; + padding: 22px 26px; + box-sizing: border-box; + min-height: calc(100vh - 160px); + } + /* Page-context bar — lives inside .canvasOverlayBars so it + hovers at the top-center of the canvas grid column without + being a child of the scrolling #previewShell. */ + #bookPageEditBar { + display: none; + position: absolute; top: 10px; left: 50%; + transform: translateX(-50%); + align-items: center; gap: 8px; + background: var(--panel, #fff); + border: 1px solid var(--line); + border-radius: 999px; + padding: 5px 14px 5px 8px; + box-shadow: 0 6px 20px rgba(15,23,42,.18); + font-size: .82rem; + } + body.template-book.editing-book-page #bookPageEditBar { display: flex; } + #bookPageEditBack { + border: 0; background: var(--accent); color: #fff; + cursor: pointer; border-radius: 999px; + padding: 4px 12px; font-weight: 600; font-size: .8rem; + } + #bookPageEditPrev, #bookPageEditNext { + border: 1px solid var(--line); background: #fff; + cursor: pointer; border-radius: 999px; + padding: 2px 10px; font-size: .85rem; line-height: 1; + min-width: 28px; + } + #bookPageEditPrev:disabled, #bookPageEditNext:disabled { + opacity: .4; cursor: not-allowed; + } + #bookPageEditLabel { + color: var(--ink); font-weight: 600; + padding: 0 4px; + } + .bookViewToggleBar { + display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + padding-bottom: 8px; border-bottom: 1px solid var(--line); + } + .bookViewToggle { + padding: 6px 14px; font-size: .88rem; cursor: pointer; + background: transparent; border: 1px solid var(--line); + border-radius: 0; + } + .bookViewToggle.active { + background: var(--accent); color: #fff; + border-color: var(--accent); + } + .bookViewSpacer { flex: 1; } + .bookCountReadout { + font-size: .82rem; color: var(--muted); white-space: nowrap; + } + .bookVisibilityChip { + font-size: .76rem; padding: 2px 10px; + border: 1px solid var(--line); background: #fff; + border-radius: 999px; cursor: pointer; + } + .bookVisibilityChip.public { color: #15803d; border-color: #86efac; } + .bookVisibilityChip.private { color: #6b21a8; border-color: #c084fc; background: #faf5ff; } + .bookVisibilityChip:disabled { opacity: .5; cursor: not-allowed; } + /* Reader Mode — full-screen book viewer launched from the + Pages overview. Mirrors Presenter Mode's pattern for decks + but with a TOC sidebar instead of a slide strip. */ + #bookReaderOverlay { + position: fixed; inset: 0; z-index: 9999; + background: #1a1a1a; color: #fafafa; + display: none; flex-direction: column; + font-family: system-ui, -apple-system, sans-serif; + } + #bookReaderOverlay.open { display: flex; } + .bookReaderHeader { + display: flex; align-items: center; gap: 12px; + padding: 10px 14px; border-bottom: 1px solid #2a2a2a; + background: #0f0f0f; + } + .bookReaderTitle { flex: 1; font-size: .95rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .bookReaderTocToggle, .bookReaderCloseBtn { + background: transparent; color: #fafafa; border: 1px solid #444; + padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: .82rem; + } + .bookReaderTocToggle:hover, .bookReaderCloseBtn:hover { background: #2a2a2a; } + .bookReaderBody { + flex: 1; display: flex; min-height: 0; position: relative; + } + .bookReaderToc { + width: 0; overflow: hidden; transition: width .18s ease; + background: #0f0f0f; border-right: 1px solid #2a2a2a; + } + .bookReaderToc.open { width: 240px; overflow-y: auto; } + .bookReaderTocList { padding: 6px 0; } + .bookReaderTocItem { + padding: 6px 14px; font-size: .82rem; color: #c0c0c0; + cursor: pointer; border-left: 3px solid transparent; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .bookReaderTocItem:hover { background: #1a1a1a; color: #fff; } + .bookReaderTocItem.current { background: #1a1a1a; color: #fff; border-left-color: var(--accent, #f59e0b); } + .bookReaderTocItem.chapterHead { color: #fde68a; font-weight: 600; padding-top: 10px; } + .bookReaderStage { + flex: 1; display: flex; align-items: center; justify-content: center; + position: relative; min-width: 0; cursor: pointer; + user-select: none; + } + .bookReaderPage { + max-width: 92%; max-height: 90%; + box-shadow: 0 8px 40px rgba(0,0,0,.6); + background: #fff; + } + .bookReaderPageWrap { + display: flex; align-items: center; justify-content: center; + width: 100%; height: 100%; + } + .bookReaderArrow { + position: absolute; top: 50%; transform: translateY(-50%); + width: 44px; height: 60px; display: flex; align-items: center; justify-content: center; + background: rgba(0,0,0,.4); border: none; color: #fafafa; + font-size: 1.6rem; cursor: pointer; opacity: 0; transition: opacity .12s; + } + .bookReaderStage:hover .bookReaderArrow { opacity: 1; } + .bookReaderArrow.prev { left: 14px; } + .bookReaderArrow.next { right: 14px; } + .bookReaderArrow:disabled { opacity: 0; pointer-events: none; } + .bookReaderFooter { + display: flex; align-items: center; gap: 14px; + padding: 8px 16px; background: #0f0f0f; border-top: 1px solid #2a2a2a; + font-size: .78rem; color: #c0c0c0; + } + .bookReaderProgress { flex: 1; height: 4px; background: #2a2a2a; border-radius: 2px; overflow: hidden; } + .bookReaderProgressFill { height: 100%; background: var(--accent, #f59e0b); width: 0%; transition: width .18s; } + .bookStarterPanel { + grid-column: 1 / -1; + padding: 18px 22px; border: 1px dashed var(--line); border-radius: 8px; + background: #fafafa; + } + .bookStarterGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; + } + .bookStarterCard { + display: flex; flex-direction: column; align-items: flex-start; + padding: 14px 14px 12px; + border: 1px solid var(--line); border-radius: 6px; + background: #fff; cursor: pointer; text-align: left; + transition: border-color .12s, box-shadow .12s, transform .08s; + } + .bookStarterCard:hover { + border-color: var(--accent); + box-shadow: 0 4px 14px rgba(15, 23, 42, .08); + transform: translateY(-1px); + } + .bookStarterCardLabel { font-size: 1rem; font-weight: 700; } + .bookStarterCardDims { font-size: .78rem; color: var(--muted); margin-top: 2px; } + .bookStarterCardDesc { font-size: .76rem; color: var(--ink); margin-top: 8px; line-height: 1.35; } + .bookPageTile { + position: relative; + border: 1px solid var(--line); background: #fff; + aspect-ratio: 2 / 3; + display: flex; flex-direction: column; + align-items: center; justify-content: center; + cursor: pointer; transition: border-color .12s; + } + .bookPageTile:hover { border-color: var(--accent); } + .bookPageTile.designedPage { background: #fff; } + .bookPageTile.reflowSegment { + background: linear-gradient(135deg, #f8fafc 25%, transparent 25%, transparent 50%, #f8fafc 50%, #f8fafc 75%, transparent 75%) 0 0/8px 8px; + } + .bookPageTileGateChip { + position: absolute; bottom: 4px; left: 4px; + padding: 2px 8px; font-size: .7rem; + background: #fef3c7; color: #92400e; + border: 1px solid #fbbf24; border-radius: 999px; + z-index: 5; pointer-events: none; + } + .bookPageTileUpdateChip { + position: absolute; top: 4px; left: 4px; + padding: 2px 8px; font-size: .72rem; + background: #fef3c7; color: #92400e; + border: 1px solid #fbbf24; border-radius: 999px; + cursor: pointer; z-index: 5; + } + .bookPageTileUpdateChip:hover { background: #fde68a; } + .bookPageTileUpdateChip:disabled { opacity: .6; cursor: progress; } + .bookPageTileNum { + position: absolute; top: 6px; left: 8px; + font-size: .72rem; color: var(--muted); font-weight: 600; + z-index: 10; + background: rgba(255,255,255,.85); + padding: 0 4px; border-radius: 2px; + } + /* Master layers shown as an underlay while editing a page — + click-through and slightly faded so the page layers above + are obviously the editable ones. Master-edit mode (the + master IS the editing target) keeps them opaque + + interactive via the :not(.editing-book-master) guard. */ + body.editing-book-page:not(.editing-book-master) .freeLayer[data-target^="book-master:"] { + pointer-events: none; + opacity: 0.55; + } + .bookMasterRow { + display: flex; align-items: center; gap: 8px; + padding: 8px 10px; border: 1px solid var(--line); + border-radius: 0; background: var(--bg); + } + .bookMasterRow.isDefault { border-color: var(--accent); background: rgba(59,130,246,.05); } + .bookMasterRowLabel { flex: 1; min-width: 0; } + .bookMasterRowLabel input { + width: 100%; padding: 3px 6px; font-size: .9rem; + border: 1px solid var(--line); background: #fff; + } + .bookMasterRowDefault { + font-size: .68rem; color: var(--accent); + text-transform: uppercase; letter-spacing: .06em; + font-weight: 700; + } + .bookMasterRowActions { display: flex; gap: 4px; flex-wrap: nowrap; } + .bookMasterRowActions button { + padding: 3px 8px; font-size: .78rem; + border: 1px solid var(--line); background: #fff; cursor: pointer; + } + .bookMasterRowActions button.danger:hover { + background: var(--warn, #fee2e2); border-color: var(--warn, #fca5a5); + } + .bookPageTileThumb { + position: absolute; inset: 0; + display: flex; align-items: center; justify-content: center; + background: #fff; + overflow: hidden; + pointer-events: none; + } + .bookPageTileThumb > div { + max-width: 100%; max-height: 100%; + } + .bookPageTileActions { + position: absolute; top: 4px; right: 4px; + display: flex; gap: 4px; + opacity: 0; transition: opacity .12s; + z-index: 20; /* always above the composited preview's text layers */ + } + .bookPageTile:hover .bookPageTileActions { opacity: 1; } + .bookPageTileAction { + width: 22px; height: 22px; padding: 0; border-radius: 0; + border: 1px solid var(--line); background: #fff; + cursor: pointer; font-size: .85rem; line-height: 1; + position: relative; z-index: 21; + } + .bookPageTileAction.danger:hover { + background: var(--warn, #fee2e2); + border-color: var(--warn, #fca5a5); + } + .bookPageTileMasterSel { + height: 22px; padding: 0 4px; font-size: .72rem; + border: 1px solid var(--line); background: #fff; + max-width: 120px; + position: relative; z-index: 21; + } + .bookPageTile.dragging { opacity: .5; } + .bookPageTile.dragover { + outline: 2px solid var(--accent); + outline-offset: -2px; + } + .bookPageTileLabel { + font-size: .8rem; color: var(--muted); text-align: center; + padding: 0 8px; + } + /* Cover view: a live-derived spread preview + readout. */ + .bookCoverSummary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 12px 18px; + padding: 10px 12px; + border: 1px solid var(--line); + border-radius: 0; + background: var(--bg); + font-size: .82rem; + } + .bookCoverSummary dt { + font-size: .72rem; color: var(--muted); + text-transform: uppercase; letter-spacing: .04em; + margin: 0 0 2px; + } + .bookCoverSummary dd { + font-weight: 600; margin: 0; + } + .bookCoverSummary dd.derived { color: var(--accent); } + .bookCoverSummary dd .smallNote { + font-weight: 400; color: var(--muted); font-size: .72rem; + display: block; margin-top: 2px; + } + .bookCoverPreviewWrap { + width: 100%; + overflow-x: auto; + padding: 20px 0; + display: flex; justify-content: center; + } + .bookCoverPreview { + display: flex; + align-items: stretch; + box-shadow: 0 1px 3px rgba(0,0,0,.08); + } + .bookCoverPanel { + position: relative; + box-sizing: border-box; + border: 1px solid var(--line); + background: #ffffff; + display: flex; align-items: center; justify-content: center; + } + .bookCoverPanel + .bookCoverPanel { border-left: none; } + .bookCoverPanel.back { background: rgba(125,211,252,.10); } + .bookCoverPanel.spine { background: rgba(192,132,252,.12); } + .bookCoverPanel.front { background: rgba(167,243,208,.12); } + .bookCoverPanelLabel { + font-size: .72rem; color: var(--muted); + text-transform: uppercase; letter-spacing: .08em; + writing-mode: horizontal-tb; + text-align: center; padding: 6px; + } + .bookCoverPanel.spine .bookCoverPanelLabel { + writing-mode: vertical-rl; + transform: rotate(180deg); + } + .bookCoverPanelDim { + position: absolute; bottom: 4px; left: 50%; + transform: translateX(-50%); + font-size: .68rem; color: var(--muted); + white-space: nowrap; + } + .bookCoverPanel.spine .bookCoverPanelDim { + bottom: auto; top: 50%; left: auto; right: 4px; + transform: translateY(-50%) rotate(-90deg); + transform-origin: right center; + } + .templateLayerPanels { display: grid; gap: 10px; @@ -3001,8 +3629,8 @@
- - + +
@@ -3021,16 +3649,17 @@
- Layout + Layout ⓘ
- - - + + + +
- Template + Template ⓘ
+ Add Media Identifiers @@ -3108,14 +3737,14 @@
- Display options + Display options ⓘ
@@ -3134,7 +3763,7 @@
- Print & Save + Print & Save ⓘ
@@ -3165,7 +3794,7 @@
- Canvas + Canvas ⓘ
@@ -3192,7 +3821,7 @@
- Slides + Slides ⓘ
@@ -3209,8 +3838,26 @@

Click a slide thumbnail to edit it. Drag thumbnails to reorder.

+
+ Book ⓘ +

A book combines a cover spread (front + spine + back), an ordered list of designed pages, optional reflowable chapters written as NIP-23 markdown, and master pages for shared running heads and page numbers. See docs/BOOK_DESIGNER_FEATURE.md for the full spec.

+
+
+ + + +
+

+
+ Publish visibility: + + +
+

Document settings, Pages overview, Cover view, and PDF export ship in subsequent phases. This entry is the Phase A2 skeleton.

+
+
- Deck theme + Deck theme ⓘ

A deck theme restyles every slide in the thumbnails and Presenter Mode — the slides themselves are never changed.

@@ -3222,7 +3869,7 @@
- Artwork + Artwork ⓘ @@ -3416,7 +4063,7 @@
- Layers + Layers ⓘ
@@ -3797,7 +4444,7 @@
- Project metadata + Project metadata ⓘ
@@ -3841,7 +4488,7 @@
- Save & Export + Save & Export ⓘ
@@ -3862,6 +4509,7 @@
@@ -3993,12 +4641,78 @@
0 slides
+
+
+
+ + + + ⓘ + + Markdown autosaves to this book. Phase D1 publishes as a NIP-23 article. +
+
+ Local only + + + + + + + + +
+
+ +
+
+
+
+
+
+
+ + + ⓘ + + 0 pages · 0 reflow +
+
+
+
+ + + + + + + +
+
+
+

Click a designed-page tile to edit it on the canvas. Click a chapter tile to edit its markdown. PDF export is raster-only in v1 (real-text mode for embeddable fonts lands in a follow-up slice).

+
+ +
+
+ ⓘ
@@ -4120,6 +4834,18 @@
+
@@ -4146,7 +4872,7 @@
- ⚡ Lightning wallet + ⚡ Lightning wallet ⓘ

Connect a NIP-47 wallet to unlock premium templates. Recommended: CoinOS — free, web-based, no install.

    @@ -4714,7 +5440,10 @@ + + + + + + + + + +