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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
- 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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
The layer hint was showing URLs and other internal data. The preview itself
is what matters, so just removed the hint text entirely.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Image layers now show as '[Image]' unless they have a specific name
- Text layers show extracted text content or '[Text]' if empty
- Prevents full image URLs from cluttering the layer list
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Disc preview now uses border-radius:50% circle matching app's real rendering
- Add white center hole overlay (not a cutout, just white circle on top)
- Layer positioning now correct: x,y,w,h as percentage of disc diameter
- Fix font sizing: convert pt to px with proper DPI scaling (96/72 ratio)
- Fix layer positioning from incorrect pixel-based to correct percentage-based
- Case template slots now use correct CSS transform for image positioning
- Show correct objectFit, zoom, x, y, rotate for both disc and template images
- Filter layers properly by target to show only designer-disc layers in preview
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Import buttons now set templateMode to 'case' for 2-disk print template view
- Shows the case cover with front/back/spine and 2 disc slots
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- For disc designs: show 'Import into Disk 1' and 'Import into Disk 2' buttons
- For templates: show 'Print / Save PDF' button
- Buttons are built dynamically based on design type
- Disc imports automatically switch to template mode
- Both designs still have 'Save JSON' option
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add Preview button next to Use on all feed cards and search results
- Create full-screen preview modal showing disc/template design
- Preview renders background image + text layers for disc designs
- Preview shows front/spine/back images for case templates
- Save JSON button downloads design payload directly
- Print/Save PDF temporarily loads design, prints, then restores original state
- Uses afterprint event to seamlessly restore user's work after printing
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add accent-color: #00d9ff to input[type='range'] and input[type='checkbox']
- Custom webkit slider thumb and track styling with cyan theme
- Custom Firefox slider styling with cyan theme
- Radio button styling with cyan accent
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Switch from @import to <link> tags for more reliable Google Fonts loading
- Add preconnect hints for faster font loading
- Change font-display from 'swap' to 'block' to ensure Press Start 2P loads
- Remove '~ DISC & CASE DESIGN STUDIO ~' tagline text
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Change 'Cover preview' from h1 to h2
- Add h2 styling: VT323 pixel font, normal color (no neon)
- h2 is 1rem (much smaller than h1's 2.2rem)
- Remove glow effects from section headings
- Maintain visual hierarchy: h1 is title, h2 is section header
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Increase h1 font size from 1.3rem to 2.2rem (!important)
- Force Press Start 2P font with !important flags
- Increase logo size from 28px to 48px
- Add !important to font-family and font-weight for consistency
- Better visual hierarchy and readability
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Increase font size from 0.9rem to 1.3rem for visibility
- Remove gradient effect that was obscuring the pixel font
- Switch to solid cyan color (#00d9ff) for clarity
- Keep neon glow effects (text-shadow + drop-shadow)
- Ensure Press Start 2P pixel font is clearly visible
- Increase letter-spacing slightly for better readability
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Title improvements:
- Add cyan-to-blue gradient text for dynamic look
- Multi-layer drop-shadow glow effects (4px + 12px)
- Text-shadow for additional neon glow
- Restructured layout: logo + title on one line, tagline below
Logo:
- Add cyan glow effect with drop-shadow filter
- Maintains 28px height with better visual impact
Tagline:
- New subtitle '~ DISC & CASE DESIGN STUDIO ~'
- Styled in bright cyan neon color
- Cyan text-shadow for layered glow effect
- Pixel-style spacing with wider letter-spacing
- Centered layout for visual balance
Result: Full neon cyberpunk aesthetic on header!
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Colors:
- Dark mode: Deep blue backgrounds (#0a0e27) with cyan accents (#00d9ff)
- Light mode: Cool cyan backgrounds (#e8f4f8) with bright cyan accents (#00b8e6)
- Updated text colors, borders, and highlights for cool palette
- Hot pink warnings (#ff006e) and bright green success (#00d084)
Edges:
- Remove all rounded corners (border-radius: 0px) for sharp pixel look
- Thicken borders from 1px to 2px for more defined edges
- Add subtle 2px box-shadows to fieldsets and buttons for 3D pixel effect
- Create hard-edged, retro computer aesthetic
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Reduce logo height from 40px to 28px
- Reduce logo right margin from 12px to 8px
- Reduce h1 font size from 1.2rem to 0.9rem
- Add white-space: nowrap to prevent title wrapping
- Tighten line-height and letter-spacing for compact display
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add Google Fonts: Press Start 2P and VT323
- Use Press Start 2P for main heading (h1) - iconic 8-bit arcade style
- Use VT323 for body text, labels, buttons, hints - readable pixel font
- Add letter-spacing throughout for crisp pixel aesthetic
- Reduce border-radius on buttons for more angular pixel look
- Create complete retro pixel/8-bit visual style
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Use paper_icon_32px.png for Template tab
- Use disk_icon_32px.png for Disc Designer tab
- Remove old larger PNG icon files
- Icons maintain 40px display size with transparent backgrounds
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>