mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-10-27 08:40:39 +00:00
Merge book-designer: Book Designer feature + in-app docs + contextual help icons
67 commits delivering the Book Designer arc (phases A-I) plus the in-app 'Read the docs' markdown viewer (30 pages across 5 groups) and ~48 contextual ⓘ help icons knitted into tabs, layouts, panels, tool sections, the pixel-art UI, welcome / community / tip modals.
This commit is contained in:
commit
0f18580178
44 files changed
+9098
-56
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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 `<details>` 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:<id>`. 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:<slug>` or
|
||||
`book-chapter:<entry-id>`. 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
|
||||
<iframe
|
||||
src="https://your-host/#/embed/<naddr-here>"
|
||||
width="600" height="900"></iframe>
|
||||
```
|
||||
|
||||
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 `<name>-interior.pdf` + `<name>-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`.
|
||||
@@ -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.
|
||||
@@ -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:<id>` 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:<entry-id>` |
|
||||
| Master | kind-30078 `casewrap-page` (role: 'master') | `book-master:<master-id>` |
|
||||
| Cover | kind-30078 `casewrap-page` (role: 'cover-spread') | `book-cover:<title-slug>` |
|
||||
| Chapter (public) | kind-30023 NIP-23 article | `chapter:<title-slug>` |
|
||||
| Chapter (private) | kind-30078 encrypted envelope | `book-chapter:<entry-id>` |
|
||||
| Manifest | kind-30078 `casewrap-book` | `book:<title-slug>` |
|
||||
|
||||
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=<naddr>` (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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
<iframe src="…/#/embed/<naddr>" width="1280" height="720"></iframe>
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 <https://coinos.io>.
|
||||
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.
|
||||
@@ -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', <contributor-pubkey>, <relay-hint>, 'design-contributor']`
|
||||
- `['link', '<kind>:<pubkey>:<dTag>', <relay-hint>]`
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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:<id>`
|
||||
(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 `<mode>:<imdbId-or-title>`; 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.
|
||||
@@ -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 `<inches> × 300` before
|
||||
exporting.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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-<mode>']` — discovery.
|
||||
- `['title', …]`, `['category', …]` — public metadata.
|
||||
- `['premium', 'true']`.
|
||||
- `['zap-gate', 'true']`, `['zap-min', '<sats>']`, `['zap-split', 'required']`.
|
||||
- `['zap', <creator-pubkey>, <relay>, '<weight>']` — creator share.
|
||||
- `['zap', <platform-pubkey>, <relay>, '<weight>']` — 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.
|
||||
@@ -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-<base36>`) 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.
|
||||
@@ -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', '<mode>:<id-or-title>']` — the addressable identifier.
|
||||
Replaceable per NIP-33: re-publishing under the same d-tag
|
||||
replaces the event in place.
|
||||
- `['t', 'casewrap']` + `['t', '<mode-specific>']` (e.g.
|
||||
`casewrap-cover`, `casewrap-deck`, `casewrap-book`).
|
||||
- `['title', '<your 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', '<contributor>', …,
|
||||
'design-contributor']` + `['link', '<kind:pubkey:dTag>', …]`.
|
||||
- `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: `<host>/#/share/<naddr>` 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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: <https://coinos.io>.
|
||||
2. Edit your Nostr kind-0 profile to add `lud16` = your LN address
|
||||
(e.g. `[email protected]`). 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.
|
||||
+6524
-56
File diff suppressed because it is too large.
Load diff
+1
@@ -0,0 +1 @@
|
||||
!function(t){"object"==typeof exports&&"object"==typeof module?t(require("../../lib/codemirror"),require("../xml/xml"),require("../meta")):"function"==typeof define&&define.amd?define(["../../lib/codemirror","../xml/xml","../meta"],t):t(CodeMirror)}(function(w){"use strict";w.defineMode("markdown",function(m,d){var g=w.getMode(m,"text/html"),u="null"==g.name;void 0===d.highlightFormatting&&(d.highlightFormatting=!1),void 0===d.maxBlockquoteDepth&&(d.maxBlockquoteDepth=0),void 0===d.taskLists&&(d.taskLists=!1),void 0===d.strikethrough&&(d.strikethrough=!1),void 0===d.emoji&&(d.emoji=!1),void 0===d.fencedCodeBlockHighlighting&&(d.fencedCodeBlockHighlighting=!0),void 0===d.fencedCodeBlockDefaultMode&&(d.fencedCodeBlockDefaultMode="text/plain"),void 0===d.xml&&(d.xml=!0),void 0===d.tokenTypeOverrides&&(d.tokenTypeOverrides={});var t,c={header:"header",code:"comment",quote:"quote",list1:"variable-2",list2:"variable-3",list3:"keyword",hr:"hr",image:"image",imageAltText:"image-alt-text",imageMarker:"image-marker",formatting:"formatting",linkInline:"link",linkEmail:"link",linkText:"link",linkHref:"string",em:"em",strong:"strong",strikethrough:"strikethrough",emoji:"builtin"};for(t in c)c.hasOwnProperty(t)&&d.tokenTypeOverrides[t]&&(c[t]=d.tokenTypeOverrides[t]);var f=/^([*\-_])(?:\s*\1){2,}\s*$/,k=/^(?:[*\-+]|^[0-9]+([.)]))\s+/,F=/^\[(x| )\](?=\s)/i,D=d.allowAtxHeaderWithoutSpace?/^(#+)/:/^(#+)(?: |$)/,p=/^ {0,3}(?:\={1,}|-{2,})\s*$/,i=/^[^#!\[\]*_\\<>` "'(~:]+/,E=/^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/,x=/^\s*\[[^\]]+?\]:.*$/,A=/[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/;function C(t,e,i){return(e.f=e.inline=i)(t,e)}function S(t,e,i){return(e.f=e.block=i)(t,e)}function n(t){var e;return t.linkTitle=!1,t.linkHref=!1,t.linkText=!1,t.em=!1,t.strong=!1,t.strikethrough=!1,t.quote=0,t.indentedCode=!1,t.f==v&&(u?u:"xml"==(e=w.innerMode(g,t.htmlState)).mode.name&&null===e.state.tagStart&&!e.state.context&&e.state.tokenize.isInText)&&(t.f=T,t.block=a,t.htmlState=null),t.trailingSpace=0,t.trailingSpaceNewLine=!1,t.prevLine=t.thisLine,t.thisLine={stream:null},null}function a(t,e){var i=t.column()===e.indentation,n=!(n=e.prevLine.stream)||!/\S/.test(n.string),u=e.indentedCode,a=e.prevLine.hr,r=!1!==e.list,o=(e.listStack[e.listStack.length-1]||0)+3,l=(e.indentedCode=!1,e.indentation);if(null===e.indentationDiff&&(e.indentationDiff=e.indentation,r)){for(e.list=null;l<e.listStack[e.listStack.length-1];)e.listStack.pop(),e.listStack.length?e.indentation=e.listStack[e.listStack.length-1]:e.list=!1;!1!==e.list&&(e.indentationDiff=l-e.listStack[e.listStack.length-1])}var h,s=!(n||a||e.prevLine.header||r&&u||e.prevLine.fencedCodeEnd),a=(!1===e.list||a||n)&&e.indentation<=o&&t.match(f),g=null;return 4<=e.indentationDiff&&(u||e.prevLine.fencedCodeEnd||e.prevLine.header||n)?(t.skipToEnd(),e.indentedCode=!0,c.code):t.eatSpace()?null:i&&e.indentation<=o&&(g=t.match(D))&&g[1].length<=6?(e.quote=0,e.header=g[1].length,e.thisLine.header=!0,d.highlightFormatting&&(e.formatting="header"),e.f=e.inline,L(e)):e.indentation<=o&&t.eat(">")?(e.quote=i?1:e.quote+1,d.highlightFormatting&&(e.formatting="quote"),t.eatSpace(),L(e)):!a&&!e.setext&&i&&e.indentation<=o&&(g=t.match(k))?(u=g[1]?"ol":"ul",e.indentation=l+t.current().length,e.list=!0,e.quote=0,e.listStack.push(e.indentation),e.em=!1,e.strong=!1,e.code=!1,e.strikethrough=!1,d.taskLists&Line truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
.CodeMirror{font-family:monospace;height:300px;color:#000;direction:ltr}.CodeMirror-lines{padding:4px 0}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-fat-cursor .CodeMirror-line::selection,.cm-fat-cursor .CodeMirror-line>span::selection,.cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.cm-fat-cursor .CodeMirror-line::-moz-selection,.cm-fat-cursor .CodeMirror-line>span::-moz-selection,.cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.cm-fat-cursor{caret-color:transparent}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-rulers{position:absolute;left:0;right:0;top:-50px;bottom:0;overflow:hidden}.CodeMirror-ruler{border-left:1px solid #ccc;top:0;bottom:0;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-type,.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-s-default .cm-error{color:red}.cm-invalidchar{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;height:100%;outline:0;position:relative;z-index:0}.CodeMirror-sizer{position:relative;border-right:50px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none;outline:0}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-50px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper ::selection{background-color:transparent}.CodeMirror-gutter-wrapper ::-moz-selection{background-color:transparent}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre.CodeMirror-line,.CodeMirror pre.CodeMirror-line-like{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual}.CodeMirror-wrap pre.CodeMirror-line,.CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;padding:.1px}.CodeMirror-rtl pre{direction:rtl}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:contentLine truncated
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).CodeMirror=t()}(this,function(){"use strict";var e=navigator.userAgent,l=navigator.platform,d=/gecko\/\d/i.test(e),s=/MSIE \d/.test(e),a=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(e),u=/Edge\/(\d+)/.exec(e),w=s||a||u,v=w&&(s?document.documentMode||6:+(u||a)[1]),x=!u&&/WebKit\//.test(e),s=x&&/Qt\/\d+\.\d+/.test(e),m=!u&&/Chrome\/(\d+)/.exec(e),V=m&&+m[1],K=/Opera\//.test(e),j=/Apple Computer/.test(navigator.vendor),c=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(e),X=/PhantomJS/.test(e),Y=j&&(/Mobile\/\w+/.test(e)||2<navigator.maxTouchPoints),$=/Android/.test(e),_=Y||$||/webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(e),C=Y||/Mac/.test(l),q=/\bCrOS\b/.test(e),a=/win/i.test(l),u=K&&e.match(/Version\/(\d*\.\d*)/),Z=((u=u&&Number(u[1]))&&15<=u&&(x=!(K=!1)),C&&(s||K&&(null==u||u<12.11))),Q=d||w&&9<=v;function J(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}var ee=function(e,t){var n,r=e.className,t=J(t).exec(r);t&&(n=r.slice(t.index+t[0].length),e.className=r.slice(0,t.index)+(n?t[1]+n:""))};function te(e){for(var t=e.childNodes.length;0<t;--t)e.removeChild(e.firstChild);return e}function y(e,t){return te(e).appendChild(t)}function M(e,t,n,r){var i=document.createElement(e);if(n&&(i.className=n),r&&(i.style.cssText=r),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o<t.length;++o)i.appendChild(t[o]);return i}function ne(e,t,n,r){e=M(e,t,n,r);return e.setAttribute("role","presentation"),e}function re(e,t){if(3==t.nodeType&&(t=t.parentNode),e.contains)return e.contains(t);do{if((t=11==t.nodeType?t.host:t)==e)return!0}while(t=t.parentNode)}function N(e){var t,n=e.ownerDocument||e;try{t=e.activeElement}catch(e){t=n.body||null}for(;t&&t.shadowRoot&&t.shadowRoot.activeElement;)t=t.shadowRoot.activeElement;return t}function ie(e,t){var n=e.className;J(t).test(n)||(e.className+=(n?" ":"")+t)}function oe(e,t){for(var n=e.split(" "),r=0;r<n.length;r++)n[r]&&!J(n[r]).test(t)&&(t+=" "+n[r]);return t}var le=document.createRange?function(e,t,n,r){var i=document.createRange();return i.setEnd(r||e,n),i.setStart(e,t),i}:function(e,t,n){var r=document.body.createTextRange();try{r.moveToElementText(e.parentNode)}catch(e){return r}return r.collapse(!0),r.moveEnd("character",n),r.moveStart("character",t),r},se=function(e){e.select()};function ae(e){return e.display.wrapper.ownerDocument}function ue(e){return ce(e.display.wrapper)}function ce(e){return e.getRootNode?e.getRootNode():e.ownerDocument}function he(e){return ae(e).defaultView}function de(e){var t=Array.prototype.slice.call(arguments,1);return function(){return e.apply(null,t)}}function fe(e,t,n){for(var r in t=t||{},e)!e.hasOwnProperty(r)||!1===n&&t.hasOwnProperty(r)||(t[r]=e[r]);return t}function S(e,t,n,r,i){null==t&&-1==(t=e.search(/[^\s\u00a0]/))&&(t=e.length);for(var o=r||0,l=i||0;;){var s=e.indexOf("\t",o);if(s<0||t<=s)return l+(t-o);l=(l+=s-o)+(n-l%n),o=s+1}}Y?se=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:w&&(se=function(e){try{e.select()}catch(e){}});function pe(){this.id=null,this.f=null,this.time=0,this.handler=de(this.onTimeout,this)}function L(e,t){for(var n=0;n<e.length;++n)if(e[n]==t)return n;return-1}pe.prototype.onTimeout=function(e){e.id=0,e.time<=+new Date?e.f():setTimeout(e.handler,e.time-+new Date)};var ge=50,me={toString:function(){return"CodeMirror.Pass"}},ve={scroll:!(pe.prototype.set=function(e,t){this.f=t;t=+new Date+e;(!this.id||t<this.time)&&(clearTimeout(this.id),this.id=setTimeout(this.handler,e),this.time=t)})},ye={origin:"*mouse"},be={origin:"+move"};function we(e,t,n){for(var r=0,i=0;;){var o=e.indexOf("\t",r),l=(o=-1==o?e.length:o)-r;if(o==e.length||t<=i+l)return r+Math.min(l,t-i);if(i+=o-r,r=o+1,t<=(i+=n-i%n))return r}}var xe=[""];function Ce(e){for(;xe.length<=e;)xe.push(z(xe)+" ");return xe[e]}function z(e){return e[e.length-1]}function Se(e,t){for(var n=[],r=0;r<e.length;r++)n[r]=t(e[r],r);return n}function Le(){}function ke(e,t){e=Object.create?Object.create(e):(Le.prototype=e,new Le);return t&&fe(t,e),e}var Te=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;function Me(e){return/\w/.test(e)||""<e&&(e.toUpperCase()!=e.toLowerCase()||Te.test(e))}function Ne(e,t){return t?!!(-1<t.source.indexOf("\\w")&&Me(e))||t.test(e):Me(e)}function Oe(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return;return 1}var Ae=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0acLine truncated
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* marked v14.1.4 - a markdown parser
|
||||
* Copyright (c) 2011-2024, Christopher Jeffrey. (MIT Licensed)
|
||||
* https://github.com/markedjs/marked
|
||||
*/
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s=/[&<>"']/,r=new RegExp(s.source,"g"),i=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,l=new RegExp(i.source,"g"),o={"&":"&","<":"<",">":">",'"':""","'":"'"},a=e=>o[e];function c(e,t){if(t){if(s.test(e))return e.replace(r,a)}else if(i.test(e))return e.replace(l,a);return e}const h=/(^|[^\[])\^/g;function p(e,t){let n="string"==typeof e?e:e.source;t=t||"";const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(h,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}function u(e){try{e=encodeURI(e).replace(/%25/g,"%")}catch{return null}return e}const k={exec:()=>null};function g(e,t){const n=e.replace(/\|/g,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(/ \|/);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(/\\\|/g,"|");return n}function f(e,t,n){const s=e.length;if(0===s)return"";let r=0;for(;r<s;){const i=e.charAt(s-r-1);if(i!==t||n){if(i===t||!n)break;r++}else r++}return e.slice(0,s-r)}function d(e,t,n,s){const r=t.href,i=t.title?c(t.title):null,l=e[1].replace(/\\([\[\]])/g,"$1");if("!"!==e[0].charAt(0)){s.state.inLink=!0;const e={type:"link",raw:n,href:r,title:i,text:l,tokens:s.inlineTokens(l)};return s.state.inLink=!1,e}return{type:"image",raw:n,href:r,title:i,text:c(l)}}class x{options;rules;lexer;constructor(t){this.options=t||e.defaults}space(e){const t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(/^(?: {1,4}| {0,3}\t)/gm,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:f(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t){const n=e.match(/^(\s+)(?:```)/);if(null===n)return t;const s=n[1];return t.split("\n").map((e=>{const t=e.match(/^\s+/);if(null===t)return e;const[n]=t;return n.length>=s.length?e.slice(s.length):e})).join("\n")}(e,t[3]||"");return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(/#$/.test(e)){const t=f(e,"#");this.options.pedantic?e=t.trim():t&&!/ $/.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:f(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=f(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l<e.length;l++)if(/^ {0,3}>/.test(e[l]))i.push(e[l]),t=!0;else{if(t)break;i.push(e[l])}e=e.slice(l);const o=i.join("\n"),a=o.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,"\n $1").replace(/^ {0,3}>[ \t]?/gm,"");n=n?`${n}\n${o}`:o,s=s?`${s}\n${a}`:a;const c=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(a,r,!0),this.lexer.state.top=c,0===e.length)break;const h=r[r.length-1];if("code"===h?.type)break;if("blockquote"===h?.type){const t=h,i=t.raw+"\n"+e.join("\n"),l=this.blockquote(i);r[r.length-1]=l,n=n.substring(0,n.length-t.raw.length)+l.raw,s=s.substring(0,s.length-t.text.length)+l.text;break}if("list"!==h?.type);else{const t=h,i=t.raw+"\n"+e.join("\n"),l=this.list(i);r[r.length-1]=l,n=n.substring(0,n.length-h.raw.length)+l.raw,s=s.substring(0,s.length-t.raw.length)+l.raw,e=i.substring(r[r.length-1].raw.length).split("\n")}}return{type:"blockquote",raw:n,tokens:r,text:s}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim();const s=n.length>1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=new RegExp(`^( {0,3}${n})((?:[\t ][^\\n]*)?(?:\\n|$))`);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(/^\t+/,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(/[^ ]/),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&/^[ \t]*$/.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=new RegExp(`^ {0,${Math.min(3,p-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?
Line truncated
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).PDFLib={})}(this,(function(t){"use strict";
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n])})(t,n)};function n(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}var r=function(){return(r=Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++)for(var i in e=arguments[n])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)};function i(t,e,n,r){return new(n||(n=Promise))((function(i,o){function a(t){try{l(r.next(t))}catch(t){o(t)}}function s(t){try{l(r.throw(t))}catch(t){o(t)}}function l(t){var e;t.done?i(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(a,s)}l((r=r.apply(t,e||[])).next())}))}function o(t,e){var n,r,i,o,a={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function s(o){return function(s){return function(o){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(i=2&o[0]?r.return:o[0]?r.throw||((i=r.return)&&i.call(r),0):r.next)&&!(i=i.call(r,o[1])).done)return i;switch(r=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return a.label++,{value:o[1],done:!1};case 5:a.label++,r=o[1],o=[0];continue;case 7:o=a.ops.pop(),a.trys.pop();continue;default:if(!(i=a.trys,(i=i.length>0&&i[i.length-1])||6!==o[0]&&2!==o[0])){a=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){a.label=o[1];break}if(6===o[0]&&a.label<i[1]){a.label=i[1],i=o;break}if(i&&a.label<i[2]){a.label=i[2],a.ops.push(o);break}i[2]&&a.ops.pop(),a.trys.pop();continue}o=e.call(t,a)}catch(t){o=[6,t],r=0}finally{n=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,s])}}}function a(){for(var t=0,e=0,n=arguments.length;e<n;e++)t+=arguments[e].length;var r=Array(t),i=0;for(e=0;e<n;e++)for(var o=arguments[e],a=0,s=o.length;a<s;a++,i++)r[i]=o[a];return r}for(var s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",l=new Uint8Array(256),h=0;h<s.length;h++)l[s.charCodeAt(h)]=h;var u,c=function(t){for(var e="",n=t.length,r=0;r<n;r+=3)e+=s[t[r]>>2],e+=s[(3&t[r])<<4|t[r+1]>>4],e+=s[(15&t[r+1])<<2|t[r+2]>>6],e+=s[63&t[r+2]];return n%3==2?e=e.substring(0,e.length-1)+"=":n%3==1&&(e=e.substring(0,e.length-2)+"=="),e},d=function(t){var e,n,r,i,o,a=.75*t.length,s=t.length,h=0;"="===t[t.length-1]&&(a--,"="===t[t.length-2]&&a--);var u=new Uint8Array(a);for(e=0;e<s;e+=4)n=l[t.charCodeAt(e)],r=l[t.charCodeAt(e+1)],i=l[t.charCodeAt(e+2)],o=l[t.charCodeAt(e+3)],u[h++]=n<<2|r>>4,u[h++]=(15&r)<<4|i>>2,u[h++]=(3&i)<<6|63&o;return u},f=/^(data)?:?([\w\/\+]+)?;?(charset=[\w-]+|base64)?.*,/i,p=function(t){var e=t.trim(),n=e.substring(0,100).match(f);if(!n)return d(e);var r=n[0],i=e.substring(r.length);return d(i)},g=function(t){return t.charCodeAt(0)},v=function(t){return t.codePointAt(0)},y=function(t,e){return x(t.toString(16),e,"0").toUpperCase()},m=function(t){return y(t,2)},b=function(t){return String.fromCharCode(t)},w=function(t){return b(parseInt(t,16))},x=function(t,e,n){for(var r="",i=0,o=e-t.length;i<o;i++)r+=n;return r+t},k=function(t,e,n){for(var r=t.length,i=0;i<r;i++)e[n++]=t.charCodeAt(i);return r},F=function(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")},S=function(t){return t.replace(/\t|\u0085|\u2028|\u2029/g," ").replace(/[\b\v]/g,"")},C=["\\n","\\f","\\r","\\u000B"],A=function(t){return/^[\n\f\r\u000B]$/.test(t)},T=function(t){return t.split(/[\n\f\r\u000B]/)},P=function(t){return t.replace(/[\n\f\r\u000B]/g," ")},z=function(t,e){var n,r=t.charCodeAt(e),i=e+1,o=1;return r>=55296&&r<=56319&&t.length>i&&(n=t.charCodeAt(i))>=56320&&n<=57343&&(o=2),[t.slice(e,e+o),o]},R=function(t){for(var e=[],n=0,r=t.length;n<r;){var i=z(t,n),o=i[0],a=i[1];e.push(o),n+=a}return e},O=function(t,e,n,r){for(var i=function(t){for(var e=C.join("|"),n=["$"],r=0,i=t.length;r<i;r++){var o=t[r];if(A(o))throw new TypeError("`wordBreak` must not include "+e);n.push(""===o?".":F(o))}var a=n.join("|");return new RegExp("("+e+")|((.*?)("+a+"))","gm")}(e),o=S(t).match(i),a="",s=0,l=[],h=function(){""!==a&&l.push(a),a="",s=0},u=0,c=o.length;u<c;u++){var d=o[u];if(A(d))h();else{var f=r(d);s+f>n&&h(),a+=d,s+=f}}return h(),l},D=/^D:(\d\d\d\d)(\d\d)?(\d\d)?(\d\d)?(\d\d)?(\d\d)?([+\-Z])?(\d\d)?'?(\d\d)?'?$/,B=function(t){var e=t.match(D);if(e){var n=e[1],r=e[2],i=void 0===r?"01":r,o=e[3],a=void 0===o?"01":o,s=e[4],l=void 0===s?"00":s,h=e[5],u=void 0===h?"00":h,c=e[6],d=void 0===c?"00":c,f=e[7],p=void 0===f?"Z":f,g=e[8],v=void 0===g?"00":g,y=e[9];return new Date(n+"-"+i+"-"+a+"T"+l+":"+u+":"+d+("Z"===p?"Z":""+p+v+":"+(void 0===y?"00":y)))}},N=function(t,e){for(var n,r,i=0;i<t.length;){var o=t.substring(i).match(e);if(!o)return{match:r,pos:i};r=o,i+=(null!==(n=o.index)&&void 0Line truncated
|
||||
//# sourceMappingURL=pdf-lib.min.js.map
|
||||
Reference in new issue
Block a user