Commit Graph
128 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.7 3e6e89e16f Add private (encrypted) publishing — Phases 1 & 2
Phase 1 — publish a design encrypted, readable only by the author:
- Crypto helpers: encryptProjectEnvelope / decryptProjectEnvelope —
  AES-256-GCM over gzipped JSON, AES key NIP-44 key-wrapped to the
  author (NIP-04 fallback), via a nostrEncryptCapability() probe. All
  in-browser, no new dependencies.
- publishEncryptedDesign: a feed-invisible kind:30078 event — random
  persistent d-tag, only d / alt / encrypted tags, no casewrap tag.
- Publish-confirm modal gains a Public / Private control with the
  privacy warning and a capability gate; wired into the template and
  disc-design publish paths.
- My Designs decrypts the author's own private events and shows a
  lock badge; buildRowsFromEvents skips any stray encrypted event so it
  can never surface in the public feed.
- state.visibility / state.privateDTag — restored on load, cleared on
  Close project, autosaved.

Phase 2 — flip a published design's visibility in place:
- Owner-only Make Public / Make Private button on the preview page;
  republishDesignVisibility re-publishes under the same d-tag with the
  opposite wrapper (NIP-33 replace), behind a confirm that states the
  public-to-private caveat.

Spec status documented in docs/PRIVATE_PUBLISHING_FEATURE.md.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 13:48:59 -04:00
thePR0M3TH3ANandClaude Opus 4.7 a0ebd9c9e2 Mark editor / tool UX TODO items as shipped
The on-canvas resize handles, text-box-resize behaviour, and expanded
Text tool panel all shipped in 1a517c9; collapse the section to a
shipped note and keep it open for future editor-UX items.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:49:20 -04:00
thePR0M3TH3ANandClaude Opus 4.7 1a517c9552 Add on-canvas resize handles and expand the Text tool panel
- Selected layers (text / shape / image / color / QR) now show 8
  drag-resize handles — 4 corners + 4 edge midpoints. Edge handles are
  single-axis (resize one direction only); corner handles resize toward
  the corner with the opposite corner anchored. Resizing is computed in
  the layer's own rotated frame, so rotated layers behave correctly, and
  the aspect-lock toggle applies to corner handles only.
- Resizing a text box changes only its w/h (the text flow area) — the
  point size is never rescaled.
- Expand #textToolOptions to match the pen/shape panels: font family,
  point size, color, alignment, and bold/italic — sets the default for
  the next text box, and restyles the selected text layer live.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:48:24 -04:00
thePR0M3TH3ANandClaude Opus 4.7 cf006ce1aa Add editor / tool UX improvements to TODO
On-canvas resize handles for all layer types, a constraint that text-box
resize never rescales the point size, and expanding the existing
#textToolOptions panel like the pen-tool panel.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:33:30 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5e44c9e2e4 Draft Pixel Art feature plan with event-size budgeting
Spec for a Nostr-native pixel-art editor: an editable `pixelart` layer
type (palette-indexed + RLE grid stored in JSON, raster as export-only),
a dedicated Pixel Editor canvas-area view, a standalone casewrap-pixelart
Designer-tab design importable into any design as a layer, PNG export,
and a phased path to animation frames + GIF.

Includes a Size limits section: relays cap event size (commonly 64 KB),
so publishing is gated on a measured event-size budget derived from the
relays' NIP-11 max_event_size (min across the user's set, 64 KB
fallback) — soft warning at ~50%, hard block at ~90%, with a live size
readout in the editor. Encoder stores the smaller of RLE / base64 to
bound the worst case; rough size table per grid dimension included.

Also index the spec in TODO.md under drafted-but-unbuilt features.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:29:14 -04:00
thePR0M3TH3ANandClaude Opus 4.7 39f0ed0eb0 Expand Artstr Stacks spec into a HyperCard behaviour model
Replace the thin single-action-per-layer model with real interactive
logic:

- Cards gain onEnter/onExit chains; layers gain an onClick chain. Each is
  an ordered action chain, not a single link.
- v1 action set: goto-card, go-back, open-url, show-layer, hide-layer,
  toggle-layer — reveals, secret panels, multi-step buttons, branching.
- New action-builder surface: a no-JSON panel with reorderable rows,
  id-based target pickers, and authoring warnings.
- Runtime tracks a per-card layer-visibility map (reset on re-entry) and
  executes chains; action overlay regions are focusable buttons.
- Safety: onEnter/onExit cannot navigate (no enter->goto->enter loops);
  chains capped and non-recursive; unknown action types ignored.
- Re-phased A (format) -> B (behaviour authoring) -> C (runtime) ->
  D (route) -> E (stack logic: variables/conditionals, future tier).

Interactivity stays a declarative, interpreted attribute of existing
layers — no new layer types, no HTML/JS, no eval.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-22 07:05:12 -04:00
thePR0M3TH3ANandClaude Opus 4.7 19ee00f14a Tighten Artstr Stacks spec after review pass
- Aspect-ratio changes now re-fit cards via the existing import-letterbox
  remap instead of stranding layers off-canvas; imports target the
  stack's aspect, not a hardcoded 16:9.
- Clarify how the shared deck renderer is fed for stacks (dims from
  stack, background from card) and startCard deletion fallback.
- Viewer: action regions are focusable buttons (Tab/Enter, screen
  readers); empty-space clicks are no-ops; goto-card keywords clamp at
  the ends; touch taps act as clicks; persist the edited card before
  launching; encrypted-stack locked state on the /p/ route.
- Fix the hit-testing risk note (rotation is exact; only clip-masked
  layers fall back to a bounding box) and add an accessibility row.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 22:40:41 -04:00
thePR0M3TH3ANandClaude Opus 4.7 22256182d2 Draft Artstr Stacks (interactive pages) feature plan
Spec for "HyperCard for Nostr" — fullscreen interactive card stacks built
as a focused extension of the Slide Deck: a new templateMode 'stack'
reusing the deck card-container engine, with a per-stack aspect ratio,
optional per-layer actions (goto-card / open-url), transparent hotspots,
and a fullscreen interactive viewer with click routing. Declarative
safe schema only — no HTML/JS. Phased A-D plus a /p/<naddr> viewer route.

Also index both PRIVATE_PUBLISHING_FEATURE.md and STACKS_FEATURE.md under
the drafted-but-unbuilt features in TODO.md.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 22:37:51 -04:00
thePR0M3TH3ANandClaude Opus 4.7 0870ccfa08 Draft private (encrypted) publishing feature plan
Spec for publishing any design to Nostr encrypted, readable only by the
author (Phase 1 "private to me"). Covers the AES-256-GCM + NIP-44/NIP-04
key-wrap envelope, leak-free tagging (no casewrap feed tag, random d-tag),
progressive decryption in My Designs, an explicit threat model, and a
Phase 0 validation spike. Phases 2-4 (public/private toggle, recipients,
NIP-59/Blossom) sketched as future work.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 22:31:21 -04:00
thePR0M3TH3ANandClaude Opus 4.7 9bf99f1abb Add Slide Decks & Presenter Mode (v1: Phases A–D)
Build a Nostr-native presentation system on top of the existing canvas
editor:

- Slide (templateMode 'slide') — a 16:9 Custom Art canvas with speaker
  notes; publishes/forks/loads as casewrap-slide.
- Deck Builder (templateMode 'deck') — a Template-tab container with an
  inline ordered slide list. Slide-sorter view: add blank, import from
  Nostr or file (non-16:9 imports letterboxed), duplicate, delete,
  drag-reorder, inline-rename; edit a slide in the canvas editor with
  write-back. Self-contained save/load/publish/fork as casewrap-deck;
  deck feed-card and publish previews show the first slide + count badge.
- Deck theme — deck-level font + background, render-time composition,
  per-slide ignoreDeckTheme opt-out.
- Presenter Mode — full-screen runtime: themed slide, notes, next
  thumbnail, counter; keyboard/click/on-screen nav; Presenter ⇄ Audience
  view toggle (slide-only full-screen with auto-hiding chrome). Launches
  from the deck tool palette and a deck's community-browser preview.

Supporting changes:
- New categories: slide ("Slide design") and slide-deck ("Slide deck");
  both use the free-form Custom tag field, not media identifiers.
- Swap the Template/Designer tab icons; 50% larger.
- Remove the IMDb/OMDb metadata import (button, handler, omdbApiKey);
  the imdbId field stays.
