- 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]>
- Increase icon size from 26px to 40px
- Set inactive tab background to transparent
- Adjust opacity from 0.85 to 0.75 for inactive state
- Remove margin-right from larger icons
- Increase gap between icon and text
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Change main font family to Verdana/Georgia for classic 90s feel
- Update headings to use Georgia serif font
- Use bold weights and classic sans-serif for buttons and labels
- Adjust font sizes and letter spacing for retro aesthetic
- Decrease button border radius for more angular 90s look
- Add Verdana/Arial to labels, hints, and buttons
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>