From 8bb46389f39de9ef07461059b767b5f2c97f8ed5 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 18 Dec 2025 15:26:06 -0500 Subject: [PATCH] Make number toppers tint via SVG masks --- classic.js | 211 ++++++++++++++++++++--------------------- index.html | 233 +++++++++++++++++++++++----------------------- output_webp/0.svg | 58 ++++++++++++ output_webp/1.svg | 59 ++++++++++++ output_webp/2.svg | 58 ++++++++++++ output_webp/3.svg | 58 ++++++++++++ output_webp/4.svg | 57 ++++++++++++ output_webp/5.svg | 57 ++++++++++++ output_webp/6.svg | 58 ++++++++++++ output_webp/7.svg | 57 ++++++++++++ output_webp/8.svg | 57 ++++++++++++ output_webp/9.svg | 57 ++++++++++++ svg.sh | 18 ++++ 13 files changed, 809 insertions(+), 229 deletions(-) create mode 100644 output_webp/0.svg create mode 100644 output_webp/1.svg create mode 100644 output_webp/2.svg create mode 100644 output_webp/3.svg create mode 100644 output_webp/4.svg create mode 100644 output_webp/5.svg create mode 100644 output_webp/6.svg create mode 100644 output_webp/7.svg create mode 100644 output_webp/8.svg create mode 100644 output_webp/9.svg create mode 100755 svg.sh diff --git a/classic.js b/classic.js index 9c32471..3567476 100644 --- a/classic.js +++ b/classic.js @@ -78,8 +78,6 @@ const PALETTE_KEY = 'classic:colors:v2'; const TOPPER_COLOR_KEY = 'classic:topperColor:v2'; const CLASSIC_STATE_KEY = 'classic:state:v1'; - const NUMBER_TINT_COLOR_KEY = 'classic:numberTintColor:v1'; - const NUMBER_TINT_OPACITY_KEY = 'classic:numberTintOpacity:v1'; const MANUAL_MODE_KEY = 'classic:manualMode:v1'; const MANUAL_OVERRIDES_KEY = 'classic:manualOverrides:v1'; const MANUAL_EXPANDED_KEY = 'classic:manualExpanded:v1'; @@ -103,7 +101,7 @@ { hex: '#0055a4', image: null }, { hex: '#40e0d0', image: null }, { hex: '#fcd34d', image: null } ]; - const defaultTopper = () => ({ hex: '#a18b67', image: 'images/chrome-gold.webp' }); + const defaultTopper = () => ({ hex: '#E32636', image: 'images/chrome-gold.webp' }); // Classic gold function getClassicColors() { let arr = defaultColors(); @@ -144,30 +142,6 @@ const clean = { hex: normHex(colorObj.hex), image: colorObj.image || null }; try { localStorage.setItem(TOPPER_COLOR_KEY, JSON.stringify(clean)); } catch {} } - function getNumberTintColor() { - try { - const saved = JSON.parse(localStorage.getItem(NUMBER_TINT_COLOR_KEY)); - if (saved && saved.hex) return normHex(saved.hex); - } catch {} - return '#ffffff'; - } - function setNumberTintColor(hex) { - const clean = normHex(hex || '#ffffff'); - try { localStorage.setItem(NUMBER_TINT_COLOR_KEY, JSON.stringify({ hex: clean })); } catch {} - return clean; - } - function getNumberTintOpacity() { - try { - const saved = parseFloat(localStorage.getItem(NUMBER_TINT_OPACITY_KEY)); - if (!isNaN(saved)) return clamp01(saved); - } catch {} - return 1; // default to full tint so number color changes are obvious - } - function setNumberTintOpacity(v) { - const clamped = clamp01(parseFloat(v)); - try { localStorage.setItem(NUMBER_TINT_OPACITY_KEY, String(clamped)); } catch {} - return clamped; - } function loadManualMode() { try { const saved = JSON.parse(localStorage.getItem(MANUAL_MODE_KEY)); @@ -307,8 +281,6 @@ let topperOffsetX_Px = 0; let topperOffsetY_Px = 0; let topperSizeMultiplier = 1; - let numberTintHex = getNumberTintColor(); - let numberTintOpacity = getNumberTintOpacity(); let shineEnabled = true; let borderEnabled = false; let manualMode = loadManualMode(); @@ -332,8 +304,6 @@ setTopperOffsetX(val) { topperOffsetX_Px = (Number(val) || 0) * 5; }, setTopperOffsetY(val) { topperOffsetY_Px = (Number(val) || 0) * -5; }, setTopperSize(multiplier) { topperSizeMultiplier = Number(multiplier) || 1; }, - setNumberTintHex(hex) { numberTintHex = setNumberTintColor(hex); }, - setNumberTintOpacity(val) { numberTintOpacity = setNumberTintOpacity(val); }, setShineEnabled(on) { shineEnabled = !!on; }, setBorderEnabled(on) { borderEnabled = !!on; }, setManualMode(on) { manualMode = !!on; saveManualMode(manualMode); }, @@ -402,24 +372,31 @@ const isNumTopper = cell.isTopper && (model.topperType || '').startsWith('num-'); if (base.image) { const w = base.width || 1, h = base.height || 1; - if (!isNumTopper) { - kids.push(svg('image', { href: base.image, x: -w/2, y: -h/2, width: w, height: h, preserveAspectRatio: base.preserveAspectRatio || 'xMidYMid meet', style: 'pointer-events:none' })); - } - const tintColor = model.numberTintHex || '#ffffff'; - const tintOpacity = model.numberTintOpacity || 0; - if (tintOpacity > 0 && isNumTopper) { + if (isNumTopper) { + // Use the SVG alpha as a mask and fill with the selected topper color. const maskId = `mask-${id}`; - kids.push(svg('mask', { id: maskId, maskUnits: 'userSpaceOnUse' }, [ + kids.push(svg('mask', { id: maskId, maskUnits: 'userSpaceOnUse', maskContentUnits: 'userSpaceOnUse', 'mask-type': 'alpha' }, [ svg('image', { href: base.image, x: -w/2, y: -h/2, width: w, height: h, preserveAspectRatio: base.preserveAspectRatio || 'xMidYMid meet', style: 'pointer-events:none' }) ])); kids.push(svg('rect', { x: -w/2, y: -h/2, width: w, height: h, - fill: tintColor, opacity: tintOpacity, + fill: model.topperColor?.hex || '#ffffff', + opacity: 1, mask: `url(#${maskId})`, - style: 'mix-blend-mode:multiply; pointer-events:none' + style: 'pointer-events:none' })); - // Also draw the image beneath with zero opacity to keep mask refs consistent - kids.push(svg('image', { href: base.image, x: -w/2, y: -h/2, width: w, height: h, preserveAspectRatio: base.preserveAspectRatio || 'xMidYMid meet', style: 'pointer-events:none;opacity:0' })); + // Overlay the original SVG lightly to keep stroke/detail without overpowering the fill. + kids.push(svg('image', { + href: base.image, + x: -w/2, + y: -h/2, + width: w, + height: h, + preserveAspectRatio: base.preserveAspectRatio || 'xMidYMid meet', + style: 'pointer-events:none;mix-blend-mode:multiply;opacity:0.4' + })); + } else { + kids.push(svg('image', { href: base.image, x: -w/2, y: -h/2, width: w, height: h, preserveAspectRatio: base.preserveAspectRatio || 'xMidYMid meet', style: 'pointer-events:none' })); } } else if (Array.isArray(base.paths)) { base.paths.forEach(p => { @@ -506,7 +483,12 @@ function distinctPaletteSlots(palette) { return slots.slice(0, limit); })(); - const colorBlock4 = [[1, 2, 3, 4], [3, 1, 4, 2], [4, 3, 2, 1], [2, 4, 1, 3]]; + const colorBlock4 = [ + [1, 2, 4, 3], + [4, 1, 3, 2], + [3, 4, 2, 1], + [2, 3, 1, 4], + ]; const colorBlock5 = [ [5, 2, 3, 4, 1], @@ -808,8 +790,6 @@ function distinctPaletteSlots(palette) { topperColor: getTopperColor(), topperType, shineEnabled, - numberTintHex, - numberTintOpacity, manualMode, manualFocusEnabled, manualFloatingQuad, @@ -893,12 +873,13 @@ function distinctPaletteSlots(palette) { const shapes = {}; const topperSize = 9.5; // ≈34" foil height when base balloons are ~11" Object.keys({ ...fallbackPaths, ...NUMBER_IMAGE_MAP }).forEach(num => { + const fallback = fallbackPaths[num]; const img = NUMBER_IMAGE_MAP[num]; - const hasImage = !!img; shapes[`topper-num-${num}`] = { - base: hasImage - ? { type: 'image', image: img, width: 1, height: 1, radius: 0.9, allowShine: false, transform: 'scale(0.9)' } - : { type: 'path', paths: [{ d: fallbackPaths[num].d, fillRule: fallbackPaths[num].fillRule || 'nonzero' }], radius: r, allowShine: true, transform: baseTransform }, + // Prefer provided SVG image for numbers so we keep the photo look. + base: img + ? { type: 'image', image: img, width: 1, height: 1, radius: 0.9, allowShine: false, preserveAspectRatio: 'xMidYMid meet', transform: 'scale(0.9)' } + : { type: 'path', paths: [{ d: fallback.d, fillRule: fallback.fillRule || 'nonzero' }], radius: r, allowShine: true, transform: baseTransform }, size: topperSize }; }); @@ -1017,6 +998,8 @@ function distinctPaletteSlots(palette) { function initClassicColorPicker(onColorChange) { const slotsContainer = document.getElementById('classic-slots'), topperSwatch = document.getElementById('classic-topper-color-swatch'), swatchGrid = document.getElementById('classic-swatch-grid'), activeLabel = document.getElementById('classic-active-label'), randomizeBtn = document.getElementById('classic-randomize-colors'), addSlotBtn = document.getElementById('classic-add-slot'), activeChip = document.getElementById('classic-active-chip'), floatingChip = document.getElementById('classic-active-chip-floating'), activeDot = document.getElementById('classic-active-dot'), floatingDot = document.getElementById('classic-active-dot-floating'); + const projectBlock = document.getElementById('classic-project-block'); + const replaceBlock = document.getElementById('classic-replace-block'); const replaceFromSel = document.getElementById('classic-replace-from'); const replaceToSel = document.getElementById('classic-replace-to'); const replaceBtn = document.getElementById('classic-replace-btn'); @@ -1024,9 +1007,8 @@ function distinctPaletteSlots(palette) { const replaceFromChip = document.getElementById('classic-replace-from-chip'); const replaceToChip = document.getElementById('classic-replace-to-chip'); const replaceCountLabel = document.getElementById('classic-replace-count'); - const numberTintSlider = document.getElementById('classic-number-tint'); const topperBlock = document.getElementById('classic-topper-color-block'); - if (!slotsContainer || !topperSwatch || !swatchGrid || !activeLabel) return; + if (!slotsContainer || !topperSwatch || !swatchGrid) return; topperSwatch.classList.add('tab-btn'); let classicColors = getClassicColors(), activeTarget = '1', slotCount = getStoredSlotCount(); const publishActiveTarget = () => { @@ -1090,6 +1072,13 @@ function distinctPaletteSlots(palette) { renderSlots(); } const allPaletteColors = flattenPalette(); + const labelForColor = (color) => { + const hex = normHex(color?.hex || color?.colour || ''); + const image = color?.image || ''; + const byImage = image ? allPaletteColors.find(c => c.image === image) : null; + const byHex = hex ? allPaletteColors.find(c => c.hex === hex) : null; + return color?.name || byImage?.name || byHex?.name || hex || (image ? 'Texture' : 'Current'); + }; const colorKeyFromVal = (val) => { const palette = buildClassicPalette(); @@ -1290,23 +1279,14 @@ function distinctPaletteSlots(palette) { slot.style.textShadow = txt.shadow; }); - const topperColor = getTopperColor(); - const currentType = document.querySelector('.topper-type-btn[aria-pressed="true"]')?.dataset.type || 'round'; - const tintColor = getNumberTintColor(); - if (currentType.startsWith('num-') && topperColor.image) { - topperSwatch.style.backgroundImage = `linear-gradient(${tintColor}99, ${tintColor}99), url("${topperColor.image}")`; - topperSwatch.style.backgroundBlendMode = 'multiply, normal'; - topperSwatch.style.backgroundSize = '220%'; - topperSwatch.style.backgroundPosition = 'center'; - topperSwatch.style.backgroundColor = tintColor; - } else { - topperSwatch.style.backgroundImage = topperColor.image ? `url("${topperColor.image}")` : 'none'; - topperSwatch.style.backgroundBlendMode = 'normal'; - topperSwatch.style.backgroundColor = topperColor.hex; - topperSwatch.style.backgroundSize = '200%'; - topperSwatch.style.backgroundPosition = 'center'; - } - const topperTxt = textStyleForColor({ hex: tintColor || topperColor.hex, image: topperColor.image }); + const topperColor = getTopperColor(); + const currentType = document.querySelector('.topper-type-btn[aria-pressed="true"]')?.dataset.type || 'round'; + topperSwatch.style.backgroundImage = topperColor.image ? `url("${topperColor.image}")` : 'none'; + topperSwatch.style.backgroundBlendMode = 'normal'; + topperSwatch.style.backgroundColor = topperColor.hex; + topperSwatch.style.backgroundSize = '200%'; + topperSwatch.style.backgroundPosition = 'center'; + const topperTxt = textStyleForColor({ hex: topperColor.hex, image: topperColor.image }); topperSwatch.style.color = topperTxt.color; topperSwatch.style.textShadow = topperTxt.shadow; const patName = (document.getElementById('classic-pattern')?.value || '').toLowerCase(); @@ -1326,15 +1306,24 @@ function distinctPaletteSlots(palette) { const manualModeOn = isManual(); const sharedActive = window.shared?.getActiveColor?.() || { hex: '#ffffff', image: null }; - activeLabel.textContent = manualModeOn ? 'Manual paint color' : (activeTarget === 'T' ? 'Topper' : `Slot #${activeTarget}`); if (activeChip) { const idx = parseInt(activeTarget, 10) - 1; const color = manualModeOn ? sharedActive : (classicColors[idx] || { hex: '#ffffff', image: null }); activeChip.setAttribute('title', manualModeOn ? 'Active color' : `Slot #${activeTarget}`); - const bgImg = color.image ? `url("${color.image}")` : 'none'; + const bgImg = color.image ? `url(\"${color.image}\")` : 'none'; const bgCol = color.hex || '#ffffff'; activeChip.style.backgroundImage = bgImg; activeChip.style.backgroundColor = bgCol; + activeChip.style.backgroundSize = color.image ? '200%' : 'cover'; + activeChip.style.backgroundPosition = 'center'; + const txt = textStyleForColor({ hex: color.hex || '#ffffff', image: color.image }); + activeChip.style.color = txt.color; + activeChip.style.textShadow = txt.shadow; + if (activeLabel) { + activeLabel.textContent = labelForColor(color); + activeLabel.style.color = txt.color; + activeLabel.style.textShadow = txt.shadow; + } if (activeDot) { activeDot.style.backgroundImage = bgImg; activeDot.style.backgroundColor = bgCol; @@ -1357,9 +1346,8 @@ function distinctPaletteSlots(palette) { if (activeChip) { activeChip.style.display = manualModeOn ? '' : 'none'; } - if (projectPaletteBox) { - projectPaletteBox.parentElement?.classList.toggle('hidden', !manualModeOn); - } + if (projectBlock) projectBlock.classList.toggle('hidden', !manualModeOn); + if (replaceBlock) replaceBlock.classList.toggle('hidden', !manualModeOn); } swatchGrid.innerHTML = ''; @@ -1385,10 +1373,11 @@ function distinctPaletteSlots(palette) { const meta = item.meta || {}; const selectedColor = { hex: meta.hex || item.hex, image: meta.image || null }; if (activeTarget === 'T') { - if (currentType.startsWith('num-')) { - setNumberTintColor(selectedColor.hex); - setNumberTintOpacity(1); - if (numberTintSlider) numberTintSlider.value = 1; + const currentTypeLocal = document.querySelector('.topper-type-btn[aria-pressed="true"]')?.dataset.type || 'round'; + if (currentTypeLocal.startsWith('num-')) { + const num = currentTypeLocal.split('-')[1]; + const imgPath = num ? NUMBER_IMAGE_MAP[num] : null; + setTopperColor({ hex: selectedColor.hex, image: imgPath || selectedColor.image || null }); } else { setTopperColor(selectedColor); } @@ -1494,7 +1483,6 @@ function distinctPaletteSlots(palette) { if (typeof window.m === 'undefined') return fail('Mithril not loaded'); projectPaletteBox = null; const display = document.getElementById('classic-display'), patSel = document.getElementById('classic-pattern'), lengthInp = document.getElementById('classic-length-ft'), clusterHint = document.getElementById('classic-cluster-hint'), reverseCb = document.getElementById('classic-reverse'), topperControls = document.getElementById('topper-controls'), topperToggleRow = document.getElementById('classic-topper-toggle-row'), topperEnabledCb = document.getElementById('classic-topper-enabled'), topperSizeInp = document.getElementById('classic-topper-size'), shineEnabledCb = document.getElementById('classic-shine-enabled'), borderEnabledCb = document.getElementById('classic-border-enabled'), manualModeBtn = document.getElementById('classic-manual-btn'), expandedToggleRow = document.getElementById('classic-expanded-row'), expandedToggle = document.getElementById('classic-expanded-toggle'), focusRow = document.getElementById('classic-focus-row'), focusPrev = document.getElementById('classic-focus-prev'), focusNext = document.getElementById('classic-focus-next'), focusLabel = document.getElementById('classic-focus-label'), floatingBar = document.getElementById('classic-mobile-bar'), floatingChip = document.getElementById('classic-active-chip-floating'), floatingUndo = document.getElementById('classic-undo-manual'), floatingRedo = document.getElementById('classic-redo-manual'), floatingPick = document.getElementById('classic-pick-manual'), floatingErase = document.getElementById('classic-erase-manual'), floatingClear = document.getElementById('classic-clear-manual'), floatingExport = document.getElementById('classic-export-manual'), quadReset = document.getElementById('classic-quad-reset'), focusZoomOut = document.getElementById('classic-focus-zoomout'), manualHub = document.getElementById('classic-manual-hub'), manualRange = document.getElementById('classic-manual-range'), manualRangeLabel = document.getElementById('classic-manual-range-label'), manualPrevBtn = document.getElementById('classic-manual-prev'), manualNextBtn = document.getElementById('classic-manual-next'), manualFullBtn = document.getElementById('classic-manual-full'), manualFocusBtn = document.getElementById('classic-manual-focus'), manualDetailDisplay = document.getElementById('classic-manual-detail-display'); - const numberTintRow = document.getElementById('classic-number-tint-row'), numberTintSlider = document.getElementById('classic-number-tint'); const nudgeOpenBtn = document.getElementById('classic-nudge-open'); const fullscreenBtn = document.getElementById('app-fullscreen-toggle'); const toolbar = document.getElementById('classic-canvas-toolbar'); @@ -1513,6 +1501,7 @@ function distinctPaletteSlots(palette) { const patternLayoutBtns = Array.from(document.querySelectorAll('[data-pattern-layout]')); const topperNudgeBtns = Array.from(document.querySelectorAll('.nudge-topper')); const topperTypeButtons = Array.from(document.querySelectorAll('.topper-type-btn')); + const topperSizeInput = document.getElementById('classic-topper-size'); const slotsContainer = document.getElementById('classic-slots'); projectPaletteBox = document.getElementById('classic-project-palette'); const manualPaletteBtn = document.getElementById('classic-manual-palette'); @@ -1568,7 +1557,6 @@ function distinctPaletteSlots(palette) { }; // Force UI to reflect initial manual state if (manualModeState) patternLayout = 'manual'; - if (numberTintSlider) numberTintSlider.value = getNumberTintOpacity(); const topperPresets = { 'Column 4:heart': { enabled: true, offsetX: 3, offsetY: -10.5, size: 1.05 }, 'Column 4:star': { enabled: true, offsetX: 3, offsetY: -7.5, size: 1.65 }, @@ -1581,6 +1569,16 @@ function distinctPaletteSlots(palette) { }; if (!display) return fail('#classic-display not found'); const GC = GridCalculator(), ctrl = GC.controller(display); + if (topperSizeInput) { + const val = Math.max(0.5, Math.min(2, parseFloat(topperSizeInput.value) || 1)); + GC.setTopperSize(val); + topperSizeInput.addEventListener('input', () => { + const next = Math.max(0.5, Math.min(2, parseFloat(topperSizeInput.value) || 1)); + GC.setTopperSize(next); + updateClassicDesign(); + if (window.updateExportButtonVisibility) window.updateExportButtonVisibility(); + }); + } let refreshClassicPaletteUi = null; const getTopperType = () => topperTypeButtons.find(btn => btn.getAttribute('aria-pressed') === 'true')?.dataset.type || 'round'; @@ -1593,20 +1591,19 @@ function distinctPaletteSlots(palette) { }); window.ClassicDesigner.lastTopperType = type; }; - function applyNumberTopperTexture(type) { + function ensureNumberTopperImage(type) { if (!type || !type.startsWith('num-')) return; const num = type.split('-')[1]; - if (!num) return; - const imgPath = NUMBER_IMAGE_MAP[num]; - if (imgPath) setTopperColor({ hex: '#ffffff', image: imgPath }); - else setTopperColor({ hex: '#d4d4d8', image: null }); // fallback silver fill if image missing + const imgPath = num ? NUMBER_IMAGE_MAP[num] : null; + if (!imgPath) return; + const cur = getTopperColor(); + if (cur?.image === imgPath) return; + setTopperColor({ hex: cur?.hex || '#ffffff', image: imgPath }); refreshClassicPaletteUi?.(); } - function resetNonNumberTopperColor(type) { if (type && type.startsWith('num-')) return; const fallback = getTopperColor(); - // If last topper type was a number, strip image to avoid leaking photo texture. if (fallback?.image && Object.values(NUMBER_IMAGE_MAP).includes(fallback.image)) { setTopperColor({ hex: fallback.hex || '#ffffff', image: null }); refreshClassicPaletteUi?.(); @@ -1621,10 +1618,10 @@ function distinctPaletteSlots(palette) { if (lastPresetKey === key || lastPresetKey === 'custom') return; topperOffsetX = preset.offsetX; topperOffsetY = preset.offsetY; - if (topperSizeInp) topperSizeInp.value = preset.size; + if (topperSizeInput) topperSizeInput.value = preset.size; if (topperEnabledCb) topperEnabledCb.checked = preset.enabled; setTopperType(type); - applyNumberTopperTexture(type); + ensureNumberTopperImage(type); resetNonNumberTopperColor(type); lastPresetKey = key; } @@ -1689,8 +1686,7 @@ function distinctPaletteSlots(palette) { topperType: getTopperType(), topperOffsetX, topperOffsetY, - topperSize: topperSizeInp?.value || '', - numberTint: numberTintSlider ? numberTintSlider.value : getNumberTintOpacity() + topperSize: topperSizeInput?.value || '' }; saveClassicState(state); } @@ -1704,12 +1700,8 @@ function distinctPaletteSlots(palette) { if (topperEnabledCb) topperEnabledCb.checked = !!saved.topperEnabled; if (typeof saved.topperOffsetX === 'number') topperOffsetX = saved.topperOffsetX; if (typeof saved.topperOffsetY === 'number') topperOffsetY = saved.topperOffsetY; - if (topperSizeInp && saved.topperSize) topperSizeInp.value = saved.topperSize; + if (topperSizeInput && saved.topperSize) topperSizeInput.value = saved.topperSize; if (saved.topperType) setTopperType(saved.topperType); - if (numberTintSlider && typeof saved.numberTint !== 'undefined') { - numberTintSlider.value = saved.numberTint; - setNumberTintOpacity(saved.numberTint); - } syncPatternStateFromSelect(); lastPresetKey = 'custom'; } @@ -1979,16 +1971,16 @@ function distinctPaletteSlots(palette) { const isColumn = patternName.toLowerCase().includes('column'); const hasTopper = patternName.includes('4') || patternName.includes('5'); const showToggle = isColumn && hasTopper; - if (patternName.toLowerCase().includes('column')) { - const baseName = patternName.includes('5') ? 'Column 5' : 'Column 4'; - applyTopperPreset(baseName, getTopperType()); - } + if (patternName.toLowerCase().includes('column')) { + const baseName = patternName.includes('5') ? 'Column 5' : 'Column 4'; + applyTopperPreset(baseName, getTopperType()); + } if (topperToggleRow) topperToggleRow.classList.toggle('hidden', !showToggle); const showTopper = showToggle && topperEnabledCb?.checked; const isNumberTopper = getTopperType().startsWith('num-'); topperControls.classList.toggle('hidden', !showTopper); - if (numberTintRow) numberTintRow.classList.toggle('hidden', !(showTopper && isNumberTopper)); + // Number tint controls removed; always use base SVG appearance for numbers. if (nudgeOpenBtn) nudgeOpenBtn.classList.toggle('hidden', !showTopper); const showReverse = patternLayout === 'spiral' && !manualOn; if (reverseLabel) reverseLabel.classList.toggle('hidden', !showReverse); @@ -2003,12 +1995,9 @@ function distinctPaletteSlots(palette) { GC.setManualMode(manualOn); GC.setReverse(!!reverseCb?.checked); GC.setTopperType(getTopperType()); - GC.setNumberTintHex(getNumberTintColor()); - GC.setNumberTintOpacity(numberTintSlider ? numberTintSlider.value : getNumberTintOpacity()); - applyNumberTopperTexture(getTopperType()); GC.setTopperOffsetX(topperOffsetX); GC.setTopperOffsetY(topperOffsetY); - GC.setTopperSize(topperSizeInp?.value); + GC.setTopperSize(topperSizeInput?.value); GC.setShineEnabled(!!shineEnabledCb?.checked); GC.setBorderEnabled(!!borderEnabledCb?.checked); const expandedOn = manualOn && manualExpandedState; @@ -2341,15 +2330,15 @@ function distinctPaletteSlots(palette) { })); topperTypeButtons.forEach(btn => btn.addEventListener('click', () => { setTopperType(btn.dataset.type); - applyNumberTopperTexture(btn.dataset.type); + ensureNumberTopperImage(btn.dataset.type); resetNonNumberTopperColor(btn.dataset.type); + if (topperEnabledCb) { + topperEnabledCb.checked = true; + GC.setTopperEnabled(true); + } lastPresetKey = null; updateClassicDesign(); })); - numberTintSlider?.addEventListener('input', () => { - GC.setNumberTintOpacity(numberTintSlider.value); - updateClassicDesign(); - }); nudgeOpenBtn?.addEventListener('click', (e) => { e.preventDefault(); window.__showFloatingNudge?.(); @@ -2373,8 +2362,8 @@ function distinctPaletteSlots(palette) { } }); document.addEventListener('fullscreenchange', updateFullscreenLabel); - [lengthInp, reverseCb, topperEnabledCb, topperSizeInp] - .forEach(el => { if (!el) return; const eventType = (el.type === 'range' || el.type === 'number') ? 'input' : 'change'; el.addEventListener(eventType, () => { if (el === topperSizeInp || el === topperEnabledCb) lastPresetKey = 'custom'; updateClassicDesign(); }); }); + [lengthInp, reverseCb, topperEnabledCb, topperSizeInput] + .forEach(el => { if (!el) return; const eventType = (el.type === 'range' || el.type === 'number') ? 'input' : 'change'; el.addEventListener(eventType, () => { if (el === topperSizeInput || el === topperEnabledCb) lastPresetKey = 'custom'; updateClassicDesign(); }); }); topperEnabledCb?.addEventListener('change', updateClassicDesign); shineEnabledCb?.addEventListener('change', (e) => { const on = !!e.target.checked; GC.setShineEnabled(on); updateClassicDesign(); window.syncAppShine?.(on); }); borderEnabledCb?.addEventListener('change', (e) => { diff --git a/index.html b/index.html index 7bbfb1a..bfa26ac 100644 --- a/index.html +++ b/index.html @@ -37,7 +37,7 @@
@@ -155,41 +155,44 @@
-
Project Palette
-
-
- Built from the current design. Click a swatch to select that color. - -
-
-
- -
Color Library
-
-

