Powerduck Item Builder Loading items.js…
Card details fills the magazine rack entry



Will save as:

Title
Blurb
No links
items.js output paste into items.js on Neocities
More From Me — detail page WYSIWYG editor · only needed when link mode is internal or both

Write your detail content here…

HTML output save as: …
`; } // ──────────────────────────── // WYSIWYG editor commands // ──────────────────────────── function focusEditor(){ contentEditor.focus({preventScroll:true}); } function execCmd(cmd, val=null){ focusEditor(); document.execCommand(cmd,false,val); refreshDetailOutput(); } function formatBlock(tag){ focusEditor(); const sel = window.getSelection(); if(!sel.rangeCount) return; let node = sel.anchorNode; if(!node) return; if(node.nodeType===3) node=node.parentNode; while(node && node!==contentEditor){ if(/^(P|DIV|H2|H3|H4|H5|H6)$/.test(node.tagName)){ const el = document.createElement(tag); el.innerHTML = node.innerHTML; node.replaceWith(el); refreshDetailOutput(); return; } node=node.parentNode; } document.execCommand('formatBlock',false,tag); refreshDetailOutput(); } function insertButton(){ const label = window.prompt('Button text','Open'); if(!label) return; const href = window.prompt('Button URL','https://'); if(!href) return; focusEditor(); document.execCommand('insertHTML',false,`

${escapeHtml(label)}

