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:
thePR0M3TH3AN committed 2026-06-04 13:45:30 -04:00
commit 0f18580178
44 files changed
+9098 -56

No files matched your search

+50
View File
@@ -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,
+376
View File
@@ -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`.
+60
View File
@@ -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.
+111
View File
@@ -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.
+49
View File
@@ -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.
+57
View File
@@ -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.
+64
View File
@@ -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.
+43
View File
@@ -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.
+43
View File
@@ -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.
+47
View File
@@ -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.
+49
View File
@@ -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.
+39
View File
@@ -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.
+98
View File
@@ -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.
+46
View File
@@ -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.
+47
View File
@@ -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.
+83
View File
@@ -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.
+49
View File
@@ -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.
+83
View File
@@ -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.
+59
View File
@@ -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.
+29
View File
@@ -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.
+45
View File
@@ -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.
+57
View File
@@ -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.
+101
View File
@@ -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.
+71
View File
@@ -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.
+79
View File
@@ -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.
+66
View File
@@ -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.
+45
View File
@@ -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.
+65
View File
@@ -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.
+54
View File
@@ -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.
+46
View File
@@ -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.
+32
View File
@@ -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.
+77
View File
@@ -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.
+32
View File
@@ -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.
+39
View File
@@ -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.
+45
View File
@@ -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).
+60
View File
@@ -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.
+60
View File
@@ -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.
+93
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+6
View File
@@ -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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},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
+16
View File
@@ -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