- Update README, SLIDE_DECK_FEATURE.md status, and TODO.md.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 22:10:06 -04:00
thePR0M3TH3ANandClaude Opus 4.7 06c5ab6768 Draft slide-deck & presenter-mode feature plan
- docs/SLIDE_DECK_FEATURE.md: phased spec for Slide Designer (16:9
  canvas + speaker notes), Deck Builder (embedded ordered slides,
  slide-sorter view, deck theme), and Presenter Mode (single-window
  v1, dual-window deferred to Phase E). Phases A-D with ship gates.
- Select tool icon changed to a rotated arrowhead so it reads as a
  cursor, not a play button (distinct from the planned Present icon).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 14:30:59 -04:00
thePR0M3TH3ANandClaude Opus 4.7 c872b5210d Uniform shape strokes & corners on non-square layers
- Shapes now render into a viewBox whose height tracks the layer's
  aspect ratio (0 0 100 vbH), so the viewBox->layer mapping is a uniform
  scale. Strokes stay even thickness and rounded-rect corners stay
  circular on any aspect — fixes the squished-stroke/corner bug class.
  Applied to both the SVG renderer and the Canvas/PDF/JPEG exporter.
- Clip/mask to a rounded-rect no longer squishes its corners on a
  non-square clipped layer: clipShapeToPathD pre-distorts rx/ry.
- Removed a dead empty if-block in the export clip path.

Square layers render identically to before; circles/polygons still
fill the box (intended bounding-box behavior).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-21 13:47:03 -04:00
thePR0M3TH3ANandClaude Opus 4.7 2298512dce Optional background image, disc-image placement fixes, browser polish
- Publishing no longer requires a background image: cover/jewel/disc
  designs publish with layers-only content. "Save design (JSON)" works
  for layer-only disc designs (removed a silent return + a payload throw).
- Disc background image now lands correctly: renderDesignerDisc() runs
  after the designer view is laid out, and the publish/feed preview uses
  the same cover-pan math as the editor instead of a naive translate(%).
- Publish-modal preview no longer squishes the disc vertically (dropped
  axis-independent max-width/height clamps; fitMiniPreview scales it).
- Template browser: "Fork off!" button renamed to "Use/Fork".

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 22:44:55 -04:00
thePR0M3TH3ANandClaude Opus 4.7 159f60385a Update favicon
Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 22:06:50 -04:00
thePR0M3TH3ANandClaude Opus 4.7 8d07c81667 Support-the-dev publish flow, clip-to-shape fix, rebrand assets
- After a successful publish, a "support the developer" prompt asks if
  the user likes the project and offers to zap the dev — resolves the
  developer's Lightning address from their Nostr profile and hands off
  to the existing tip/zap modal. Shown once per session.
- Fix clip/mask to a custom vector (path) shape: the <clipPath> wrapped
  the path in a <g>, which browsers drop, so path-shape clips silently
  failed. Moved the transform onto the <path> itself.
- A shape used as a clip/mask source now renders no fill or stroke of
  its own (editor, export, and publish preview) so the clipped layer
  shows through cleanly.
- New animated logo (cropped tight, resized), Designer-tab icon, updated
  slogan, GitHub URL, and an artstr.org footer link.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 22:05:55 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ef7d4803f7 Text/Shape/Hand tools, Designer-tab restructure, rename to Artstr Studio
- New left-palette tools: Hand (pan), Shape (drag to draw a primitive),
  Text (click to place). Removed the redundant sidebar add-buttons.
- Pencil strokes now keep a constant on-screen thickness (pixel-space
  viewBox) instead of scaling with stroke length/aspect.
- Restructured tabs: "Disc Designer" -> "Designer", which now hosts a
  Layout panel with Custom Art (default) + Disc Design; Custom Art moved
  off the Template tab. Underlying appMode/save-format unchanged.
- Renamed the app from CaseWrap Studio to Artstr Studio (brand text only;
  Nostr tags and localStorage keys kept for compatibility); smaller
  one-line logo wordmark.
- README updated for the new tabs, tools, and keyboard shortcuts.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 15:30:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 183e3b16d3 Unified canvas workspace, docked corner widget, feed flicker fix
- Canvas area is now a docked grid: full-height left tool palette and
  right options panel, full-width bottom zoom bar, all flush and
  sharp-edged with the scrollbar above the zoom bar.
- Nostr login widget docked flush into the top-right corner to match.
- Thinner scrollbars throughout (~half the default).
- Template browser no longer flashes while assets stream: feed
  re-renders are throttled (the FEED_PROGRESS_DEBOUNCE_MS path that
  was declared but never wired up).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 11:16:37 -04:00
thePR0M3TH3ANandClaude Opus 4.7 16c1577af4 Pen & pencil tools, unified vector editing, sidebar polish
- Pen/pencil tools (Phases A-E): left tool palette, contextual options
  panel, freehand pencil strokes, Bézier pen paths, anchor/handle editing,
  per-layer hidden flag with eye toggle in the layer list.
- Unified vector editing: the pen panel edits any selected shape's
  fill/stroke; a "Make editable path" action converts primitives and
  single-path SVGs into editable pen paths (SVG d-string parser + arcs).
- Stroke no longer clips at the shape's bounding box (overflow:visible).
- Toolbars anchored outside the scroll area so they stay locked on screen.
- Keyboard shortcuts: Delete, arrow-nudge, [/], Ctrl+D, Ctrl+C/X/V.
- Collapsible sidebar panels, collapsed by default (Layout stays open);
  selecting a layer auto-opens the Layers panel.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-20 10:32:28 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ee0052847e Draft pen & pencil tool feature plan
docs/PEN_TOOL_FEATURE.md: pen and pencil tools that author
shape.kind:'path' layers rather than a new raster type, so drawn
paths inherit the existing fill / gradient / stroke / clip / opacity
/ transform / undo / export / Nostr plumbing for free.

Decisions locked with the project owner:
- Pencil strokes are raw polylines, no smoothing (early-web look).
- Only pen paths are node-editable; pencil paths are not.
- A closed pen path auto-fills with a solid accent color on close.
- Pen + pencil work in Disc Designer mode too — the overlay attaches
  to #designerDiscWrap and coordinate capture branches on
  isDesignerLayer, mirroring startLayerDrag.

Plan: a state.editorTool tool-mode system (select / pen / pencil), a
left floating tool palette and a right contextual options toolbar
matching the zoom-toolbar styling, a pointer-capturing pen overlay,
shape.pen.nodes metadata for re-editable pen paths, and a new
layer.hidden flag for mask-source shapes. Five phases: A tool-mode +
toolbars, B pencil, C pen create, D pen node editing, E visibility
flag + mask polish.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 22:20:45 -04:00
thePR0M3TH3ANandClaude Opus 4.7 162da4b9e4 QR code layers, combined-wrap panel, profile-close fix, TODO
QR code layer (Phase A):
- New `qr` layer type. Add QR button alongside Image / Text / Color /
  Shape in every layer panel. Spawns 1:1 with a placeholder URL.
- Controls: data textarea, error-correction level (L/M/Q/H), quiet
  zone, module + background colors, transparent-background toggle.
- Renders as crisp SVG on the editor and all five preview surfaces
  (dark modules merged into one path); canvas/PDF/JPEG export draws
  it cell-by-cell. Round-trips through JSON and Nostr publish/fork;
  old clients skip the unknown type. Works with drag / resize /
  rotate / opacity / aspect-lock / clip / clipboard / undo-redo.

Combined-wrap layer panel:
- In cover mode with Artwork mode "One combined wrap image", the
  layer panel collapses the separate Back / Spine / Front panels
  into a single "Cover trim area" panel — the wrap is one open
  canvas, so the sub-regions aren't meaningful. The combined panel
  still lists any pre-existing back/spine/front layers via a
  matchKeys set so they aren't orphaned from the UI.

Profile modal close fix:
- closeProfilePage used history.back(), which navigated the browser
  out of the app entirely when the profile was cold-loaded from a
  shared /u/<npub> link (that entry being first in the tab's
  history). Switched to history.replaceState to "/" — cleans the
  URL in place without navigating or firing popstate. Matches how
  closeNostrPreview already handles the /share/ URL.

Docs:
- Add TODO.md tracking deferred work: QR Phase B/C, IMDb-import
  removal, profile-page polish, edit-flow polish, and the unbuilt
  badges / collections / zap-gated / NWC-zaps features.
- Remove docs/UNDO_REDO_FEATURE.md and docs/QR_CODE_LAYER_FEATURE.md
  (shipped) and docs/TMDB_AUTOFILL_FEATURE.md (declined).