Tap or click on canvas to sample a balloon’s color (use the eyedropper).

-
- Active Color -
- +
Organic Colors
+
+
+
+ Active color +
+ +
-
-
-
Replace Color
-
-
- - - - +
+
+ Project Palette + +
+
-
-

Tap a chip to choose colors. “From” shows only colors used on canvas.

- - - -

+ +
+
Replace Color
+
+ + + + +
+
+ + + +

+
+
+ +
+
Color Library
+
@@ -289,7 +292,7 @@ @@ -317,18 +320,6 @@
-
-
Topper Size
- -
-
@@ -364,55 +355,57 @@
- Active color: - -
-
Project Palette
-
-
Replace Color (Manual)
-
-
- - - - -
-
-

Manual paint only. “From” lists colors already used on canvas; “To” comes from the Classic library.

- - - -

+ Active color +
+ +
+
+ +
+
Project Palette
+
+
+ +
+
Replace Color
+
+
+ + + + +
+
+ + + +

+
-
Pick a color for Slot #1 (from colors.js):
-
-
-
Save & Share
-
-
- - -

SVG keeps the vector Classic layout; PNG is raster.

-
-

Classic JSON save/load coming soon.

-
+
+
Save & Share
+
+
+ + +

SVG keeps the vector Classic layout; PNG is raster.

