Refine mobile UI and simplify tool help
This commit is contained in:
parent
7e6ac4cf4b
commit
a5016b4fa7
51
index.html
51
index.html
@ -41,11 +41,6 @@
|
|||||||
<button type="button" class="tab-btn tab-idle" data-target="#tab-wall" aria-pressed="false">Wall</button>
|
<button type="button" class="tab-btn tab-idle" data-target="#tab-wall" aria-pressed="false">Wall</button>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex items-center gap-1 px-2 py-1 rounded-xl bg-white/70 border border-gray-200 shadow-sm" title="Active Color">
|
|
||||||
<div id="current-color-chip-global" class="current-color-chip">
|
|
||||||
<span id="current-color-label-global" class="text-[10px] font-semibold text-slate-700"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button id="app-fullscreen-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Toggle fullscreen">Fullscreen</button>
|
<button id="app-fullscreen-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Toggle fullscreen">Fullscreen</button>
|
||||||
<button id="clear-canvas-btn-top" class="btn-danger text-xs px-3 py-2">Start Fresh</button>
|
<button id="clear-canvas-btn-top" class="btn-danger text-xs px-3 py-2">Start Fresh</button>
|
||||||
</div>
|
</div>
|
||||||
@ -79,11 +74,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-2 mb-3">
|
<div class="grid grid-cols-3 gap-2 mb-3">
|
||||||
<button id="tool-undo" class="tool-btn" title="Ctrl+Z" aria-label="Undo">
|
<button id="tool-undo" class="tool-btn" aria-label="Undo">
|
||||||
<svg viewBox="0 0 24 24"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>
|
<svg viewBox="0 0 24 24"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>
|
||||||
<span class="hidden sm:inline">Undo</span>
|
<span class="hidden sm:inline">Undo</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="tool-redo" class="tool-btn" title="Ctrl+Y" aria-label="Redo">
|
<button id="tool-redo" class="tool-btn" aria-label="Redo">
|
||||||
<svg viewBox="0 0 24 24"><path d="M18.4 10.6C16.55 9 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/></svg>
|
<svg viewBox="0 0 24 24"><path d="M18.4 10.6C16.55 9 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/></svg>
|
||||||
<span class="hidden sm:inline">Redo</span>
|
<span class="hidden sm:inline">Redo</span>
|
||||||
</button>
|
</button>
|
||||||
@ -99,31 +94,17 @@
|
|||||||
<input id="garland-density" type="range" min="0.6" max="1.6" step="0.1" value="1" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
<input id="garland-density" type="range" min="0.6" max="1.6" step="0.1" value="1" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
||||||
<span id="garland-density-label" class="w-10 text-right text-xs text-gray-500">1.0</span>
|
<span id="garland-density-label" class="w-10 text-right text-xs text-gray-500">1.0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center justify-between">
|
||||||
<label for="garland-color-main1" class="font-medium w-24">Main A</label>
|
<span class="font-medium text-sm text-gray-700">Main Colors</span>
|
||||||
<select id="garland-color-main1" class="select text-sm flex-1"></select>
|
<button type="button" id="garland-add-color" class="btn-blue text-xs px-3 py-1">+ Add</button>
|
||||||
<span id="garland-swatch-main1" class="swatch tiny"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="garland-main-chips" class="flex flex-wrap gap-2"></div>
|
||||||
|
<p class="hint text-xs">Tap a chip to change it. You can add up to 10 main colors.</p>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label for="garland-color-main2" class="font-medium w-24">Main B</label>
|
<span class="font-medium text-sm text-gray-700">Accent</span>
|
||||||
<select id="garland-color-main2" class="select text-sm flex-1"></select>
|
<button type="button" id="garland-accent-chip" class="replace-chip" aria-label="Pick accent color"></button>
|
||||||
<span id="garland-swatch-main2" class="swatch tiny"></span>
|
<button type="button" id="garland-accent-clear" class="btn-yellow text-xs px-3 py-1">Clear</button>
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<label for="garland-color-main3" class="font-medium w-24">Main C</label>
|
|
||||||
<select id="garland-color-main3" class="select text-sm flex-1"></select>
|
|
||||||
<span id="garland-swatch-main3" class="swatch tiny"></span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<label for="garland-color-main4" class="font-medium w-24">Main D</label>
|
|
||||||
<select id="garland-color-main4" class="select text-sm flex-1"></select>
|
|
||||||
<span id="garland-swatch-main4" class="swatch tiny"></span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<label for="garland-color-accent" class="font-medium w-24">5" Accent</label>
|
|
||||||
<select id="garland-color-accent" class="select text-sm flex-1"></select>
|
|
||||||
<span id="garland-swatch-accent" class="swatch tiny"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -138,7 +119,7 @@
|
|||||||
<button id="duplicate-selected" class="btn-dark" disabled>Duplicate</button>
|
<button id="duplicate-selected" class="btn-dark" disabled>Duplicate</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="hint">Drag balloons to reposition. Use keyboard arrows for fine nudges.</p>
|
<p class="hint">Drag balloons to reposition. Use arrows/touches for fine nudges.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex items-center gap-2 text-xs text-gray-600">
|
<div class="mt-2 flex items-center gap-2 text-xs text-gray-600">
|
||||||
<span class="font-semibold">Resize</span>
|
<span class="font-semibold">Resize</span>
|
||||||
@ -185,7 +166,7 @@
|
|||||||
|
|
||||||
<div class="panel-heading mt-4">Color Library</div>
|
<div class="panel-heading mt-4">Color Library</div>
|
||||||
<div class="panel-card">
|
<div class="panel-card">
|
||||||
<p class="hint mb-2">Alt+click on canvas to sample a balloon’s color.</p>
|
<p class="hint mb-2">Tap or click on canvas to sample a balloon’s color (use the eyedropper).</p>
|
||||||
<div class="flex items-center gap-3 mb-2">
|
<div class="flex items-center gap-3 mb-2">
|
||||||
<span class="text-sm font-medium text-gray-700">Active Color</span>
|
<span class="text-sm font-medium text-gray-700">Active Color</span>
|
||||||
<div id="current-color-chip" class="current-color-chip">
|
<div id="current-color-chip" class="current-color-chip">
|
||||||
@ -447,7 +428,7 @@
|
|||||||
Show wireframe for empty spots
|
Show wireframe for empty spots
|
||||||
</label>
|
</label>
|
||||||
<label class="text-sm font-medium inline-flex items-center gap-2 col-span-2">
|
<label class="text-sm font-medium inline-flex items-center gap-2 col-span-2">
|
||||||
<input id="wall-outline" type="checkbox" class="align-middle">
|
<input id="wall-outline" type="checkbox" class="align-middle" checked>
|
||||||
Outline balloons
|
Outline balloons
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -464,7 +445,7 @@
|
|||||||
<span>Erase</span>
|
<span>Erase</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint mt-2 text-xs">Paint applies the active color; Erase clears. Hold Shift/Ctrl for temporary erase.</p>
|
<p class="hint mt-2 text-xs">Paint applies the active color; Erase clears. Hold modifier on desktop to erase temporarily.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -477,7 +458,7 @@
|
|||||||
<span id="wall-active-color-label" class="text-[10px] font-semibold text-slate-700"></span>
|
<span id="wall-active-color-label" class="text-[10px] font-semibold text-slate-700"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint mt-2">Tap a swatch to set. Tap a balloon to paint; tap again (same color) to clear. Alt+click (desktop) to pick.</p>
|
<p class="hint mt-2">Tap a swatch to set. Tap a balloon to paint; tap again (same color) to clear. Use the eyedropper to pick from the canvas.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-heading mt-4">Used Colors</div>
|
<div class="panel-heading mt-4">Used Colors</div>
|
||||||
<div class="panel-card">
|
<div class="panel-card">
|
||||||
|
|||||||
177
organic.js
177
organic.js
@ -138,16 +138,10 @@
|
|||||||
const fitViewBtn = document.getElementById('fit-view-btn');
|
const fitViewBtn = document.getElementById('fit-view-btn');
|
||||||
const garlandDensityInput = document.getElementById('garland-density');
|
const garlandDensityInput = document.getElementById('garland-density');
|
||||||
const garlandDensityLabel = document.getElementById('garland-density-label');
|
const garlandDensityLabel = document.getElementById('garland-density-label');
|
||||||
const garlandColorMain1Sel = document.getElementById('garland-color-main1');
|
const garlandMainChips = document.getElementById('garland-main-chips');
|
||||||
const garlandColorMain2Sel = document.getElementById('garland-color-main2');
|
const garlandAddColorBtn = document.getElementById('garland-add-color');
|
||||||
const garlandColorMain3Sel = document.getElementById('garland-color-main3');
|
const garlandAccentChip = document.getElementById('garland-accent-chip');
|
||||||
const garlandColorMain4Sel = document.getElementById('garland-color-main4');
|
const garlandAccentClearBtn = document.getElementById('garland-accent-clear');
|
||||||
const garlandColorAccentSel = document.getElementById('garland-color-accent');
|
|
||||||
const garlandSwatchMain1 = document.getElementById('garland-swatch-main1');
|
|
||||||
const garlandSwatchMain2 = document.getElementById('garland-swatch-main2');
|
|
||||||
const garlandSwatchMain3 = document.getElementById('garland-swatch-main3');
|
|
||||||
const garlandSwatchMain4 = document.getElementById('garland-swatch-main4');
|
|
||||||
const garlandSwatchAccent = document.getElementById('garland-swatch-accent');
|
|
||||||
const garlandControls = document.getElementById('garland-controls');
|
const garlandControls = document.getElementById('garland-controls');
|
||||||
|
|
||||||
const sizePresetGroup = document.getElementById('size-preset-group');
|
const sizePresetGroup = document.getElementById('size-preset-group');
|
||||||
@ -214,8 +208,8 @@
|
|||||||
let usedSortDesc = true;
|
let usedSortDesc = true;
|
||||||
let garlandPath = [];
|
let garlandPath = [];
|
||||||
let garlandDensity = parseFloat(garlandDensityInput?.value || '1') || 1;
|
let garlandDensity = parseFloat(garlandDensityInput?.value || '1') || 1;
|
||||||
let garlandMainIdx = [0, 0, 0, 0];
|
let garlandMainIdx = [0];
|
||||||
let garlandAccentIdx = 0;
|
let garlandAccentIdx = -1;
|
||||||
let lastCommitMode = '';
|
let lastCommitMode = '';
|
||||||
let lastAddStatus = '';
|
let lastAddStatus = '';
|
||||||
let evtStats = { down: 0, up: 0, cancel: 0, touchEnd: 0, addBalloon: 0, addGarland: 0, lastType: '' };
|
let evtStats = { down: 0, up: 0, cancel: 0, touchEnd: 0, addBalloon: 0, addGarland: 0, lastType: '' };
|
||||||
@ -234,11 +228,11 @@
|
|||||||
const canRedo = historyPointer < historyStack.length - 1;
|
const canRedo = historyPointer < historyStack.length - 1;
|
||||||
if (toolUndoBtn) {
|
if (toolUndoBtn) {
|
||||||
toolUndoBtn.disabled = !canUndo;
|
toolUndoBtn.disabled = !canUndo;
|
||||||
toolUndoBtn.title = canUndo ? 'Undo (Ctrl+Z)' : 'Nothing to undo';
|
toolUndoBtn.title = canUndo ? 'Undo' : 'Nothing to undo';
|
||||||
}
|
}
|
||||||
if (toolRedoBtn) {
|
if (toolRedoBtn) {
|
||||||
toolRedoBtn.disabled = !canRedo;
|
toolRedoBtn.disabled = !canRedo;
|
||||||
toolRedoBtn.title = canRedo ? 'Redo (Ctrl+Y)' : 'Nothing to redo';
|
toolRedoBtn.title = canRedo ? 'Redo' : 'Nothing to redo';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -997,8 +991,8 @@
|
|||||||
if (garlandDensityLabel) garlandDensityLabel.textContent = garlandDensity.toFixed(1);
|
if (garlandDensityLabel) garlandDensityLabel.textContent = garlandDensity.toFixed(1);
|
||||||
}
|
}
|
||||||
if (Array.isArray(s.garlandMainIdx)) {
|
if (Array.isArray(s.garlandMainIdx)) {
|
||||||
garlandMainIdx = s.garlandMainIdx.slice(0, 4).map(v => Number(v) || -1);
|
garlandMainIdx = s.garlandMainIdx.slice(0, 10).map(v => Number.isInteger(v) ? v : -1).filter((v, i) => i < 10);
|
||||||
while (garlandMainIdx.length < 4) garlandMainIdx.push(-1);
|
if (!garlandMainIdx.length) garlandMainIdx = [selectedColorIdx];
|
||||||
}
|
}
|
||||||
if (typeof s.garlandAccentIdx === 'number') garlandAccentIdx = s.garlandAccentIdx;
|
if (typeof s.garlandAccentIdx === 'number') garlandAccentIdx = s.garlandAccentIdx;
|
||||||
if (typeof s.isBorderEnabled === 'boolean') isBorderEnabled = s.isBorderEnabled;
|
if (typeof s.isBorderEnabled === 'boolean') isBorderEnabled = s.isBorderEnabled;
|
||||||
@ -1010,6 +1004,109 @@
|
|||||||
loadAppState();
|
loadAppState();
|
||||||
resetHistory(); // establish initial history state for undo/redo controls
|
resetHistory(); // establish initial history state for undo/redo controls
|
||||||
|
|
||||||
|
// ====== Garland color UI (dynamic chips) ======
|
||||||
|
const styleChip = (el, meta) => {
|
||||||
|
if (!el || !meta) return;
|
||||||
|
if (meta.image) {
|
||||||
|
el.style.backgroundImage = `url("${meta.image}")`;
|
||||||
|
el.style.backgroundColor = meta.hex || '#fff';
|
||||||
|
el.style.backgroundSize = `${100 * SWATCH_TEXTURE_ZOOM}%`;
|
||||||
|
el.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
|
||||||
|
} else {
|
||||||
|
el.style.backgroundImage = 'none';
|
||||||
|
el.style.backgroundColor = meta.hex || '#f1f5f9';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const garlandMaxColors = 10;
|
||||||
|
function renderGarlandMainChips() {
|
||||||
|
if (!garlandMainChips) return;
|
||||||
|
garlandMainChips.innerHTML = '';
|
||||||
|
const items = garlandMainIdx.length ? garlandMainIdx : [selectedColorIdx];
|
||||||
|
items.forEach((idx, i) => {
|
||||||
|
const wrap = document.createElement('div');
|
||||||
|
wrap.className = 'flex items-center gap-1';
|
||||||
|
const chip = document.createElement('button');
|
||||||
|
chip.type = 'button';
|
||||||
|
chip.className = 'replace-chip garland-chip';
|
||||||
|
const meta = FLAT_COLORS[idx] || FLAT_COLORS[selectedColorIdx] || FLAT_COLORS[0];
|
||||||
|
styleChip(chip, meta);
|
||||||
|
chip.title = meta?.name || meta?.hex || 'Color';
|
||||||
|
chip.addEventListener('click', () => {
|
||||||
|
if (!window.openColorPicker) return;
|
||||||
|
window.openColorPicker({
|
||||||
|
title: 'Path color',
|
||||||
|
subtitle: 'Pick a main color',
|
||||||
|
items: (FLAT_COLORS || []).map((c, ci) => ({ label: c.name || c.hex, metaText: c.family || '', idx: ci })),
|
||||||
|
onSelect: (item) => {
|
||||||
|
garlandMainIdx[i] = item.idx;
|
||||||
|
renderGarlandMainChips();
|
||||||
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.type = 'button';
|
||||||
|
removeBtn.className = 'btn-yellow text-xs px-2 py-1';
|
||||||
|
removeBtn.textContent = '×';
|
||||||
|
removeBtn.title = 'Remove color';
|
||||||
|
removeBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
garlandMainIdx.splice(i, 1);
|
||||||
|
if (!garlandMainIdx.length) garlandMainIdx.push(selectedColorIdx);
|
||||||
|
renderGarlandMainChips();
|
||||||
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
|
});
|
||||||
|
wrap.appendChild(chip);
|
||||||
|
wrap.appendChild(removeBtn);
|
||||||
|
garlandMainChips.appendChild(wrap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
garlandAddColorBtn?.addEventListener('click', () => {
|
||||||
|
if (garlandMainIdx.length >= garlandMaxColors) { showModal(`Max ${garlandMaxColors} colors.`); return; }
|
||||||
|
if (!window.openColorPicker) return;
|
||||||
|
window.openColorPicker({
|
||||||
|
title: 'Add path color',
|
||||||
|
subtitle: 'Choose a main color',
|
||||||
|
items: (FLAT_COLORS || []).map((c, ci) => ({ label: c.name || c.hex, metaText: c.family || '', idx: ci })),
|
||||||
|
onSelect: (item) => {
|
||||||
|
garlandMainIdx.push(item.idx);
|
||||||
|
renderGarlandMainChips();
|
||||||
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateAccentChip = () => {
|
||||||
|
if (!garlandAccentChip) return;
|
||||||
|
const meta = garlandAccentIdx >= 0 ? FLAT_COLORS[garlandAccentIdx] : null;
|
||||||
|
styleChip(garlandAccentChip, meta || { hex: '#f8fafc' });
|
||||||
|
};
|
||||||
|
garlandAccentChip?.addEventListener('click', () => {
|
||||||
|
if (!window.openColorPicker) return;
|
||||||
|
window.openColorPicker({
|
||||||
|
title: 'Accent color',
|
||||||
|
subtitle: 'Choose a 5" accent color',
|
||||||
|
items: (FLAT_COLORS || []).map((c, ci) => ({ label: c.name || c.hex, metaText: c.family || '', idx: ci })),
|
||||||
|
onSelect: (item) => {
|
||||||
|
garlandAccentIdx = item.idx;
|
||||||
|
updateAccentChip();
|
||||||
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
garlandAccentClearBtn?.addEventListener('click', () => {
|
||||||
|
garlandAccentIdx = -1;
|
||||||
|
updateAccentChip();
|
||||||
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
|
});
|
||||||
|
|
||||||
// ====== UI Rendering (Palettes) ======
|
// ====== UI Rendering (Palettes) ======
|
||||||
function renderAllowedPalette() {
|
function renderAllowedPalette() {
|
||||||
if (!paletteBox) return;
|
if (!paletteBox) return;
|
||||||
@ -1789,31 +1886,13 @@
|
|||||||
if (mode === 'garland') requestDraw();
|
if (mode === 'garland') requestDraw();
|
||||||
persist();
|
persist();
|
||||||
});
|
});
|
||||||
const handleGarlandColorChange = () => {
|
const refreshGarlandColors = () => {
|
||||||
updateGarlandSwatches();
|
renderGarlandMainChips();
|
||||||
persist();
|
updateAccentChip();
|
||||||
if (mode === 'garland') requestDraw();
|
if (mode === 'garland') requestDraw();
|
||||||
|
persist();
|
||||||
};
|
};
|
||||||
garlandColorMain1Sel?.addEventListener('change', e => {
|
refreshGarlandColors();
|
||||||
garlandMainIdx[0] = parseInt(e.target.value, 10) || -1;
|
|
||||||
handleGarlandColorChange();
|
|
||||||
});
|
|
||||||
garlandColorMain2Sel?.addEventListener('change', e => {
|
|
||||||
garlandMainIdx[1] = parseInt(e.target.value, 10) || -1;
|
|
||||||
handleGarlandColorChange();
|
|
||||||
});
|
|
||||||
garlandColorMain3Sel?.addEventListener('change', e => {
|
|
||||||
garlandMainIdx[2] = parseInt(e.target.value, 10) || -1;
|
|
||||||
handleGarlandColorChange();
|
|
||||||
});
|
|
||||||
garlandColorMain4Sel?.addEventListener('change', e => {
|
|
||||||
garlandMainIdx[3] = parseInt(e.target.value, 10) || -1;
|
|
||||||
handleGarlandColorChange();
|
|
||||||
});
|
|
||||||
garlandColorAccentSel?.addEventListener('change', e => {
|
|
||||||
garlandAccentIdx = parseInt(e.target.value, 10) || -1;
|
|
||||||
handleGarlandColorChange();
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteSelectedBtn?.addEventListener('click', deleteSelected);
|
deleteSelectedBtn?.addEventListener('click', deleteSelected);
|
||||||
duplicateSelectedBtn?.addEventListener('click', duplicateSelected);
|
duplicateSelectedBtn?.addEventListener('click', duplicateSelected);
|
||||||
@ -1957,26 +2036,6 @@
|
|||||||
updateGarlandSwatches();
|
updateGarlandSwatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGarlandSwatches() {
|
|
||||||
const setSw = (sw, idx) => {
|
|
||||||
if (!sw) return;
|
|
||||||
const meta = idx >= 0 ? FLAT_COLORS[idx] : null;
|
|
||||||
if (meta?.image) {
|
|
||||||
sw.style.backgroundImage = `url("${meta.image}")`;
|
|
||||||
sw.style.backgroundColor = meta.hex || '#fff';
|
|
||||||
sw.style.backgroundSize = 'cover';
|
|
||||||
} else {
|
|
||||||
sw.style.backgroundImage = 'none';
|
|
||||||
sw.style.backgroundColor = meta?.hex || '#f1f5f9';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setSw(garlandSwatchMain1, garlandMainIdx[0]);
|
|
||||||
setSw(garlandSwatchMain2, garlandMainIdx[1]);
|
|
||||||
setSw(garlandSwatchMain3, garlandMainIdx[2]);
|
|
||||||
setSw(garlandSwatchMain4, garlandMainIdx[3]);
|
|
||||||
setSw(garlandSwatchAccent, garlandAccentIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateReplaceChips = () => {
|
const updateReplaceChips = () => {
|
||||||
const fromHex = replaceFromSel?.value;
|
const fromHex = replaceFromSel?.value;
|
||||||
const toIdx = parseInt(replaceToSel?.value || '-1', 10);
|
const toIdx = parseInt(replaceToSel?.value || '-1', 10);
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// Ensure shared helpers are ready
|
// Ensure shared helpers are ready
|
||||||
if (!window.shared) return;
|
if (!window.shared) return;
|
||||||
const { clamp, clamp01 } = window.shared;
|
const { clamp, clamp01, SWATCH_TEXTURE_ZOOM } = window.shared;
|
||||||
const { FLAT_COLORS } = window.shared;
|
const { FLAT_COLORS } = window.shared;
|
||||||
|
|
||||||
// Modal helpers
|
// Modal helpers
|
||||||
@ -58,9 +58,10 @@
|
|||||||
const setChipStyle = (el, meta) => {
|
const setChipStyle = (el, meta) => {
|
||||||
if (!el || !meta) return;
|
if (!el || !meta) return;
|
||||||
if (meta.image) {
|
if (meta.image) {
|
||||||
|
const zoom = Math.max(1, meta.imageZoom ?? SWATCH_TEXTURE_ZOOM ?? 2.5);
|
||||||
el.style.backgroundImage = `url("${meta.image}")`;
|
el.style.backgroundImage = `url("${meta.image}")`;
|
||||||
el.style.backgroundColor = meta.hex || '#fff';
|
el.style.backgroundColor = meta.hex || '#fff';
|
||||||
el.style.backgroundSize = 'cover';
|
el.style.backgroundSize = `${100 * zoom}%`;
|
||||||
el.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
|
el.style.backgroundPosition = `${(meta.imageFocus?.x ?? 0.5) * 100}% ${(meta.imageFocus?.y ?? 0.5) * 100}%`;
|
||||||
} else {
|
} else {
|
||||||
el.style.backgroundImage = 'none';
|
el.style.backgroundImage = 'none';
|
||||||
@ -526,7 +527,8 @@
|
|||||||
const isMobileView = () => window.matchMedia('(max-width: 1023px)').matches;
|
const isMobileView = () => window.matchMedia('(max-width: 1023px)').matches;
|
||||||
const updateMobileActionBarVisibility = () => {
|
const updateMobileActionBarVisibility = () => {
|
||||||
if (!mobileActionBar) return;
|
if (!mobileActionBar) return;
|
||||||
const shouldShow = current === '#tab-organic' && isMobileView();
|
const modalOpen = !!document.querySelector('.color-modal:not(.hidden)');
|
||||||
|
const shouldShow = current === '#tab-organic' && isMobileView() && !modalOpen;
|
||||||
mobileActionBar.classList.toggle('hidden', !shouldShow);
|
mobileActionBar.classList.toggle('hidden', !shouldShow);
|
||||||
};
|
};
|
||||||
const wireMobileActionButtons = () => {
|
const wireMobileActionButtons = () => {
|
||||||
|
|||||||
14
style.css
14
style.css
@ -446,7 +446,7 @@ body[data-active-tab="#tab-wall"] #clear-canvas-btn-top {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.control-sheet { bottom: 4.5rem; max-height: 55vh; }
|
.control-sheet { bottom: 4.5rem; max-height: 55vh; }
|
||||||
.control-sheet.minimized { transform: translateY(95%); }
|
.control-sheet.minimized { transform: translateY(115%); }
|
||||||
|
|
||||||
/* Larger tap targets and spacing */
|
/* Larger tap targets and spacing */
|
||||||
.tool-btn,
|
.tool-btn,
|
||||||
@ -480,7 +480,7 @@ body[data-active-tab="#tab-wall"] #clear-canvas-btn-top {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
z-index: 45;
|
z-index: 20; /* below control sheets (30) and modals (60) */
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-modal {
|
.color-modal {
|
||||||
@ -624,6 +624,16 @@ body[data-active-tab="#tab-wall"] #clear-canvas-btn-top {
|
|||||||
box-shadow: 0 -6px 30px rgba(15, 23, 42, 0.12);
|
box-shadow: 0 -6px 30px rgba(15, 23, 42, 0.12);
|
||||||
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
||||||
}
|
}
|
||||||
|
.mobile-tabbar.hidden { display: none; }
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
/* Tuck canvases above the tabbar */
|
||||||
|
#classic-display,
|
||||||
|
#wall-display,
|
||||||
|
#balloon-canvas {
|
||||||
|
margin-bottom: 5.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
.mobile-tabbar .mobile-tab-btn {
|
.mobile-tabbar .mobile-tab-btn {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
12
wall.js
12
wall.js
@ -92,7 +92,7 @@
|
|||||||
|
|
||||||
function wallDefaultState() {
|
function wallDefaultState() {
|
||||||
// Default to wireframes on so empty cells are visible/clickable.
|
// Default to wireframes on so empty cells are visible/clickable.
|
||||||
return { rows: 7, cols: 9, spacing: 75, bigSize: 52, pattern: 'grid', fillGaps: false, showWireframes: true, outline: false, colors: [], customColors: {}, patternStore: {}, activeColorIdx: 0 };
|
return { rows: 7, cols: 9, spacing: 75, bigSize: 52, pattern: 'grid', fillGaps: false, showWireframes: true, outline: true, colors: [], customColors: {}, patternStore: {}, activeColorIdx: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build FLAT_COLORS locally if shared failed to populate (e.g., palette not ready)
|
// Build FLAT_COLORS locally if shared failed to populate (e.g., palette not ready)
|
||||||
@ -439,8 +439,8 @@
|
|||||||
const meta = wallColorMeta(gapIdx);
|
const meta = wallColorMeta(gapIdx);
|
||||||
const patId = ensurePattern(meta);
|
const patId = ensurePattern(meta);
|
||||||
const fill = invisible ? hitFill : (patId ? `url(#${patId})` : meta.hex);
|
const fill = invisible ? hitFill : (patId ? `url(#${patId})` : meta.hex);
|
||||||
const stroke = 'none';
|
const stroke = invisible || isEmpty ? 'none' : (showOutline ? '#111827' : 'none');
|
||||||
const strokeW = 0;
|
const strokeW = invisible || isEmpty ? 0 : (showOutline ? 0.6 : 0);
|
||||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||||
const rGap = bigR * 0.82; // slightly smaller 11" gap balloon
|
const rGap = bigR * 0.82; // slightly smaller 11" gap balloon
|
||||||
const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
|
const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
|
||||||
@ -498,9 +498,9 @@
|
|||||||
const patId = ensurePattern(meta);
|
const patId = ensurePattern(meta);
|
||||||
const fill = invisibleLink ? 'rgba(0,0,0,0.001)' : (linkIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
const fill = invisibleLink ? 'rgba(0,0,0,0.001)' : (linkIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||||
console.log(`l#-r-c: keyId: ${linkKey}, customIdx: ${linkCustomIdx}, isEmpty: ${linkIsEmpty}, invisible: ${invisibleLink}, fill: ${fill}, meta:`, meta);
|
console.log(`l#-r-c: keyId: ${linkKey}, customIdx: ${linkCustomIdx}, isEmpty: ${linkIsEmpty}, invisible: ${invisibleLink}, fill: ${fill}, meta:`, meta);
|
||||||
// Always outline X-pattern link ovals; thicken when outline toggle is on.
|
// Outline only when filled; light wireframe when empty and wireframes shown.
|
||||||
const stroke = invisibleLink ? 'none' : (showOutline ? '#111827' : '#cbd5e1');
|
const stroke = invisibleLink ? 'none' : (linkIsEmpty ? (showWireframes ? '#cbd5e1' : 'none') : (showOutline ? '#111827' : 'none'));
|
||||||
const strokeW = invisibleLink ? 0 : (showOutline ? 0.8 : 0.6);
|
const strokeW = invisibleLink ? 0 : (linkIsEmpty ? (showWireframes ? 1.2 : 0) : (showOutline ? 0.8 : 0));
|
||||||
const filter = invisibleLink || linkIsEmpty ? '' : `filter="url(#${bigShadow})"`;
|
const filter = invisibleLink || linkIsEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||||
const shine = linkIsEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
|
const shine = linkIsEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user