- README: document QR layers, undo/redo, fit-to-view + zoom toolbar,
  and the off-canvas veil; refresh the schema and docs references.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 22:09:15 -04:00
thePR0M3TH3ANandClaude Opus 4.7 d0ba9587bc Undo/redo, canvas-fit zoom toolbar, off-canvas veil, scroll-into-view
Undo / redo (Phases A-C per docs/UNDO_REDO_FEATURE.md):
- Memory-only stack of structuredClone'd state snapshots, capped at
  50 entries. UNDO_SNAPSHOT_FIELDS lists exactly which editor-state
  fields participate (appMode, templateMode, savedMetaByMode, meta,
  cover dimensions + artwork, discs / discMeta / spine labels, jewel,
  customArt, discDesign, layers, selectedLayerId, editing-in-place
  context, display toggles). Community / cache / clipboard state is
  intentionally excluded.
- pushHistory captures pre-change state; pushHistoryDebounced
  collapses rapid edits (slider drags, typing, color-picker scrubs)
  into a single history entry per debounce window. _isRestoring
  guard prevents render-driven re-pushes during restore.
- Restore re-runs the sync/render pipeline so input fields, mode
  tabs, and the editing banner all reflect the rolled-back state.
- Undo / Redo buttons in the topbar with disabled state, snapshot
  label hints in the title attribute, and Ctrl+Z / Ctrl+Shift+Z /
  Ctrl+Y keyboard shortcuts that defer to the browser's native
  text-input undo whenever an editable element has focus.
- Push triggers wired across layer lifecycle (add / delete /
  duplicate / cut / paste / paste-all), drag-end on pointerup, every
  shape paint property (fill, gradient stops / angle / center /
  radius, stroke type / color / width / dash, kind-specific params,
  per-element SVG fill / stroke / stroke-width), clip kind /
  inline-shape params / layer-ref selection, custom-path / SVG
  upload modal confirm in create / edit / edit-clip modes, mode
  switches (Template <-> Disc Designer plus cover / disc / jewel /
  customart), close project / clear images / load project / fork /
  enter and exit edit mode, slot artwork loads + transform inputs
  (cover slots, combined wrap, disc design, jewel inserts), spine
  labels, metadata text fields (debounced) and category / language
  selects, Custom Art canvas preset / size / background, dimension
  inputs (panelW / trimH / spineW / bleed / safe), preset selector,
  layer reorder (forward / back), and the display-options toggles.

Canvas zoom + scroll toolbar:
- New always-visible toolbar below the preview area with zoom-out,
  zoom slider (20-200%), zoom-in, percent readout, Fit, and 1:1.
  Sits as a non-scrolling sibling at the bottom of main's flex
  column so it stays on screen regardless of canvas scroll.
- Default zoomAuto = true. computeFitZoom reads previewShell client
  size and picks a percent that lands the sheet inside the viewport
  with ~32 px breathing room. Window resize recomputes if auto-fit
  is still active. Touching any zoom control turns auto-fit off;
  hitting Fit turns it back on.
- main is now a flex column with max-height: 100vh; previewShell
  flex: 1 + overflow: auto + min-height: 0. The previous
  min-height: calc(100vh - 112px) is gone since flex sizes correctly.

Off-canvas veil:
- A transparent .canvasVeil child inside .sheet with inset:0 and a
  giant 9999px box-shadow at 62% preview-gray fills everything
  outside the artboard bounds. Layer content extending past the
  edge sits visually under the shadow so it looks faded, making
  the canvas edges crystal clear. Dark mode uses a dark-tint
  shadow. Print / JPEG export / export-print mode all hide the
  veil so saved output preserves bleed and off-canvas content.

Selected-layer scroll-into-view:
- Clicking a layer on the canvas now scrolls the sidebar's
  "Selected layer" panel into view (block: 'nearest', so it
  no-ops when already visible). Sidebar list clicks don't
  trigger the scroll since the panel is already in the user's
  view.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 16:13:42 -04:00
thePR0M3TH3ANandClaude Opus 4.7 412f932b9f Harden undo/redo feature plan for current scope
Rewrite docs/UNDO_REDO_FEATURE.md to match what the codebase actually
contains today. Explicit snapshotted-vs-not field list covering
savedMetaByMode, customArt canvas, editing-in-place context, the full
layer schema with shape/fill/stroke/clip sub-objects, and discMeta /
spine labels. Trigger list organized by category (layer lifecycle,
geometry, paint, SVG per-element, clip, mode/structural, artwork
loads, metadata, custom-art canvas) with debounce windows per field
type. Switches storage from localStorage to memory-only for v1 to
avoid quota issues with our richer state. Adds an _isRestoring guard,
focused-input bypass for the keyboard shortcut, and 11 acceptance
criteria covering cross-mode meta swap, edit-in-place, clipboard
interaction, and the stack cap. Four implementation slices.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 15:12:39 -04:00
thePR0M3TH3ANandClaude Opus 4.7 a02c562a9f Custom Art metadata simplification, JPEG mask fix, README rewrite
Custom Art project metadata:
- Hide the "Add Media Identifiers" panel when in Custom Art mode —
  IMDb / UPC / TheTVDB / MusicBrainz don't fit wallpaper / banner /
  logo work.
- Show a single optional Custom tag field in its place. Stored as
  state.meta.customTag, round-trips through save/load, and emits to
  Nostr as both an NIP-73-style 'i custom:<tag>' identifier and a
  topical 't <tag>' tag (lowercased, hyphenated, capped at 64 chars).
- Hide the IMDb import button + hint in Custom Art mode.
- Auto-select the "Custom art" category the first time the user
  enters customart mode without one set.

