From 6b7b2ee42a622a24c7d512641f2dfb38d46c017d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 15 May 2026 23:17:23 -0400 Subject: [PATCH] per-slider resets --- src/index.html | 349 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 283 insertions(+), 66 deletions(-) diff --git a/src/index.html b/src/index.html index 7acc23c..4d322fd 100644 --- a/src/index.html +++ b/src/index.html @@ -505,12 +505,13 @@ body.dark .appModeTab.active { color: #fff; } - body.app-designer .template-only { display: none !important; } body.app-template .designer-only { display: none !important; } + body.app-designer .template-only { display: none !important; } body.app-designer label[for="layerTarget"], body.app-designer #layerTarget { display: none !important; } /* Layers panel hides in Template tab when in disc-sheet mode — disc layers live in the Designer */ - body.app-template.template-disc #layersFieldset { display: none !important; } + body.app-template.template-disc #layersFieldset, + body.app-template.template-jewel #layersFieldset { display: none !important; } /* Designer single-disc preview */ body.app-designer .sheetWrap { display: none !important; } @@ -829,10 +830,43 @@ display: none !important; } - /* Layers: disc position targeting only matters in disc mode */ - body.template-jewel .layer-controls, - body.template-cover .layer-controls { - display: none !important; + .templateLayerPanels { + display: grid; + gap: 10px; + margin-top: 10px; + } + + .templateLayerPanel { + border: 1px solid var(--line); + border-radius: 0px; + padding: 10px; + background: rgba(127,127,127,.05); + display: grid; + gap: 8px; + } + + .templateLayerPanelHead { + display: flex; + justify-content: space-between; + gap: 10px; + align-items: center; + font-size: .82rem; + font-weight: 800; + } + + .templateLayerPanelHead span:last-child { + color: var(--muted); + font-weight: 700; + } + + .templateLayerPanelList { + display: grid; + gap: 6px; + } + + .templateLayerPanelActions { + display: grid; + gap: 6px; } .discSlot { @@ -1331,11 +1365,40 @@ .rangeLine { display: grid; - grid-template-columns: 1fr 48px; + grid-template-columns: 1fr auto 56px; gap: 8px; align-items: center; } + .rangeLine button.rangeReset { + padding: 5px 8px; + font-size: .72rem; + line-height: 1; + white-space: nowrap; + border: 1px solid var(--line); + border-radius: 0px; + background: #f8fafc; + color: var(--muted); + cursor: pointer; + transition: background .15s, color .15s; + } + + .rangeLine button.rangeReset:hover { + background: #e2e8f0; + color: var(--ink); + } + + body.dark .rangeLine button.rangeReset { + background: #1e293b; + border-color: var(--line); + color: var(--muted); + } + + body.dark .rangeLine button.rangeReset:hover { + background: #334155; + color: var(--ink); + } + .rangeLine output { font-size: .74rem; color: var(--muted); @@ -1718,6 +1781,7 @@
+ 60%
@@ -1789,24 +1853,28 @@
+
+
+
+
@@ -1887,21 +1955,25 @@
+
+
+
+
@@ -1914,25 +1986,8 @@

Designs A–D created here can be assigned to print positions in the Template tab.

-
+
Layers - - - - -
- - -
-
- - -
-

Add image or text layers on top of your disc layout.

-
@@ -2343,6 +2398,14 @@ { key: 'front', label: 'Front cover' } ]; + const TEMPLATE_LAYER_TARGETS = [ + { key: 'page', label: 'Whole page' }, + { key: 'cover-trim', label: 'Cover trim area' }, + { key: 'back', label: 'Back cover' }, + { key: 'spine', label: 'Spine' }, + { key: 'front', label: 'Front cover' } + ]; + const discTemplate = { sheetW: 8.5, sheetH: 11, @@ -3055,7 +3118,8 @@ function syncUrlInputs() { $('combinedUrl').value = state.combined.src || ''; - $('newLayerUrl').value = ''; + const newLayerUrl = $('newLayerUrl'); + if (newLayerUrl) newLayerUrl.value = ''; for (const { key } of slotNames) { $(`url-${key}`).value = state.slots[key].src || ''; } @@ -3298,24 +3362,28 @@
+
+
+
+
@@ -3401,24 +3469,28 @@
+
+
+
+
@@ -3554,21 +3626,25 @@
+
+
+
+
@@ -3742,6 +3818,15 @@ $('rotate-combined-out').textContent = `${c.rotate}°`; } + function bindRangeReset(buttonId, handler) { + const button = $(buttonId); + if (!button) return; + button.addEventListener('click', () => { + handler(); + render(); + }); + } + function syncCommunityInputs() { const rl = $('relayList'); if (rl) rl.value = state.community.relays; @@ -3969,7 +4054,7 @@ state.templateMode = 'disc'; } else if (target.startsWith('jewel')) { state.templateMode = 'jewel'; - } else if (target !== 'page') { + } else { state.templateMode = 'cover'; } syncInputs(); @@ -3981,8 +4066,8 @@ render(); } - function addImageLayer(src, name) { - const target = $('layerTarget').value; + function addImageLayer(src, name, targetOverride = '') { + const target = targetOverride || $('layerTarget')?.value || 'page'; const bounds = targetBounds(target); applyTargetMode(target); addLayer({ @@ -4001,8 +4086,8 @@ }); } - function addTextLayer() { - const target = $('layerTarget').value; + function addTextLayer(targetOverride = '') { + const target = targetOverride || $('layerTarget')?.value || 'page'; const bounds = targetBounds(target); applyTargetMode(target); addLayer({ @@ -4098,34 +4183,125 @@ function renderLayerList() { const list = $('layerList'); + if (!list) return; list.innerHTML = ''; - const visibleLayers = state.layers.filter((layer) => - state.appMode === 'designer' - ? layer.target === 'designer-disc' - : layer.target !== 'designer-disc' - ); - const listedLayers = visibleLayers.filter(layer => !layer._generatedDiscLayer); - if (!listedLayers.length) { - const empty = document.createElement('p'); - empty.className = 'hint'; - empty.textContent = 'No extra layers yet.'; - list.appendChild(empty); + if (state.appMode === 'designer') { + const listedLayers = state.layers + .filter((layer) => layer.target === 'designer-disc') + .filter((layer) => !layer._generatedDiscLayer); + if (!listedLayers.length) { + const empty = document.createElement('p'); + empty.className = 'hint'; + empty.textContent = 'No extra layers yet.'; + list.appendChild(empty); + return; + } + + const ordered = [...listedLayers].sort((a, b) => (b.z || 0) - (a.z || 0)); + for (const layer of ordered) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = `layerItem${layer.id === state.selectedLayerId ? ' active' : ''}`; + const rawName = layer.name || layer.type; + const displayName = rawName.length > 32 ? rawName.slice(0, 29) + '...' : rawName; + item.innerHTML = `${displayName}${layer.type}`; + item.addEventListener('click', () => { + state.selectedLayerId = layer.id; + render(); + }); + list.appendChild(item); + } return; } - const ordered = [...listedLayers].sort((a, b) => (b.z || 0) - (a.z || 0)); - for (const layer of ordered) { - const item = document.createElement('button'); - item.type = 'button'; - item.className = `layerItem${layer.id === state.selectedLayerId ? ' active' : ''}`; - const rawName = layer.name || layer.type; - const displayName = rawName.length > 32 ? rawName.slice(0, 29) + '...' : rawName; - item.innerHTML = `${displayName}${layer.type}`; - item.addEventListener('click', () => { - state.selectedLayerId = layer.id; - render(); + if (state.templateMode !== 'cover') { + const hint = document.createElement('p'); + hint.className = 'hint'; + hint.textContent = 'Layer panels are shown for case wraps.'; + list.appendChild(hint); + return; + } + + const targetLayers = state.layers.filter((layer) => layer.target !== 'designer-disc' && !layer._generatedDiscLayer); + for (const targetDef of TEMPLATE_LAYER_TARGETS) { + const panel = document.createElement('section'); + panel.className = 'templateLayerPanel'; + + const head = document.createElement('div'); + head.className = 'templateLayerPanelHead'; + const count = targetLayers.filter((layer) => layer.target === targetDef.key).length; + head.innerHTML = `${escapeHtml(targetDef.label)}${count} layer${count === 1 ? '' : 's'}`; + panel.appendChild(head); + + const actions = document.createElement('div'); + actions.className = 'templateLayerPanelActions'; + + const urlRow = document.createElement('div'); + urlRow.className = 'urlRow'; + const urlInput = document.createElement('input'); + urlInput.type = 'url'; + urlInput.placeholder = 'https://example.com/layer.png'; + urlInput.id = `template-layer-url-${targetDef.key}`; + const copyBtn = document.createElement('button'); + copyBtn.type = 'button'; + copyBtn.className = 'copyUrlBtn'; + copyBtn.textContent = 'Copy'; + copyBtn.addEventListener('click', () => copyUrl(urlInput.id)); + urlRow.append(urlInput, copyBtn); + + const buttonRow = document.createElement('div'); + buttonRow.className = 'buttonRow'; + const addImageBtn = document.createElement('button'); + addImageBtn.type = 'button'; + addImageBtn.textContent = 'Add image layer'; + addImageBtn.addEventListener('click', () => { + const raw = urlInput.value.trim(); + if (!raw) return; + try { + const url = normalizeImageUrl(raw); + addImageLayer(url, url.split('/').pop() || 'Image layer', targetDef.key); + urlInput.value = ''; + } catch (error) { + alert(error.message || 'Invalid image URL.'); + } }); - list.appendChild(item); + const addTextBtn = document.createElement('button'); + addTextBtn.type = 'button'; + addTextBtn.textContent = 'Add text layer'; + addTextBtn.addEventListener('click', () => addTextLayer(targetDef.key)); + buttonRow.append(addImageBtn, addTextBtn); + + actions.append(urlRow, buttonRow); + panel.appendChild(actions); + + const listWrap = document.createElement('div'); + listWrap.className = 'templateLayerPanelList'; + const ordered = targetLayers + .filter((layer) => layer.target === targetDef.key) + .sort((a, b) => (b.z || 0) - (a.z || 0)); + if (!ordered.length) { + const empty = document.createElement('p'); + empty.className = 'hint'; + empty.textContent = 'No layers in this area yet.'; + listWrap.appendChild(empty); + } else { + for (const layer of ordered) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = `layerItem${layer.id === state.selectedLayerId ? ' active' : ''}`; + const rawName = layer.name || layer.type; + const displayName = rawName.length > 32 ? rawName.slice(0, 29) + '...' : rawName; + item.innerHTML = `${displayName}${layer.type}`; + item.addEventListener('click', () => { + state.selectedLayerId = layer.id; + render(); + }); + listWrap.appendChild(item); + } + } + + panel.appendChild(listWrap); + list.appendChild(panel); } } @@ -6848,6 +7024,42 @@ }); } + bindRangeReset('reset-zoom', () => { + state.zoom = 60; + syncInputs(); + }); + + bindRangeReset('reset-zoom-combined', () => { state.combined.zoom = 1; syncCombinedInputs(); }); + bindRangeReset('reset-x-combined', () => { state.combined.x = 0; syncCombinedInputs(); }); + bindRangeReset('reset-y-combined', () => { state.combined.y = 0; syncCombinedInputs(); }); + bindRangeReset('reset-rotate-combined', () => { state.combined.rotate = 0; syncCombinedInputs(); }); + + for (const { key } of slotNames) { + bindRangeReset(`reset-zoom-${key}`, () => { state.slots[key].zoom = 1; syncSlotInputs(key); }); + bindRangeReset(`reset-x-${key}`, () => { state.slots[key].x = 0; syncSlotInputs(key); }); + bindRangeReset(`reset-y-${key}`, () => { state.slots[key].y = 0; syncSlotInputs(key); }); + bindRangeReset(`reset-rotate-${key}`, () => { state.slots[key].rotate = 0; syncSlotInputs(key); }); + } + + for (const { key } of jewelSlotNames) { + bindRangeReset(`reset-jewel-zoom-${key}`, () => { state.jewelInserts[key].zoom = 1; syncJewelInputs(key); }); + bindRangeReset(`reset-jewel-x-${key}`, () => { state.jewelInserts[key].x = 0; syncJewelInputs(key); }); + bindRangeReset(`reset-jewel-y-${key}`, () => { state.jewelInserts[key].y = 0; syncJewelInputs(key); }); + bindRangeReset(`reset-jewel-rotate-${key}`, () => { state.jewelInserts[key].rotate = 0; syncJewelInputs(key); }); + } + + for (const { key } of discSlotNames) { + bindRangeReset(`reset-disc-zoom-${key}`, () => { state.discs[key].zoom = 1; syncDiscTemplateInputs(); }); + bindRangeReset(`reset-disc-x-${key}`, () => { state.discs[key].x = 0; syncDiscTemplateInputs(); }); + bindRangeReset(`reset-disc-y-${key}`, () => { state.discs[key].y = 0; syncDiscTemplateInputs(); }); + bindRangeReset(`reset-disc-rotate-${key}`, () => { state.discs[key].rotate = 0; syncDiscTemplateInputs(); }); + } + + bindRangeReset('reset-zoom-designer', () => { state.discDesign.zoom = 1; syncDesignerInputs(); }); + bindRangeReset('reset-x-designer', () => { state.discDesign.x = 0; syncDesignerInputs(); }); + bindRangeReset('reset-y-designer', () => { state.discDesign.y = 0; syncDesignerInputs(); }); + bindRangeReset('reset-rotate-designer', () => { state.discDesign.rotate = 0; syncDesignerInputs(); }); + $('reset-combined').addEventListener('click', () => { Object.assign(state.combined, { area: 'trim', @@ -6896,19 +7108,24 @@ render(); }); - $('addImageLayerBtn').addEventListener('click', () => { - const raw = $('newLayerUrl').value.trim(); - if (!raw) return; - try { - const url = normalizeImageUrl(raw); - addImageLayer(url, url.split('/').pop() || 'Image layer'); - $('newLayerUrl').value = ''; - } catch (error) { - alert(error.message || 'Invalid image URL.'); - } - }); + const addImageLayerBtn = $('addImageLayerBtn'); + if (addImageLayerBtn) { + addImageLayerBtn.addEventListener('click', () => { + const newLayerUrl = $('newLayerUrl'); + const raw = newLayerUrl?.value.trim(); + if (!raw) return; + try { + const url = normalizeImageUrl(raw); + addImageLayer(url, url.split('/').pop() || 'Image layer'); + if (newLayerUrl) newLayerUrl.value = ''; + } catch (error) { + alert(error.message || 'Invalid image URL.'); + } + }); + } - $('addTextLayerBtn').addEventListener('click', addTextLayer); + const addTextLayerBtn = $('addTextLayerBtn'); + if (addTextLayerBtn) addTextLayerBtn.addEventListener('click', () => addTextLayer()); $('sheet').addEventListener('click', () => { state.selectedLayerId = '';