`); refreshDetailOutput(); } function insertImage(){ const src = window.prompt('Image URL (or relative path like images/foo.jpg)','images/'); if(!src) return; focusEditor(); document.execCommand('insertHTML',false,``); refreshDetailOutput(); } document.querySelectorAll('[data-cmd]').forEach(btn=>{ btn.addEventListener('click',()=>execCmd(btn.dataset.cmd)); }); document.querySelectorAll('[data-action]').forEach(btn=>{ btn.addEventListener('click',()=>{ const a = btn.dataset.action; if(a==='clear') return execCmd('removeFormat'); if(a==='link'){ const u=window.prompt('URL','https://'); if(u) execCmd('createLink',u); return; } if(a==='unlink') return execCmd('unlink'); if(a==='h2') return formatBlock('h2'); if(a==='h3') return formatBlock('h3'); if(a==='p') return formatBlock('p'); if(a==='button') return insertButton(); if(a==='image') return insertImage(); }); }); // ──────────────────────────── // Detail page output refresh // ──────────────────────────── function refreshDetailOutput(){ detailOutput.value = generateDetailHtml(); btnCopyDetail.disabled = false; btnDownloadDetail.disabled = false; } // ──────────────────────────── // Load existing item into form // ──────────────────────────── function loadExisting(){ const idx = parseInt(existingSelect.value, 10); if(isNaN(idx)||!Array.isArray(window.ITEMS)||!window.ITEMS[idx]) return; const it = window.ITEMS[idx]; fieldTitle.value = it.title||''; fieldCategory.value = it.category||''; fieldDesc.value = it.description||''; fieldImage.value = it.image||''; fieldUrl.value = it.url||''; fieldButtonLabel.value = it.buttonLabel||''; fieldDetailLabel.value = it.detailLabel||''; let mode='none'; if(it.url && it.detailUrl) mode='both'; else if(it.url) mode='external'; else if(it.detailUrl) mode='internal'; setLinkMode(mode); updateSlug(); updateCardPreview(); syncDetailTitleFromCard(); saveDraft(); setStatus(statusItems,'Loaded item '+idx+' – '+( it.title||'untitled'),'ok'); } // ──────────────────────────── // Draft persistence // ──────────────────────────── function saveDraft(){ try{ localStorage.setItem(DRAFT_KEY, JSON.stringify({ title: fieldTitle.value, category: fieldCategory.value, desc: fieldDesc.value, image: fieldImage.value, url: fieldUrl.value, buttonLabel: fieldButtonLabel.value, detailLabel: fieldDetailLabel.value, linkMode: getLinkMode(), detailTitle: detailPageTitle.textContent, detailSubtitle: detailPageSubtitle.textContent, detailHtml: contentEditor.innerHTML })); }catch(e){} } function loadDraft(){ try{ const raw = localStorage.getItem(DRAFT_KEY); if(!raw) return; const d = JSON.parse(raw); fieldTitle.value = d.title||''; fieldCategory.value = d.category||''; fieldDesc.value = d.desc||''; fieldImage.value = d.image||''; fieldUrl.value = d.url||''; fieldButtonLabel.value = d.buttonLabel||''; fieldDetailLabel.value = d.detailLabel||''; if(d.linkMode) setLinkMode(d.linkMode); if(d.detailTitle){ detailPageTitle.textContent=d.detailTitle; detailPageTitle.dataset.userEdited='1'; } if(d.detailSubtitle){ detailPageSubtitle.textContent=d.detailSubtitle; detailPageSubtitle.dataset.userEdited='1'; } if(d.detailHtml){ contentEditor.innerHTML=d.detailHtml; } }catch(e){} } // ──────────────────────────── // Desktop / mobile preview toggle // ──────────────────────────── function setPreviewMode(mode){ detailPreviewShell.classList.toggle('mobile', mode==='mobile'); desktopPreviewBtn.classList.toggle('active', mode==='desktop'); mobilePreviewBtn.classList.toggle('active', mode==='mobile'); } desktopPreviewBtn.addEventListener('click',()=>setPreviewMode('desktop')); mobilePreviewBtn.addEventListener('click', ()=>setPreviewMode('mobile')); // ──────────────────────────── // Event wiring // ──────────────────────────── const cardFields = [fieldTitle,fieldCategory,fieldDesc,fieldImage,fieldUrl,fieldButtonLabel,fieldDetailLabel]; cardFields.forEach(inp=>{ inp.addEventListener('input',()=>{ updateSlug(); updateCardPreview(); syncDetailTitleFromCard(); saveDraft(); }); }); document.querySelectorAll('input[name="linkMode"]').forEach(r=>{ r.addEventListener('change',()=>{ updateCardPreview(); saveDraft(); }); }); existingSelect.addEventListener('change',loadExisting); btnBlank.addEventListener('click', e=>{ e.preventDefault(); fieldTitle.value=fieldCategory.value=fieldDesc.value=fieldImage.value=''; fieldUrl.value=fieldButtonLabel.value=fieldDetailLabel.value=''; existingSelect.value=''; setLinkMode('external'); delete detailPageTitle.dataset.userEdited; delete detailPageSubtitle.dataset.userEdited; detailPageTitle.textContent='Page title'; detailPageSubtitle.textContent='Subtitle or tagline'; contentEditor.innerHTML='

Write your detail content here…

'; updateSlug(); updateCardPreview(); saveDraft(); setStatus(statusItems,'Blank form ready.','ok'); }); btnGenerateItems.addEventListener('click', e=>{ e.preventDefault(); generateItemsJs(); }); btnCopyItems.addEventListener('click', e=>{ e.preventDefault(); if(!itemsOutput.value.trim()) return; navigator.clipboard ? navigator.clipboard.writeText(itemsOutput.value) : legacyCopy(itemsOutput.value); setStatus(statusItems,'items.js copied to clipboard.','ok'); }); btnRefreshItems.addEventListener('click', e=>{ e.preventDefault(); location.reload(); }); btnGenerateDetail.addEventListener('click', e=>{ e.preventDefault(); const mode = getLinkMode(); if(mode!=='internal'&&mode!=='both'){ setStatus(statusDetail,'Set link mode to internal or both to use the detail page.','warn'); return; } refreshDetailOutput(); setStatus(statusDetail,'Detail page HTML generated as '+updateSlug()+'.html','ok'); }); btnCopyDetail.addEventListener('click', e=>{ e.preventDefault(); if(!detailOutput.value.trim()) return; navigator.clipboard ? navigator.clipboard.writeText(detailOutput.value) : legacyCopy(detailOutput.value); setStatus(statusDetail,'Detail page HTML copied to clipboard.','ok'); }); btnDownloadDetail.addEventListener('click', e=>{ e.preventDefault(); if(!detailOutput.value.trim()) return; const slug = updateSlug(); const blob = new Blob([detailOutput.value],{type:'text/html;charset=utf-8'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href=url; a.download=slug+'.html'; a.click(); URL.revokeObjectURL(url); setStatus(statusDetail,'Downloaded '+slug+'.html','ok'); }); // save detail edits to draft [detailPageTitle,detailPageSubtitle,contentEditor].forEach(el=>{ el.addEventListener('input',()=>{ if(el!==contentEditor){ el.dataset.userEdited='1'; } saveDraft(); }); el.addEventListener('keyup', saveDraft); el.addEventListener('paste',()=>setTimeout(saveDraft,0)); }); function legacyCopy(text){ const ta=document.createElement('textarea'); ta.value=text; document.body.appendChild(ta); ta.select(); try{document.execCommand('copy');}catch(e){} document.body.removeChild(ta); } // ──────────────────────────── // Init // ──────────────────────────── window.addEventListener('load',()=>{ updateMeta(); itemsOutput.value = Array.isArray(window.ITEMS) ? 'window.ITEMS = '+JSON.stringify(window.ITEMS,null,2).replace(/"([a-zA-Z_][a-zA-Z0-9_]*)":/g,'$1:')+';\n' : '// window.ITEMS not found'; loadDraft(); updateSlug(); updateCardPreview(); setPreviewMode('desktop'); }); })();