JPEG export mask fix:
- body.export-print > *:not(#exportPrintContainer) { display: none }
  was hiding the global SVG <defs> container that holds <clipPath>
  definitions for layer masks. With the defs invisible, every
  clip-path: url(#…) reference resolved to nothing during print,
  so the PDF that the JPEG flow re-rasterizes had unclipped layers.
  Carve #globalClipDefs out of the exclusion selector and force
  it to zero-size + overflow:hidden so it stays in the DOM but
  takes no space. Direct "Print / Save PDF" was unaffected because
  it doesn't use the export-print mode.

README rewrite:
- Brings the README in line with what the app actually does today:
  Custom Art mode, vector shape primitives + gradients + strokes +
  custom SVG upload with per-element editor, clipping/masking,
  layer clipboard, profile pages at /u/:npub, Lightning tips with
  WebLN auto-pay + NIP-57 zap tracking + Zapped tab, edit-in-place
  via NIP-33 replaceable semantics, the publish-confirm modal, the
  current schema version (5 / min 4), and the vendored qrcode lib.

Remove docs/VECTOR_SHAPES_FEATURE.md — the feature shipped.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 14:37:36 -04:00
thePR0M3TH3ANandClaude Opus 4.7 861e17e3f9 Vector shapes (Phases A-E) + project polish
Vector shapes feature (per docs/VECTOR_SHAPES_FEATURE.md):

Phase A - Shape primitive layers (rect, rounded-rect, circle, ellipse,
triangle, polygon, star, line) with a 0-100 viewBox coord system that
renders on all six surfaces (editor canvas + five preview renderers)
plus Canvas2D export. Solid fill, kind-specific controls (corner
radius, sides, star points, inner ratio, line stroke width). Layer
data is purely additive in JSON; old clients skip unknown shape kinds.

Phase B - Linear + radial gradient fills with two-stop pickers. SVG
emits via <defs><linearGradient>/<radialGradient>; canvas via
createLinearGradient/createRadialGradient. Angles follow CSS
linear-gradient convention. Switching fill types preserves the most
relevant color.

Phase C - Solid strokes on all non-line shapes. Color, width (in
shape-local viewBox units), dash style (solid / dashed / dotted) with
auto-scaled dash patterns. SVG emits stroke attributes; canvas uses
strokeStyle + setLineDash.

Phase D - Custom SVG / full-SVG layer via file upload:
- File picker (.svg, image/svg+xml) replaces a paste textarea.
- Detects single <path d=…> vs full <svg>…</svg>; single paths land as
  kind:'path' with editable fill/stroke, multi-element SVGs land as
  kind:'svg' with fills baked in.
- Sanitizer strips <script>, <foreignObject>, <iframe>, <embed>,
  <animate>, <image>, <a>, all on* event handlers, external
  hrefs/xlink:hrefs, and javascript:/external-url style entries.
- Canvas export serializes the sanitized inner into a blob URL, loads
  via Image, drawImage.
- Per-element editor: a list inside the shape panel exposes every
  paintable child (path / rect / circle / ellipse / polygon /
  polyline / line) with fill + stroke color pickers and a stroke-width
  input. Auto-applies a viewBox-relative default stroke width when
  stroke is first set so strokes are visible at any zoom. Layer-level
  fill / stroke panels are hidden for SVG imports (per-element is the
  canonical control).

Phase E - Clip / mask on any layer type:
- New optional `clip` field with two flavors: inline-shape (clip to a
  primitive defined inline) and layer-ref (clip to another shape
  layer's geometry in the same target).
- Clip source dropdown in the selected-layer panel offers rect /
  rounded-rect / circle / ellipse / triangle / polygon / star / custom
  path / From another layer. Kind-specific param inputs show on demand.
- DOM rendering uses a shared hidden SVG defs container with
  <clipPath clipPathUnits="objectBoundingBox"> entries, referenced via
  clip-path: url(#clip-<layerId>) on each layer wrapper.
- Canvas export builds a Path2D with a DOMMatrix transform mapping
  0-100 coords to the layer's pixel bounds, then ctx.clip() before
  drawing the layer contents.
- Cycle detection: if A clips to B and B clips back to A, both fall
  back to unclipped instead of looping.
- Single-path-SVG clips reuse the upload modal in an edit-clip flow
  that rejects multi-element SVGs with an inline error.

Phase A bonus polish:
- Shapes spawn 1:1 (min(bounds.w, bounds.h) * 0.4).
- Session-wide aspect-ratio lock (default on) with a 🔒/🔓 toggle
  between the W/H inputs in the selected-layer panel. Typing in one
  dimension updates the other proportionally on every layer type.

Per-mode metadata isolation:
- state.savedMetaByMode parks Template-mode and Disc-Designer-mode
  metadata separately. swapMetaForAppMode runs before any appMode
  flip so loading a template in disc designer doesn't overwrite the
  designer's title / category / identifiers. omdbApiKey stays sticky
  across modes as a per-user preference.

Strict per-sub-mode layout isolation:
- isLayerForCurrentMode no longer treats target='page' as a universal
  wildcard. Each layer belongs to exactly one sub-mode by its target.
- Removed the "Whole page" entry from the layer-target dropdown.
- migrateLegacyLayerTargets retargets legacy page/empty layers to the
  current sub-mode's primary target on load. Wired into all four
  mode-specific load paths and the legacy full-project path.
- Fixes the bug where case-cover artwork was rendering misaligned in
  the disc-sheet preview.

Layer clipboard:
- Session-only clipboard supports cross-sub-mode and cross-tool paste
  with retargeting. Copy / Cut on selected layer + Copy all / Paste
  in the panel header. Paste retargets to the current sub-mode's
  primary target and clamps positions so a layer copied from a wide
  cover doesn't land off-canvas in a 4.6" disc.

UI polish:
- Hide unused artwork-mode sidebar controls: cover full-wrap mode
  hides the separate Back / Spine / Front controls and vice versa,
  via the existing body.mode-combined / mode-separate classes.
- Category-aware identifier visibility: only show IMDb / UPC /
  TheTVDB / MusicBrainz fields that fit the chosen category. Custom
  art hides all four.
- Added "Custom art" to the canonical mediaCategories list and
  populated both the template and designer category selects from it
  (the template select was previously hardcoded).
- Selected-layer panel hides entirely when nothing is selected.
- Jewel spine-text details block hides until at least one jewel
  insert has artwork.
- Close project / disc-designer reset / clear images all use a
  shared blankMeta() helper that covers UPC / TheTVDB / MusicBrainz
  (the previous reset path was missing them). Close project also
  resets state.customArt to its defaults so the background color
  doesn't carry over.
- Disc Designer mode now has a proper add-layer panel (Add image
  with URL + Copy / Add text / Add color block / Add shape menu) in
  addition to the layer list.
- Project file loads route through loadProjectFromText regardless of
  which "Load from file" button was used; an info toast announces a
  mode switch when one is needed.

Publish flow:
- New publish-confirm modal renders a live design preview, a
  metadata table (Type / Title / Category / Language / IMDb / UPC /
  TheTVDB / MusicBrainz / Author), and an edit-mode tradeoff banner.
  Replaces the bare confirm(). Errors render inline.
- Removed the legacy "Copy Nostr event" button — only "Copy event ID"
  remains, and it stays hidden until something has been published.

Toast system:
- All 41 native alert() calls swapped for in-app toasts (toastError
  with a red border for failures, toastSuccess with the accent color
  for publish/copy confirmations). Auto-dismiss after a few seconds;
  manual close button on each.

NIP-57 zap tab:
- _feedPagination grew a Zapped entry so the new tab can render
  without crashing the rerender pipeline.
- fetchZapsForEvents now runs chunks in parallel and streams a
  debounced rerender on every ingested receipt, so totals appear
  within a second or two instead of taking up to ~28 s.
- invalidateFeedCache clears the zap cache too so Refresh re-queries
  receipts.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-19 14:10:02 -04:00
thePR0M3TH3ANandClaude Opus 4.7 52dbfae945 Strict layout isolation + per-mode metadata + layer clipboard
Strict layout isolation:
- isLayerForCurrentMode no longer treats target='page' or empty as a
  universal "show everywhere" wildcard. Each layer now belongs to
  exactly one sub-mode (cover / disc-sheet / jewel / customart) by
  its target string.
- Removed "Whole page" from the layer-target dropdown so users can't
  create new layers that leak across sub-modes.
- migrateLegacyLayerTargets retargets legacy page/empty layers to the
  current sub-mode's primary target on load (cover→cover-trim,
  disc→disc-top, jewel→jewel-front, customart→canvas). Wired into all
  four mode-specific load paths and the legacy full-project path.
- Fixes the bug where case-cover artwork was rendering misaligned in
  the disc-sheet preview after switching sub-modes.

Per-mode metadata isolation:
- state.savedMetaByMode parks Template-mode and Disc-Designer-mode
  metadata separately. swapMetaForAppMode runs before any appMode
  flip (user-driven switchAppMode, loadProjectFromText auto-switch,
  importDiscDesignFromPayload's openDesigner path), so loading a
  template file in disc-designer mode no longer overwrites the
  designer's title / category / identifiers.
- omdbApiKey is treated as a per-user preference and stays sticky
  across mode switches.

Layer clipboard:
- Session-only clipboard supports cross-sub-mode and cross-tool
  paste with retargeting. Selected-layer panel gains Copy and Cut
  buttons; the Layers panel grows a Copy all / Paste header row with
  a status hint ("Clipboard: N layers from X").
- Paste retargets every item to the current sub-mode's primary
  target (or designer-disc in Disc Designer) and clamps positions
  so a layer copied from a wide cover doesn't land off-canvas when
  pasted into a 4.6" disc. Each pasted layer gets a fresh id and z;
  the last one becomes the selection.
- Resets on reload — not persisted to localStorage in v1.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 19:55:57 -04:00
thePR0M3TH3ANandClaude Opus 4.7 c5a04423ad Draft vector shapes feature, prune shipped feature docs
VECTOR_SHAPES_FEATURE.md plans a new shape layer type that subsumes
primitives (rect / rounded-rect / circle / ellipse / polygon / star /
line / custom SVG path) under a single discriminated kind, plus
solid / linear-gradient / radial-gradient fills, strokes, and clip
support that any layer type can use. JSON storage is purely additive,
backwards compatible with existing payloads, and forward compatible
for unknown shape kinds. Phased delivery: A primitives + solid fill,
B gradients, C strokes, D custom paths, E masking.

Pruned docs for features that have already shipped in this codebase:

- COLOR_AND_FONT_LAYERS_FEATURE.md — color layers + font picker
- CUSTOM_ART_MODE_FEATURE.md — custom-art template mode
- EDIT_DESIGN_FEATURE.md — replaceable-event edit-in-place
- LIGHTNING_ZAPS_FEATURE.md — LNURL-pay tip flow
- PROFILE_PAGES_FEATURE.md — /u/:npub profile pages
- STREAMING_RELAY_SYNC_FEATURE.md — progressive feed loading
- TEMPLATE_FORKING_FEATURE.md — Fork off! flow
- TRUST_AND_BLOCK_FEATURE.md — block / mute-sync / trust tiers / report
- WOT_SOFT_MODERATION_FEATURE.md — hop-1 mute/report blur
- ZAP_TRACKING_FEATURE.md — NIP-57 zap requests and receipt sums

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 18:47:22 -04:00
thePR0M3TH3ANandClaude Opus 4.7 556d5c5b09 Route both file-load buttons through the smart project loader
Loading a project file picked from the Disc Designer's "Load from
file" button used to call importDiscDesignFromPayload directly, which
silently no-ops on a template payload. Now both the template and
disc-designer file inputs run the JSON through loadProjectFromText,
which detects the payload type and switches state.appMode to the
matching tool when needed.

Also fixes a gap where importDiscDesignFromPayload's openDesigner
branch updated state.appMode but never called syncModeTabsUI, so the
mode-tab indicator stayed stuck on the previous tool after loading a
disc design from template mode.

An info toast announces the switch when one happens, so the user
isn't surprised by the sidebar reconfiguring under them.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 18:39:59 -04:00
thePR0M3TH3ANandClaude Opus 4.7 506a46cc0d Hide sidebar controls that don't apply to the current mode
Artwork mode in the cover designer:
- When set to "One combined wrap image", hide the separate
  Back / Spine / Front slot controls.
- When set to "Separate" mode, hide the combined-wrap details.
- Uses the existing body.mode-combined / mode-separate classes
  that were already being toggled.

Category-aware media identifiers:
- IDENTIFIER_RELEVANCE_BY_CATEGORY maps each category to the IDs
  that make sense for it. Blu-ray / DVD → IMDb + UPC + TheTVDB.
  Games (PS1/PS2/PS3/Xbox/PC/Wii/etc.) → UPC only. CD / Audio CD
  → UPC + MusicBrainz. No category selected falls back to showing
  everything.
- syncIdentifierVisibility runs from syncInputs and on every
  category-field change in both the template and disc-designer
  metadata panels, so loaded / forked / edited designs land in
  the right visibility state too.

Selected-layer panel:
- The "Selected layer" details block now hides entirely when
  nothing is selected instead of sitting in the sidebar as an
  empty widget. The existing per-type sub-panels (image / text /
  color) keep their toggle logic for when a layer is selected.

Jewel spine text:
- The spine-text details block is hidden until at least one of
  jewelInserts.front or jewelInserts.tray has artwork loaded.
  Avoids the "label nothing" affordance for empty templates.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 18:35:27 -04:00
thePR0M3TH3ANandClaude Opus 4.7 40c0df6fee Publish-confirm modal, in-app toasts, Zapped tab fix, dc-designer layers, sticky-meta fix
Publish confirm modal:
- Replaces the bare confirm() before posting to Nostr. Shows a live
  preview of the design (uses the same renderers as the design
  browser), a metadata table (Type, Title, Category, Language,
  IMDb / UPC / TheTVDB / MusicBrainz when filled, Author), and an
  inline edit warning when the user is updating an existing design.
- Pre-flight validation surfaces missing title/category/artwork
  before opening the modal; publish errors render inline so the
  user can retry without losing context.
- ESC / × / backdrop close it; same modal-z-stack treatment as the
  other overlays so it lands on top.

Toast system:
- Replaces every native alert() across the app (41 callsites) with
  in-app toasts. toastError flags failures with a red border;
  toastSuccess paints publish/copy confirmations with the accent
  color. Toasts auto-dismiss (~3.5 s info/success, ~5.5 s error)
  and stack at the bottom-right with a manual close button.

Project metadata stickiness:
- Clear all images, Clear disc, and Clear jewel handlers now also
  reset state.meta (preserving the user's omdbApiKey) and re-sync
  the input fields. Previously, title / IMDb / UPC / TheTVDB /
  MusicBrainz carried over from a cleared project into the next
  one.
- The two existing reset helpers were missing the newer identifier
  fields (UPC, TheTVDB, MusicBrainz). All three call sites now use
  a single blankMeta() helper as the source of truth.

Disc Designer layers panel:
- Designer mode now has the same Add image / Add text / Add color
  controls as the template editor — a single panel since discs are
  one target. URL input + Copy button + the three add buttons.
  Previously the panel showed the layer list but no way to add
  layers in the first place.

Zapped tab fixes:
- _feedPagination was only initialised for New and Popular, so the
  Zapped rerender threw "Cannot set properties of undefined" on
  every paint and the panel never populated. Added the Zapped
  pagination state.
- fetchZapsForEvents now batches chunks in parallel instead of
  sequentially, and streams a debounced rerender on every ingested
  receipt, so zap totals appear within a second or two instead of
  taking up to ~28 s for a 200-design feed.
- invalidateFeedCache now also clears the zap cache (byEvent /
  byAuthor / receiptIds / fetchedFor* sets). Without that, hitting
  Refresh never re-queried receipts because every event id was
  already marked "fetched".

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 17:35:04 -04:00
thePR0M3TH3ANandClaude Opus 4.7 acae6afc62 Add edit-in-place for published designs
Phase A — State, helpers, banner:
- New state.editingDTag / editingKind / editingEventId / editingTitle.
- enterEditMode / exitEditMode / isEditing helpers.
- Yellow banner across the top of the editor reading
  "Editing published design: <title> [Cancel edit]" whenever
  editing context is active.
- Editing state is cleared on close-project, disc-designer reset,
  fresh load from disk, and on successful publish.

Phase B — Publish-flow rewire:
- publishCurrentTemplateToNostr and publishDiscDesignToNostr both
  reuse state.editingDTag when set, so the new kind-30078 event
  shares the (kind, pubkey, d-tag) address of the original and
  supersedes it on relays via NIP-33 replaceable semantics. No
  NIP-09 deletion is needed for the replace itself.
- Publish buttons relabel to "Publish update" in edit mode.
- First-time-per-session confirm explains the tradeoff: zaps and
  reactions tagged to the old event id remain on relays but no
  longer link to this design (totals reset). naddr share links
  keep working; nevent links don't.
- Success message reads "Design updated." in edit mode.

Phase C — UI entry points:
- Owner-only Edit button in buildFeedCard's actions row, so it
  appears on My Designs cards, the profile page grid (own profile),
  and any other surface that uses the shared card factory.
- Owner-only Edit button in the design preview action row, between
  Save JSON and the existing action stack.
- Edit launches startEditFromRow which closes any open community/
  profile modal, loads the design via importDiscDesignFromPayload
  (disc-design mode) or loadProjectFromText (everything else), and
  then enters edit mode with the original d-tag and event id.

Preview header layout fix:
- The nostrPreviewActions wrapper used to be a flex item that
  wrapped its 7 dynamic buttons inside itself, then Refresh / Back
  / Close sat beside it as separate flex items — producing three
  staggered rows. The wrapper now uses display:contents when
  visible (toggled via data-visible attribute) so all preview
  buttons share the same flex container, wrap together, and align
  to the right edge in at most two rows. Buttons in this row also
  get smaller padding and white-space:nowrap to keep them stable.

Self-targeting hidden:
- Block and Report buttons no longer render on feed cards whose
  author is the signed-in user; profile-page chips already had
  the equivalent isSelf gate.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 15:21:50 -04:00
thePR0M3TH3ANandClaude Opus 4.7 b707b42909 Draft edit-published-designs feature plan
Uses NIP-33 parameterized-replaceable semantics: republishing kind-30078
with the same d-tag from the same pubkey supersedes the old event on
compliant relays. No NIP-09 deletion required for the replace itself.

Plan covers state/banner (Phase A), publish-flow rewire (Phase B),
owner-only Edit buttons on My Designs, profile, and preview (Phase C),
and polish (Phase D). Surfaces the honest tradeoff that reactions and
zap receipts orphan to the old event id, while naddr share links keep
working since they're d-tag based.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 14:48:27 -04:00
thePR0M3TH3ANandClaude Opus 4.7 c9cf28fbb9 Add NIP-57 zap tracking with totals, sort, and auto-close
Phase 1 — Sign zap requests:
- When the LNURL endpoint advertises allowsNostr + nostrPubkey and the
  tipper has NIP-07, sign a kind-9734 zap request and pass it as the
  nostr= param to the pay callback. Tags include e (event id), a
  (naddr coords) and p (recipient) for feed-card / preview tips, or
  just p for profile tips.
- Silent fallback to plain LNURL-pay for non-zap endpoints or
  signed-out users. Tips never silently fail.
- Tiny "⚡ Tracked zap" badge in the invoice view when the zap path
  engaged, so QR / copy users know their tip will count.

Phase 2 — Aggregate and display:
- Parse kind-9735 receipts via the embedded description tag (NIP-57
  amount) or a bolt11 amount fallback (with multiplier table).
- _zapCache aggregates by event id and by author pubkey; receipts are
  deduped by event id.
- Feed enrichment phase batch-queries receipts by #e for visible
  designs; profile-page opens query by #p for the author and by #e
  for each design in the grid.
- Feed cards display a "⚡ <total>" badge in the actions row.
- Design preview meta line gets the same badge.
- Profile page header shows "⚡ X sats received" under the npub.

Sort and visibility:
- New "⚡ Zapped" tab in both the Community modal and the welcome
  modal. Sort is pure zap-total with date as the tiebreaker.
- Existing Popular sort now leads with zap totals, falling back to
  reactions, trust, and date.
- isPreviewBroken now flags designs pre-emptively when the preview
  URL is missing (for modes that require artwork) or uses a non-http
  scheme, so broken designs sink to the bottom on first paint
  instead of waiting for an actual image-load failure.
- Broken-image set persists to localStorage so detected breakage
  survives reloads.

Payment detection:
- After a tracked invoice is issued, a background poller queries
  kind-9735 events with #p=recipient + since=now. When a receipt
  arrives whose embedded zap request matches the tipper's pubkey,
  the event id, and the amount (within 10% tolerance), the modal
  shows "✓ Payment received! Closing…" and auto-closes 1.2s later.
  Polling stops after 5 minutes (invoice expiry), on close, or on
  "New amount".
- WebLN auto-pay button appears in the invoice view when
  window.webln is available; one click pays via the extension and
  closes the modal on success. Rejections are handled quietly so
  the QR / copy paths remain available.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 14:46:31 -04:00
thePR0M3TH3ANandClaude Opus 4.7 e33d15f41f Add Lightning tips (LNURL-pay → bolt11 → QR)
A self-contained tip flow that works for any author whose kind-0 profile
publishes a lud16 Lightning address or lud06 LNURL. Picks an amount,
fetches a bolt11 invoice from the LNURL-pay endpoint, renders it as a
scannable QR + copyable string. No NIP-57 zap request, no NWC, no
WebLN — just the smallest path to actually moving sats.

Phase A — LNURL helpers and tip modal:
- Vendored qrcode-generator (~20 KB, MIT) as src/vendor/qrcode.min.js
  so the QR encoder runs offline with no CDN dependency.
- lud16ToLnurlpUrl / lud06ToHttpUrl / resolveLnurlPayUrl pick the right
  endpoint from a profile.
- fetchLnurlPayMetadata validates the payRequest tag and surfaces the
  min/max range; fetchLnurlInvoice requests an amount-locked bolt11.
- renderQrInto auto-scales the typeNumber so long invoices still fit.
- tipModal: amount-chip picker (21 / 100 / 500 / 1k / 5k / 21k / Custom),
  inline min/max validation, fetching state, then an invoice view with
  QR, lightning: deeplink, copyable bolt11, and a New amount / Done row.
- Brought to front via the modal z-stack; ESC, backdrop, and × close it.

Phase B — Feed card surface:
- Every feed card whose author has a resolvable LN address gets a
  ⚡ Tip button in the actions row. No button when there's no address,
  so feeds stay clean.

Phase C — Design preview surface:
- Same conditional ⚡ Tip button in the design preview action row, next
  to Save JSON and Copy share link.

Profile pages now treat the Lightning chip as the entry point — clicking
the chip opens the tip modal instead of copying the address. Profiles
with an address that fails to resolve fall back to the original copy chip.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 13:44:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 e9b38710d6 Add /u/:npub profile pages
Phase A — URL routing and modal skeleton:
- Vercel rewrites for /u/:npub (with hash fallback for static hosts).
- profileModal markup, CSS for banner/avatar/header/chips/designs grid.
- decodeProfileIdentifier handles npub1, nprofile1, and raw hex.
- popstate routing dispatches between /share/:id and /u/:npub.

Phase B — Profile metadata fetch and render:
- ensureProfilePageData with 5-minute TTL cache layered over the
  existing state.community.profiles and _feedCache.profiles caches.
- Renders banner, avatar, display name, NIP-05, npub, about, lud16 /
  decoded lud06 copy chip, website chip.
- Action row: Follow/Unfollow (kind-3 toggle), Block (with confirm),
  Copy npub, Open in nostr client. Self-view shows "This is you".
- toggleProfileFollow added for arbitrary-pubkey follow/unfollow.

Phase C — Designs grid and wiring:
- Streams kind-30078 events by author into the designs grid using the
  existing buildFeedCard + diffWallCards plumbing. Filter dropdown
  reruns the render against the cache.
- Author surfaces across the app (feed-card chip, design preview meta)
  now route to openProfilePage.
- Top-right profile button opens the signed-in user's own profile page
  instead of the Mine tab.

Phase D — Polish:
- ESC closes profile / report / close-project / pdf-jpeg modals.
- Initials avatar fallback with a pubkey-derived hue when picture is
  missing or 404s; same gentle fallback for banner.
- Block-state banner with one-click Unblock on profiles you've blocked.
- Async NIP-05 verification adds a ✓ when /.well-known/nostr.json
  resolves the claim back to the same pubkey.
- Modal z-stack helper bringModalToFront so the design preview opened
  from inside a profile lands on top instead of being hidden behind it.
- Closing a preview that was launched from a profile dismisses the
  community modal too, returning the user to where they came from.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 13:21:30 -04:00
thePR0M3TH3ANandClaude Opus 4.7 97b5b926ea Draft profile pages and Lightning tips feature plans
PROFILE_PAGES_FEATURE.md: URL-routed /u/:npub profile pages with banner,
avatar, kind-0 metadata, NIP-05, npub copy, action row, and a filterable
designs grid. Zaps and tips are explicitly out of scope.

LIGHTNING_ZAPS_FEATURE.md: minimal LNURL-pay → bolt11 → QR tip flow.
No NWC, no NIP-57 zap requests, no zap receipts. Vendors qrcode-generator
for in-browser QR rendering. Lands separately, behind profile pages.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-18 12:49:53 -04:00
thePR0M3TH3ANandClaude Opus 4.7 46025ca7e1 Replace close-project browser confirm with in-app modal
Both Close project buttons (template editor and disc designer) now open a
styled closeProjectModal with three actions: Save and close (primary),
Close without saving (danger), and Cancel. Save errors are shown inline
in the modal rather than triggering another native alert, so the close
flow never falls back to the browser dialog.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-17 21:05:39 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5df58f7a65 Fork Off! polish, sidebar order, flash fix, soft-mod styling, fork dedup
- Rename Use button to "Fork off!" with tooltip; clean customArt state on
  fork to prevent leftover state from previous mode bleeding into the editor.
- Filter layers by current mode in renderLayers via isLayerForCurrentMode so
  fork loads only show layers belonging to the active template type.
- Introduce WORKFLOW_ORDER + renumberWorkflowSteps to fix sidebar panel
  order and visible step numbering across all five modes using CSS order.
- Replace template-browser wipe-and-rebuild with diffWallCards DOM diff so
  cards no longer flash on progressive loads.
- Style Block and Report buttons as .danger; gate Block behind a confirm
  dialog naming the author and pointing to the Blocked authors panel.
- Widen feed cards (minmax 240px) and let action row wrap so Block doesn't
  clip on narrow viewports.
- Add designFingerprint + dedupByFingerprint to collapse identical
  cross-author forks to the earliest post; preserve duplicates in Mine tab.
- Update Custom Art mode doc with shipped status and Phase D wishlist.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-17 20:55:04 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 f67467c517 Custom Art mode Phases B+C: publish, share, browser filter, preview, export
Phase B — Nostr publish + share-link round-trip:
- publishCurrentTemplateToNostr has a customart branch using
  customArtPayload(), 'casewrap-customart' type tag, and a
  'customart:<id>' d-tag (with WxH fallback when no imdb/title).
  Pre-publish guard rejects an empty white canvas with no layers.
- previewFromPayload(payload, 'customart') returns the first
  image-layer src as the thumbnail/broken-image detector target.
- templateTypeForMode returns 'customart' for the customart mode.
- Share link path: shareIdForRow already mode-aware, generates a
  proper NIP-19 naddr for new-style customart publishes.

Preview rendering (needed by share-link round-trip + browser cards):
- New renderCustomArtPreviewDOM(container, payload, opts) — renders
  the canvas at its aspect ratio with the background color, then
  iterates target=canvas layers (sorted by z) with explicit z-index
  values, same pattern as the disc-sheet renderer. Compact mode
  ships a fixed-pixel natural size for fitMiniPreview's transform
  scaling.
- openDesignPreview dispatches to renderCustomArtPreviewDOM for
  mode === 'customart'.
- buildFeedCard's mini-preview dispatch adds customart.

Phase C — Type filter:
- Welcome modal Type select gains "Custom art" option.
- Community modal Search-tab Type select gains "Custom art" option.
- New/Popular/Mine feed-type filter rows gain "Custom Art" button.
- Existing buildRowsFromEvents typeFilter handles 'customart' for
  free since rows already carry row.mode = normalizeTemplateMode.

JPEG export:
- enterExportPrintMode preserves the canvas background color in
  customart mode (was being wiped by the inline cssText override).
  The existing else-branch already sized @page to the sheet (canvas)
  dimensions; combined with the bg preserve, the saved PDF prints
  exactly the canvas content edge-to-edge for the JPEG conversion.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 19:41:16 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 a49d0b0c6c Custom Art mode Phase A: editor + state + save/load
Adds a new template mode for free-form digital canvases (wallpapers,
banners, social images, posters). One canvas, layers stack on top.
Phase A is the editor foundation; publish + browser come next.

State:
- New state.customArt = { width, height, background, preset }
  defaulting to 1920x1080 white
- CUSTOM_ART_PRESETS (10 entries: 16:9, 9:16, QHD, 4K, 1:1, 4:5, 3:1,
  Letter portrait/landscape, plus Custom)
- customArtCanvasInches() converts pixel dims to internal inches at 96dpi
  so the existing inch-based layer math keeps working unchanged

Mode integration:
- normalizeTemplateMode recognizes 'customart' / 'custom-art' and
  payload.customArt fallback
- sheetW() / sheetH() return the canvas in inches when in customart mode
- targetBounds('canvas') returns the full canvas
- applyTargetMode, updateLayerTargetOptions, templateLayerTargetsForMode
  all support the 'canvas' target
- New body.template-customart class hides cover/disc/jewel-specific
  fieldsets, bleed, trim, fold, and slot UI

Editor:
- New "Custom art" layout tab in the sidebar mode switcher
- New #customArtFieldset with preset dropdown, width/height inputs
  (min 100, max 8192), and background color picker
- Selecting a preset snaps width/height; manual edits flip preset to
  "Custom" automatically
- Canvas background applied to .sheet inline; reset when leaving the mode
- Layers panel shows a single Canvas region for customart with the same
  Add Image / Add Text / Add Color buttons

Persistence:
- customArtPayload() function mirrors discDesignPayload / coverTemplate
- exportPayload() now includes a customArt blob alongside the other modes
- downloadProject routes to customArtPayload with filename
  custom_art_<wxh>.json
- loadProjectFromText has a customart branch that restores state.customArt
  plus canvas-targeted layers without clobbering other modes' state

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 19:34:11 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 41a731d790 Add media-identifiers panel + custom-art-mode spec doc
Project metadata:
- New "+ Add Media Identifiers" button styled as a real button (not a
  details-summary text link) on both the main metadata fieldset and the
  disc-designer's metadata fieldset
- Expands to 4 optional inputs: IMDb ID, UPC, TheTVDB ID,
  MusicBrainz Disc ID. Button label dynamically becomes
  "Media Identifiers (N)" when any are filled
- State adds state.meta.upc / tvdbId / musicbrainzDiscId alongside
  the existing imdbId. Old payloads with only imdbId continue to work
- Publish event tags emit NIP-73-style ['i', 'imdb:tt1234567'],
  ['i', 'upc:...'], ['i', 'tvdb:...'], ['i', 'musicbrainz:...'] alongside
  the legacy ['i', 'tt1234567'] for backward compat
- Community feed search haystack now includes all four identifiers, so
  typing a UPC into search finds matching designs

Docs:
- New docs/CUSTOM_ART_MODE_FEATURE.md spec for the upcoming 5th
  template mode (generic canvas at arbitrary aspect ratios for
  wallpapers, banners, social posts, posters). Three-phase plan:
  Phase A editor, Phase B publish/share, Phase C browser+filter+export.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 19:25:11 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 20cbe78805 Mini-preview cards, fixed scaling, welcome close button, danger styling
Template browser cards now render WYSIWYG mini-previews instead of just the
background image. Each card shows the full layered composition (color fills,
text, image layers, slot artwork) at thumbnail scale, with the design's
natural silhouette preserved — discs stay circular, sheets stay rectangular,
cover stays wide.

Preview renderers (4 total):
- Added a `compact` option to renderTemplatePreviewDOM,
  renderDiscSheetPreviewDOM, renderJewelPreviewDOM, renderDiscPreviewDOM.
  When compact: drop labels (BACK/SPINE/FRONT, Disc 1/2, FRONT/TRAY,
  sheet titles), drop shadow, drop border lines.
- Compact mode renders each preview at a fixed natural pixel size
  (720 wide for sheets, 380 for disc-design); fitMiniPreview applies a
  CSS transform: scale() to fit the thumb. Everything — text, layer
  positions, slot art — scales together so the mini is a true miniature
  of what `Preview` shows.

Disc-design preview specific fixes:
- Was the only renderer not sorting layers by z, so they rendered in
  array order and a higher-z layer added earlier could be hidden behind
  a lower-z layer added later. Now sorted ascending and stacked in DOM.
- Added explicit z-index values matching the disc-sheet renderer
  (bg img z:1, layers z:3+sortedIndex, hole z:12) to avoid stacking
  context surprises from the per-layer transform: rotate().
- Used Number(layer.x) etc. for defensive coercion of legacy payloads.
- Made the center hole percentage-sized so it scales with the disc.

Cover preview fix:
- Dropped the unnecessary `row` wrapper that broke mini-preview sizing.
  preview is appended directly to container, same shape as the other
  three renderers. The `!hasArtwork` early-return now only fires in
  non-compact mode so empty covers still show their silhouette in the
  card thumb.

UI polish:
- Close button (×) in the top-right corner of the welcome modal.
- Both "Close project" buttons (template-mode and disc-designer)
  now use the .danger class to match the styling of the other
  destructive buttons in their panels.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 19:11:18 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 d94eff9671 Add shareable design links via NIP-19 naddr/nevent
Share button (template browser preview):
- New "Copy share link" button next to Save JSON / Print
- Generates a NIP-19 identifier:
  * naddr (stable, address-based) for new-style events whose d-tag
    starts with the mode prefix (cover:, disc:, jewel:, disc-design:)
  * nevent (event-id-based) for legacy events whose d-tag is a bare
    IMDB id, so a cover share-link doesn't collide with the
    disc-design share-link for the same movie
- Encoded URLs use #/share/<id> form so they work on any host without
  a server rewrite; the URL bar also updates while a preview is open

Receiving:
- On init, parseShareIdFromLocation() reads either /share/<id> or
  #/share/<id> and dispatches to loadSharedDesign()
- loadSharedDesign uses a two-stage fast path: query the relay hints
  embedded in the naddr/nevent first (2s timeout), fall back to the
  user's default relay list (4s timeout) only if those don't have it
- Skips the full fetchFeedAndReactions(); instead fetches only the
  one event + that author's kind-0 profile + kind-7 reactions on that
  event + kind-5 deletions for that author/kind, all in parallel with
  short timeouts. Typical lookup is now ~1-3s instead of ~20s.

Bech32/NIP-19 codec:
- Self-contained encoder + decoder for note/nevent/naddr (no
  dependencies). Decoder also accepts a raw 64-char hex event id.

URL lifecycle:
- openDesignPreview replaces the URL with #/share/<id> so reload/
  bookmark/copy-from-address-bar all work
- closeNostrPreview restores the URL to / so reload doesn't re-open

Hosting:
- vercel.json with rewrites maps /share/:id -> / for the "pretty"
  path form. Hash form works without any host config.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 14:54:02 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 915ad80a64 Add color/text layers, lock, duplicate, font picker, JPEG-via-PDF export
Layer system:
- New 'color' layer type: solid fill, color picker in selected-layer controls,
  full-fill of target section by default, locked by default
- Text layer: replaced 'Name' field with a multi-line 'Text' textarea bound
  to layer.html (round-trips with the canvas's contentEditable edits)
- Text layer: web-safe font family picker (15 options, each previewing its
  own font) and font-family applied across all 5 render sites
- Layer locking: locked layers get pointer-events:none on the canvas and a
  LOCK badge in the layer list; selected-layer panel has a lock checkbox
- Duplicate-layer button: clones the selected layer with new id, slight
  position offset, fresh z, and "copy" appended to the name
- Move forward / Move back now step one position at a time (swap z with the
  immediate neighbor in the same target region) instead of jumping to the
  extremes

Cover preview:
- renderTemplatePreviewDOM now renders user layers (color/text/image) for
  cover-relevant targets, with correct coordinate translation from page
  (including bleed) into the trim-only preview viewport

Print PDF:
- print-color-adjust: exact added so color-layer backgrounds aren't stripped
- Cover-mode page is now landscape Letter (11x8.5) with the sheet centered
  via position: fixed, dropping the old 90-degree rotation hack
- Hide .modal in @media print so open modals don't bleed into the print

Title/category save bug:
- preferredDiscProjectTitle/imdb/category/language now fall back to
  state.meta values when both disc-slot metas are empty, instead of
  blanking state.meta.title each render in disc mode

Close project:
- closeTemplateProject and closeDiscDesignerProject now sync all input
  panels (slots, discs, jewel, combined, title fields) after resetting
  state, and also clear cover layers, jewel inserts, and both spine-label
  sets

Welcome modal: minor regression-fix wiring (kept from prior commit)

JPEG export via PDF:
- New "Export JPEG" modal with a two-step flow:
  1) Open the print dialog -> Save as PDF (page sized to artwork)
  2) Pick that PDF -> PDF.js rasterizes page 1 -> JPEG download
- Lazy-loads pdf.js from cdnjs on first use
- Print "export mode": temporarily moves .sheet to a body-level
  #exportPrintContainer with explicit inline styles, sets @page to the
  trim area (cover: panelW*2+spineW x trimH, no bleed margin), hides
  everything else; restored on afterprint
- Saved PDF filename is "<regular-save-name>-jpeg-source.pdf" so it's
  easy to tell apart from the print-ready PDF

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-17 14:03:53 -04:00
thePR0M3TH3AN f61ab7729f update 2026-05-16 20:17:38 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 88cf2bceab Layer panel parity, jewel/disc title cleanup, preview & welcome upgrades
Layers:
- Layers panel now shows in disc-sheet and jewel template modes and inside
  the disc-design designer (was hidden in all three)
- Layer target dropdown is mode-aware: disc shows Disc 1/Disc 2, jewel shows
  the two inserts, cover shows trim/back/spine/front

"Use" button (template browser):
- Cover/disc/jewel "Use" now switches the editor out of designer mode via
  switchAppMode('template'), so the right tool is visible after loading
- Disc-design "Use" always opens the disc-design designer unless an explicit
  slot import target was set

Disc-sheet title:
- Template browser title for 2-disc sheets concatenates per-slot titles as
  "Disc 1 title + Disc 2 title" via new discSheetDisplayTitle() helper

Jewel case spine text:
- New jewelSpineLabels {left,right} mirrored from the disc spine pattern
- Inputs in jewel artwork panel, vertical-rl labels rendered over both tray
  spines, persisted via jewelTemplate.spineLabels in save/publish payloads

Jewel preview:
- New renderJewelPreviewDOM() renders front + tray with spines, spine text,
  and any jewel-target layers in the template browser preview pane

Broken image flagging:
- _feedCache.brokenImages tracks failed preview URLs
- Broken cards get a "MISSING IMAGE" badge, dimmed/hatched thumbnail, and
  are de-ranked to the end of the sort on New and Popular tabs

Disc-design panel:
- Removed standalone "Design name" input; design.name now mirrors
  state.meta.title automatically

Welcome modal:
- Redesigned to match the community browser: New/Popular tabs, feedWall grid,
  buildFeedCard tiles, "Browse all designs" + "Start from scratch" footer
- Search input now searches inline using the same card layout

Docs:
- New spec at docs/COLOR_AND_FONT_LAYERS_FEATURE.md covering the upcoming
  color-fill layer type and text font-family picker

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-16 18:47:42 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 b02e826481 Add WoT soft-moderation (blur-by-WoT) + fix feed scroll and d-tag collisions
Soft moderation (Phase 5):
- Blur cards that ≥2 hop-1 follows have muted (kind-10000) or reported (kind-1984)
- Overlay shows count + dominant reason; "Show anyway" reveals for the session
- Settings toggle under Community (on by default, hidden when logged out)
- Trust beats crowd: self/trusted authors never blurred
- Spec doc at docs/WOT_SOFT_MODERATION_FEATURE.md

Feed scrolling:
- Removed nested scroll container on .feedWall (max-height + overflow-y caused
  the wall to only show ~2 rows because the outer .modalCard ate scroll events)
- Re-targeted infinite-scroll listener to .modalCard
- Added top-up loop in appendFeedRows so the first chunk fills the viewport

Template d-tag collisions:
- Cover/disc/jewel/disc-design publishes now prefix the d tag with the mode
  (e.g. cover:tt12345 vs disc:tt12345) so a user can publish multiple template
  types for the same IMDB without one replacing the other at the relay level
- buildRowsFromEvents and deduplicateRows now key by (address, mode) so legacy
  events with unprefixed d-tags also coexist correctly in the UI

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-16 15:25:30 -04:00
thePR0M3TH3ANandClaude Sonnet 4.6 e4e5671c85 Add Web-of-Trust phases 3-4: trust ranking, hop-2 graph, and NIP-56 reporting
Phase 3:
- Streaming relay sync with progressive feed rendering (onEvent callbacks, 150ms debounce)
- Read-side soft cooldown (30s base, 5min max) so transient timeouts don't lock relays for an hour
- NIP-09 deletion fix: timestamp-aware tombstoning so re-published designs appear correctly
- Hop-2 follow graph (fetchHop2Follows) with 15min sessionStorage cache
- 4-tier trust system: self/trusted/extended/unknown with TIER_WEIGHT sort
- Trust filter select (All/Follows/Follows+Extended) on New and Popular tabs
- Trust badges on feed cards (Following / 2nd°) when logged in
- Streaming status line showing relay progress during fetch

Phase 4:
- NIP-56 kind-1984 report flow: modal with reason, note, and "also block" option
- Report button on each feed card alongside Block
- reportModal moved before <script> so DOM is ready when init() wires listeners

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-16 14:29:24 -04:00
thePR0M3TH3ANandClaude Haiku 4.5 bb93207871 Fix invisible community feed: filter kind-30078 by casewrap tag
The template browser was returning zero results because kind 30078
(NIP-78 app data) is shared by many unrelated apps. A bare
{kinds:[30078], limit:200} query was getting flooded by other apps'
events (northlight-fragment alone had 300+ per relay), pushing
casewrap templates past the cutoff.

Narrow the relay-side query with #t:['casewrap'] for templates and
#k:['30078'] for deletions so the limit applies to this app only.

Also ships several supporting fixes that came out of the same
debugging session:

- queryRelays: add onEvent / onRelayDone streaming callbacks so
  templates render as they arrive instead of after every relay
  settles. Fast relays now populate the feed in < 1s.
- markRelayFailure: accept a context arg ('read' vs 'publish').
  Read failures use 30s base / 5min max cooldowns (was 10min / 1hr).
  A single transient read timeout no longer silently shrinks the
  relay pool for an hour.
- fetchFeedAndReactions: stream into a Map, debounce a 150ms
  rebuild of _feedCache.events, fire notifyFeedProgress() on each
  batch so loadFeedTab re-renders progressively.
- loadFeedTab: register a per-tab rerender closure as
  _currentFeedRerender, render whatever is cached immediately,
  re-render on each progress tick. _currentFeedRerender === rerender
  identity check prevents a late await from clobbering a tab the
  user switched away from.
- Add live status line ("Streaming · N templates · R/T relays")
  above the feed wall in New and Popular tabs.
- buildRowsFromEvents: respect NIP-09 deletion semantics by tracking
  per-address deletion timestamps. Templates published AFTER a kind-5
  deletion at the same address are no longer silently filtered.
- Drop wss://relay.nostr.pub from default relays (DNS no longer
  resolves).

See docs/STREAMING_RELAY_SYNC_FEATURE.md for the full plan and
rationale.

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-05-16 13:58:30 -04:00
thePR0M3TH3AN 9e39ce208e Sync blocked authors to nostr mute list 2026-05-16 13:15:11 -04:00
thePR0M3TH3AN 90e6ab911e Merge pull request #7 from PR0M3TH3AN/codex/implement-phase-1-of-web-of-trust-system
Phase 1: Add client-side local block list for community feed
2026-05-16 13:10:01 -04:00