+

Classic JSON save/load coming soon.

+
+
@@ -520,42 +517,42 @@
-
Active Color
-
-
- Current -
- +
Wall Colors
+
+
+
+ Active color +
+ +
-

Tap a swatch to set. Tap a balloon to paint; tap again (same color) to clear. Use the eyedropper to pick from the canvas.

-
-
Used Colors
-
-
-
Click to pick. Remove unused clears empty/transparent entries.
- + +
+
+ Project Palette +
+
-
-
-
Wall Palette
-
-
-
-
Replace Colors
-
-
- - - - + +
+
+ + + + +
+
+ + + +
+
-
-

Tap a chip to choose colors. “Replace” shows only colors used in this wall.

- - - -
+ +
+
Color Library
+
diff --git a/output_webp/0.svg b/output_webp/0.svg new file mode 100644 index 0000000..127892d --- /dev/null +++ b/output_webp/0.svg @@ -0,0 +1,58 @@ + + + + diff --git a/output_webp/1.svg b/output_webp/1.svg new file mode 100644 index 0000000..1292efa --- /dev/null +++ b/output_webp/1.svg @@ -0,0 +1,59 @@ + + + + diff --git a/output_webp/2.svg b/output_webp/2.svg new file mode 100644 index 0000000..fbf53f2 --- /dev/null +++ b/output_webp/2.svg @@ -0,0 +1,58 @@ + + + + diff --git a/output_webp/3.svg b/output_webp/3.svg new file mode 100644 index 0000000..07248a9 --- /dev/null +++ b/output_webp/3.svg @@ -0,0 +1,58 @@ + + + + diff --git a/output_webp/4.svg b/output_webp/4.svg new file mode 100644 index 0000000..a4b7361 --- /dev/null +++ b/output_webp/4.svg @@ -0,0 +1,57 @@ + + + + diff --git a/output_webp/5.svg b/output_webp/5.svg new file mode 100644 index 0000000..4fc8b74 --- /dev/null +++ b/output_webp/5.svg @@ -0,0 +1,57 @@ + + + + diff --git a/output_webp/6.svg b/output_webp/6.svg new file mode 100644 index 0000000..16a374e --- /dev/null +++ b/output_webp/6.svg @@ -0,0 +1,58 @@ + + + + diff --git a/output_webp/7.svg b/output_webp/7.svg new file mode 100644 index 0000000..68c4f79 --- /dev/null +++ b/output_webp/7.svg @@ -0,0 +1,57 @@ + + + + diff --git a/output_webp/8.svg b/output_webp/8.svg new file mode 100644 index 0000000..f0e4da7 --- /dev/null +++ b/output_webp/8.svg @@ -0,0 +1,57 @@ + + + + diff --git a/output_webp/9.svg b/output_webp/9.svg new file mode 100644 index 0000000..cf92b55 --- /dev/null +++ b/output_webp/9.svg @@ -0,0 +1,57 @@ + + + + diff --git a/svg.sh b/svg.sh new file mode 100755 index 0000000..c55a7e9 --- /dev/null +++ b/svg.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" + +# Backup originals once +mkdir -p output_webp_backup +cp --no-clobber output_webp/*.svg output_webp_backup/ || true + +# Convert strokes to filled shapes and force a solid fill +FILL="#ffffff" # solid white interior for mask; stroke stays black for outline +for f in output_webp/*.svg; do + echo "Fixing $f" + inkscape "$f" --batch-process \ + --actions="select-all;object-stroke-to-path;object-to-path;object-set-attribute:fill,$FILL;object-set-attribute:stroke,#000000;object-set-attribute:fill-rule,evenodd;object-set-attribute:style," \ + --export-type=svg --export-filename="$f" --export-overwrite +done + +echo "Done. Originals in output_webp_backup/"