diff --git a/gauge-designer.html b/gauge-designer.html
index 6ef5480..9faef17 100644
--- a/gauge-designer.html
+++ b/gauge-designer.html
@@ -291,7 +291,7 @@
-
+
3%
@@ -363,7 +363,7 @@
-
+
6%
@@ -377,7 +377,7 @@
-
+
3%
@@ -484,6 +484,10 @@
PDF prints at exact physical size.
Print at 100% — no scaling.
+
+
+
+
@@ -1018,6 +1022,87 @@ $('exportPngBtn').addEventListener('click', () => {
link.click();
});
+// ── save / load design ────────────────────────────────────────────────────────
+$('saveDesignBtn').addEventListener('click', () => {
+ const data = { version: 1, gaugeMode, sweepDir, zones: JSON.parse(JSON.stringify(zones)) };
+ document.querySelectorAll('#sidebar input, #sidebar select').forEach(el => {
+ if (!el.id || el.type === 'file') return;
+ data[el.id] = el.type === 'checkbox' ? el.checked : el.value;
+ });
+ if (logoImg) {
+ data.logoDataUrl = logoImg.src;
+ data.logoFileName = $('logoFileName').textContent;
+ }
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+ const a = document.createElement('a');
+ a.href = URL.createObjectURL(blob);
+ a.download = `${$('gaugeTitle').value || 'gauge'}-design.json`;
+ a.click();
+ URL.revokeObjectURL(a.href);
+});
+
+$('loadDesignBtn').addEventListener('click', () => $('loadDesignInput').click());
+
+$('loadDesignInput').addEventListener('change', e => {
+ const file = e.target.files[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = ev => {
+ let data;
+ try { data = JSON.parse(ev.target.result); } catch { alert('Invalid design file.'); return; }
+
+ // restore all inputs silently (no events yet)
+ document.querySelectorAll('#sidebar input, #sidebar select').forEach(el => {
+ if (!el.id || el.type === 'file' || !(el.id in data)) return;
+ if (el.type === 'checkbox') el.checked = data[el.id];
+ else el.value = data[el.id];
+ });
+
+ // update range display spans by firing input events on ranges
+ document.querySelectorAll('#sidebar input[type=range]').forEach(el => {
+ el.dispatchEvent(new Event('input'));
+ });
+
+ // restore gauge mode UI
+ gaugeMode = data.gaugeMode || 'standard';
+ if (gaugeMode === 'center') {
+ $('modeCtr').classList.add('active'); $('modeStd').classList.remove('active');
+ $('ctrFields').style.display = ''; $('stdFields').style.display = 'none';
+ $('ctrTickNote').style.display = ''; $('stdTickNote').style.display = 'none';
+ $('stdMajorInterval').style.display = 'none';
+ } else {
+ $('modeStd').classList.add('active'); $('modeCtr').classList.remove('active');
+ $('stdFields').style.display = ''; $('ctrFields').style.display = 'none';
+ $('stdTickNote').style.display = ''; $('ctrTickNote').style.display = 'none';
+ $('stdMajorInterval').style.display = '';
+ }
+
+ // restore sweep direction UI
+ sweepDir = data.sweepDir || 'cw';
+ if (sweepDir === 'ccw') {
+ $('dirCCW').classList.add('active'); $('dirCW').classList.remove('active');
+ } else {
+ $('dirCW').classList.add('active'); $('dirCCW').classList.remove('active');
+ }
+
+ // restore zones
+ zones = data.zones || [];
+ renderZones();
+
+ // restore logo
+ if (data.logoDataUrl) {
+ const img = new Image();
+ img.onload = () => { setLogo(img, data.logoFileName || 'logo'); drawGauge(); };
+ img.src = data.logoDataUrl;
+ } else {
+ clearLogo();
+ drawGauge();
+ }
+ };
+ reader.readAsText(file);
+ e.target.value = '';
+});
+
// ── preview background toggle ─────────────────────────────────────────────────
const previewArea = $('preview-area');