diff --git a/src/index.html b/src/index.html
index 477e85d..e905f37 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1657,8 +1657,7 @@
.toast.toastOut { opacity:0; transition:opacity .2s; }
@keyframes toastIn { from { transform:translateY(8px); opacity:0; } to { transform:translateY(0); opacity:1; } }
- .publishConfirmPreview { background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:10px; margin:0 0 12px; display:flex; justify-content:center; align-items:center; max-height:280px; overflow:hidden; }
- .publishConfirmPreview > * { max-width:100%; max-height:260px; }
+ .publishConfirmPreview { background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:10px; margin:0 0 12px; display:flex; justify-content:center; align-items:center; height:280px; overflow:hidden; }
.publishConfirmMeta { margin:0 0 12px; font-size:.82rem; }
.publishConfirmMetaRow { display:flex; gap:10px; padding:5px 0; border-bottom:1px solid var(--line); }
.publishConfirmMetaRow:last-child { border-bottom:0; }
@@ -4014,7 +4013,6 @@
function discDesignPayload() {
persistCurrentDiscDesign();
const design = state.discDesign;
- if (!design?.src) throw new Error('Load an image URL before publishing this disc design.');
const layers = currentDesignerLayers();
return {
version: SCHEMA_VERSION,
@@ -6090,10 +6088,12 @@
renderJewelSlots();
renderCombined();
renderLayers();
- renderDesignerDisc();
syncLayerInputs();
syncDesignerInputs();
updateBodyClasses();
+ // After updateBodyClasses so the designer view is displayed/laid out —
+ // the disc image's cover-pan math needs the wrapper's real size.
+ renderDesignerDisc();
refreshPenOverlayGeometry();
refreshPenOverlayContent();
if (_editorTool !== 'select') syncToolOptionInputs();
@@ -8960,8 +8960,8 @@
} else if (state.templateMode === 'jewel') {
payload = jewelTemplatePayload();
typeTag = 'casewrap-jewel';
- if (!previewFromPayload(payload, 'jewel')) {
- throw new Error('Load jewel insert artwork before publishing.');
+ if (!previewFromPayload(payload, 'jewel') && !(payload.layers || []).length) {
+ throw new Error('Add jewel insert artwork or at least one layer before publishing.');
}
} else if (state.templateMode === 'customart') {
payload = customArtPayload();
@@ -8974,8 +8974,8 @@
} else {
payload = coverTemplatePayload();
typeTag = 'casewrap-cover';
- if (!previewFromPayload(payload, 'cover')) {
- throw new Error('Load cover artwork before publishing.');
+ if (!previewFromPayload(payload, 'cover') && !(payload.layers || []).length) {
+ throw new Error('Add cover artwork or at least one layer before publishing.');
}
}
const mode = normalizeTemplateMode(payload);
@@ -8999,11 +8999,13 @@
async function publishDiscDesignToNostr() {
const design = state.discDesign;
- if (!design?.src) throw new Error('Load an image URL before publishing this disc design.');
if (!state.meta.category?.trim()) {
throw new Error('Select a category in Project Metadata before publishing.');
}
const payload = discDesignPayload();
+ if (!design?.src && !(payload.layers || []).length) {
+ throw new Error('Load an image or add at least one layer before publishing this disc design.');
+ }
const designName = (design.name || '').trim() || state.meta.title;
if (!designName?.trim()) {
throw new Error('Add a title in Project Metadata or a custom design name before publishing.');
@@ -10709,7 +10711,6 @@
if (!title) { toastError('Please enter a title in Project Metadata before saving.'); return; }
if (!category) { toastError('Please select a category in Project Metadata before saving.'); return; }
const design = state.discDesign;
- if (!design?.src) return;
const payload = discDesignPayload();
const name = (design.name || 'disc_design')
.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
@@ -13260,7 +13261,7 @@
prevBtn.type = 'button'; prevBtn.textContent = 'Preview';
prevBtn.addEventListener('click', e => { e.stopPropagation(); openDesignPreview(row); });
const btn = document.createElement('button');
- btn.type = 'button'; btn.textContent = 'Fork off!';
+ btn.type = 'button'; btn.textContent = 'Use/Fork';
btn.title = 'Fork this design — opens it in the editor so you can save your own version';
btn.addEventListener('click', e => { e.stopPropagation(); onUse(); });
const tipBtn = makeFeedCardTipButton(row);
@@ -13803,7 +13804,7 @@
prevBtn.addEventListener('click', e => { e.stopPropagation(); openDesignPreview(row); });
const useBtn = document.createElement('button');
useBtn.type = 'button';
- useBtn.textContent = 'Fork off!';
+ useBtn.textContent = 'Use/Fork';
useBtn.title = 'Fork this design — opens it in the editor so you can save your own version';
useBtn.addEventListener('click', e => { e.stopPropagation(); onUse(); });
actions.append(makeReactionBtn(row), prevBtn, useBtn);
@@ -13957,7 +13958,7 @@
const forkBtn = document.createElement('button');
forkBtn.type = 'button';
- forkBtn.textContent = 'Fork off!';
+ forkBtn.textContent = 'Use/Fork';
forkBtn.title = 'Fork this design — opens it in the editor so you can save your own version';
forkBtn.addEventListener('click', (e) => {
e.stopPropagation();
@@ -14177,8 +14178,37 @@
if (disc.src) {
const img = document.createElement('img');
img.src = disc.src;
- img.style.cssText = `position:absolute;left:50%;top:50%;width:100%;height:100%;object-fit:${disc.fit || 'cover'};transform:translate(-50%,-50%) translate(${disc.x||0}%,${disc.y||0}%) rotate(${disc.rotate||0}deg) scale(${disc.zoom||1});z-index:1;`;
+ img.alt = '';
+ img.style.cssText = 'position:absolute;left:50%;top:50%;width:100%;height:100%;object-fit:fill;transform-origin:center center;user-select:none;pointer-events:none;z-index:1;';
discWrap.appendChild(img);
+ // Match the editor's cover-pan math (applyRotationCover) so the
+ // background image sits exactly where the user placed it — the old
+ // translate(x%) model panned by a different, much larger range.
+ const updateDiscArt = () => {
+ const fit = disc.fit || 'cover';
+ const cW = discWrap.clientWidth, cH = discWrap.clientHeight;
+ if (!cW || !cH) return;
+ const nW = img.naturalWidth || cW, nH = img.naturalHeight || cH;
+ let baseW = cW, baseH = cH;
+ if (fit === 'none') { baseW = nW; baseH = nH; }
+ else if (fit !== 'fill') {
+ const fs = fit === 'contain' ? Math.min(cW / nW, cH / nH) : Math.max(cW / nW, cH / nH);
+ baseW = nW * fs; baseH = nH * fs;
+ }
+ const rotate = Number(disc.rotate) || 0, zoom = Number(disc.zoom) || 1;
+ const rad = rotate * Math.PI / 180, ac = Math.abs(Math.cos(rad)), as = Math.abs(Math.sin(rad));
+ const rotatedW = baseW * ac + baseH * as, rotatedH = baseW * as + baseH * ac;
+ const coverScale = fit === 'cover'
+ ? Math.max(cW / Math.max(rotatedW, 1), cH / Math.max(rotatedH, 1), 1) : 1;
+ const finalScale = coverScale * zoom;
+ const xPx = ((Number(disc.x) || 0) / 100) * Math.max(0, (rotatedW * finalScale - cW) / 2);
+ const yPx = ((Number(disc.y) || 0) / 100) * Math.max(0, (rotatedH * finalScale - cH) / 2);
+ img.style.width = `${baseW}px`;
+ img.style.height = `${baseH}px`;
+ img.style.transform = `translate(-50%,-50%) translate(${xPx}px,${yPx}px) rotate(${rotate}deg) scale(${finalScale})`;
+ };
+ if (img.complete) requestAnimationFrame(updateDiscArt);
+ img.addEventListener('load', updateDiscArt, { once: true });
}
layers.forEach((layer, i) => {