-
+
@@ -1148,6 +1164,8 @@
};
const state = {
+ meta: { title: "", imdbId: "", omdbApiKey: "" },
+ darkMode: false,
templateMode: 'cover',
preset: 'dvd',
panelW: presets.dvd.panelW,
@@ -1243,6 +1261,9 @@
$('zoomOut').textContent = `${state.zoom}%`;
$('artworkMode').value = state.artworkMode;
$('showGuides').checked = state.showGuides;
+ $('darkMode').checked = !!state.darkMode;
+ $('titleField').value = state.meta?.title || "";
+ $('imdbField').value = state.meta?.imdbId || "";
$('showBleed').checked = state.showBleed;
$('showCrops').checked = state.showCrops;
$('printGuides').checked = state.printGuides;
@@ -1257,6 +1278,7 @@
document.body.classList.toggle('hide-crops', !state.showCrops);
document.body.classList.toggle('print-guides', state.printGuides);
document.body.classList.toggle('print-crops', state.printCrops);
+ document.body.classList.toggle('dark', !!state.darkMode);
}
function renderDimensions() {
@@ -1918,8 +1940,19 @@
render();
}
+ function exportPayload() {
+ return {
+ version: 2,
+ meta: state.meta,
+ ui: { zoom: state.zoom, darkMode: state.darkMode, showGuides: state.showGuides, showBleed: state.showBleed, showCrops: state.showCrops, printGuides: state.printGuides, printCrops: state.printCrops },
+ coverTemplate: { preset: state.preset, panelW: state.panelW, trimH: state.trimH, spineW: state.spineW, bleed: state.bleed, safe: state.safe, artworkMode: state.artworkMode, slots: state.slots, combined: state.combined },
+ discTemplate: { config: discTemplate, discs: state.discs },
+ layers: state.layers
+ };
+ }
+
function downloadProject() {
- const blob = new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' });
+ const blob = new Blob([JSON.stringify(exportPayload(), null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const date = new Date().toISOString().slice(0, 10);
@@ -1933,7 +1966,8 @@
function loadProjectFromText(text) {
const parsed = JSON.parse(text);
- const next = { ...state, ...parsed };
+ const source = parsed.version ? { ...parsed.ui, ...parsed.coverTemplate, discs: parsed.discTemplate?.discs, meta: parsed.meta, layers: parsed.layers } : parsed;
+ const next = { ...state, ...source };
next.slots = { ...state.slots, ...(parsed.slots || {}) };
next.combined = { ...state.combined, ...(parsed.combined || {}) };
next.discs = { ...state.discs, ...(parsed.discs || {}) };
@@ -1971,13 +2005,40 @@
render();
});
- for (const id of ['showGuides', 'showBleed', 'showCrops', 'printGuides', 'printCrops']) {
+ for (const id of ['showGuides', 'showBleed', 'showCrops', 'printGuides', 'printCrops','darkMode']) {
$(id).addEventListener('change', (event) => {
- state[id] = event.target.checked;
+ if (id === "darkMode") state.darkMode = event.target.checked; else state[id] = event.target.checked;
render();
});
}
+ $("titleField").addEventListener("input", (e)=>{state.meta.title=e.target.value; render();});
+ $("imdbField").addEventListener("input", (e)=>{state.meta.imdbId=e.target.value; render();});
+
+ $("imdbImportBtn").addEventListener("click", async ()=>{
+ const imdb = state.meta.imdbId.trim();
+ const key = prompt("OMDb API key:", state.meta.omdbApiKey || "");
+ if (!imdb || !key) return;
+ state.meta.omdbApiKey = key;
+ const res = await fetch(`https://www.omdbapi.com/?i=${encodeURIComponent(imdb)}&apikey=${encodeURIComponent(key)}`);
+ const data = await res.json();
+ if (data && data.Title) { state.meta.title = data.Title; $("titleField").value = data.Title; render(); } else alert("IMDb import failed");
+ });
+
+ $("nostrExportBtn").addEventListener("click", async ()=>{
+ if (!window.nostr) return alert("No Nostr extension found");
+ const evt={kind:30078,created_at:Math.floor(Date.now()/1000),tags:[["d", state.meta.imdbId||state.meta.title||"template"]],content:JSON.stringify(state)};
+ evt.pubkey = await window.nostr.getPublicKey();
+ const signed = await window.nostr.signEvent(evt);
+ await navigator.clipboard.writeText(JSON.stringify(signed));
+ alert("Signed Nostr event copied to clipboard");
+ });
+
+ $("nostrImportBtn").addEventListener("click", ()=>{
+ const raw = prompt("Paste Nostr event JSON"); if (!raw) return;
+ try { const evt = JSON.parse(raw); if (evt.content) loadProjectFromText(evt.content); } catch { alert("Bad event JSON"); }
+ });
+
$('printBtn').addEventListener('click', () => {
render();
window.print();
From 9ff07ff9a50b32c827a49b043eac8657a0f5b925 Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Thu, 14 May 2026 09:21:30 -0400
Subject: [PATCH 2/3] Add visible dark mode toggle and Nostr search modal with
template previews
---
src/index.html | 81 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 2 deletions(-)
diff --git a/src/index.html b/src/index.html
index ded5f5c..4dc3063 100644
--- a/src/index.html
+++ b/src/index.html
@@ -675,6 +675,15 @@
color: #475569;
}
+
+ .modal { position: fixed; inset: 0; background: rgba(0,0,0,.6); display:none; z-index:1000; }
+ .modal.open { display:block; }
+ .modalCard { width:min(900px,92vw); max-height:88vh; overflow:auto; margin:4vh auto; background:var(--panel); color:var(--ink); border:1px solid var(--line); border-radius:16px; padding:14px; }
+ .searchGrid{display:grid;grid-template-columns:1fr 140px 110px;gap:8px;align-items:end}
+ .results{display:grid;gap:8px;margin-top:10px}
+ .resultItem{display:grid;grid-template-columns:100px 1fr auto;gap:10px;align-items:center;border:1px solid var(--line);border-radius:10px;padding:8px;background:rgba(127,127,127,.07)}
+ .resultItem img{width:100px;height:70px;object-fit:cover;border-radius:6px;border:1px solid var(--line)}
+
@media (max-width: 900px) {
.app { grid-template-columns: 1fr; }
aside { position: relative; max-height: none; border-right: 0; border-bottom: 1px solid var(--line); }
@@ -840,7 +849,7 @@
-
+
IMDb import uses OMDb-compatible endpoint + API key.
@@ -1118,6 +1127,20 @@
+
+