diff --git a/css/style.css b/css/style.css index 91b05cd..fd3e89b 100644 --- a/css/style.css +++ b/css/style.css @@ -4214,6 +4214,44 @@ select { margin-top: 6px; } +.badge-color-row { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + margin-top: 4px; +} + +.badge-color-swatch { + width: 28px; + height: 28px; + border-radius: 50%; + border: 2px solid transparent; + padding: 0; + cursor: pointer; + box-shadow: 0 0 0 1px rgba(0,0,0,0.1) inset; + transition: transform 0.12s ease, border-color 0.12s ease; +} + +.badge-color-swatch:hover { + transform: scale(1.08); +} + +.badge-color-swatch.active { + border-color: #007AFF; + box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.25); +} + +.badge-color-row input[type="color"] { + width: 36px; + height: 30px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: transparent; + cursor: pointer; + padding: 2px; +} + .badge-actions { display: flex; flex-wrap: wrap; diff --git a/index.php b/index.php index 1eef724..c9d772c 100644 --- a/index.php +++ b/index.php @@ -638,6 +638,36 @@ https://live.staticflickr.com/65535/12345678901_abcdef1234_b.jpg"> +
+ + +
+
+ + +
+
+ +
+ + + + + + + +
+
+
+ +
+ + + + + +
+

diff --git a/js/app.js b/js/app.js index 2bab332..fba9eaa 100644 --- a/js/app.js +++ b/js/app.js @@ -4251,6 +4251,12 @@ document.addEventListener('DOMContentLoaded', function() { priceEnabled: document.getElementById('badge-price-enabled'), priceOptions: document.getElementById('badge-price-options'), priceValue: document.getElementById('badge-price-value'), + priceY: document.getElementById('badge-price-y'), + priceYValue: document.getElementById('badge-price-y-value'), + priceSize: document.getElementById('badge-price-size'), + priceSizeValue: document.getElementById('badge-price-size-value'), + priceBg: document.getElementById('badge-price-bg'), + priceFg: document.getElementById('badge-price-fg'), nickEnabled: document.getElementById('badge-nickname-enabled'), nickOptions: document.getElementById('badge-nickname-options'), nickValue: document.getElementById('badge-nickname-value'), @@ -4308,19 +4314,29 @@ document.addEventListener('DOMContentLoaded', function() { c.closePath(); } - function drawPriceArc(c, size, text) { - const yTop = size * 0.74; - const yBottom = size * 0.965; + function drawPriceArc(c, size, item, text) { + const scale = (item.priceSize || 100) / 100; + const offsetY = ((item.priceY || 0) / 100) * size; + const baseTopFrac = 0.74; + const baseBottomFrac = 0.965; + const baseHeight = (baseBottomFrac - baseTopFrac) * size; + const height = baseHeight * scale; + const centerY = ((baseTopFrac + baseBottomFrac) / 2) * size + offsetY; + const yTop = centerY - height / 2; + const yBottom = centerY + height / 2; + const bg = item.priceBg || '#FFCC00'; + const fg = item.priceFg || '#000000'; c.save(); - c.fillStyle = '#FFCC00'; + c.fillStyle = bg; c.fillRect(0, yTop, size, yBottom - yTop); - const grad = c.createLinearGradient(0, yTop, 0, yTop + size * 0.012); - grad.addColorStop(0, 'rgba(255,255,255,0.35)'); - grad.addColorStop(1, 'rgba(255,255,255,0)'); - c.fillStyle = grad; + // Subtle inner highlight at top edge of the band. + const hi = c.createLinearGradient(0, yTop, 0, yTop + size * 0.012); + hi.addColorStop(0, 'rgba(255,255,255,0.3)'); + hi.addColorStop(1, 'rgba(255,255,255,0)'); + c.fillStyle = hi; c.fillRect(0, yTop, size, size * 0.012); const fontSize = (yBottom - yTop) * 0.55; - c.fillStyle = '#000'; + c.fillStyle = fg; c.textAlign = 'center'; c.textBaseline = 'middle'; c.font = `700 ${fontSize}px ${FONT_STACK}`; @@ -4328,20 +4344,27 @@ document.addEventListener('DOMContentLoaded', function() { c.restore(); } - function drawPriceRect(c, size, text) { - const w = size * 0.78; - const h = size * 0.13; + function drawPriceRect(c, size, item, text) { + const scale = (item.priceSize || 100) / 100; + const offsetY = ((item.priceY || 0) / 100) * size; + const baseW = size * 0.78; + const baseH = size * 0.13; + const w = baseW * scale; + const h = baseH * scale; const x = (size - w) / 2; - const y = size * 0.755; + const baseTop = size * 0.755; + const y = baseTop + offsetY + (baseH - h) / 2; + const bg = item.priceBg || '#FFCC00'; + const fg = item.priceFg || '#000000'; c.save(); c.fillStyle = 'rgba(0,0,0,0.22)'; roundRect(c, x, y + size * 0.008, w, h, h * 0.28); c.fill(); - c.fillStyle = '#FFCC00'; + c.fillStyle = bg; roundRect(c, x, y, w, h, h * 0.28); c.fill(); const fontSize = h * 0.62; - c.fillStyle = '#000'; + c.fillStyle = fg; c.textAlign = 'center'; c.textBaseline = 'middle'; c.font = `700 ${fontSize}px ${FONT_STACK}`; @@ -4440,9 +4463,9 @@ document.addEventListener('DOMContentLoaded', function() { if (hasPrice) { if (item.priceStyle === 'rect') { - drawPriceRect(c, size, priceText); + drawPriceRect(c, size, item, priceText); } else { - drawPriceArc(c, size, priceText); + drawPriceArc(c, size, item, priceText); } } @@ -4507,6 +4530,10 @@ document.addEventListener('DOMContentLoaded', function() { showPrice: false, priceValue: '', priceStyle: 'arc', + priceY: 0, // -50..+20: vertical offset from default position + priceSize: 100, // 60..160 (% scale of band height) + priceBg: '#FFCC00', // ribbon color + priceFg: '#000000', // digits color showNickname: false, nickname: '', nickColor: 'white', @@ -4555,6 +4582,14 @@ document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('input[name="badge-price-style"]').forEach(r => { r.checked = r.value === item.priceStyle; }); + el.priceY.value = item.priceY; + el.priceYValue.textContent = item.priceY; + el.priceSize.value = item.priceSize; + el.priceSizeValue.textContent = item.priceSize + '%'; + el.priceBg.value = item.priceBg; + el.priceFg.value = item.priceFg; + syncSwatches('bg', item.priceBg); + syncSwatches('fg', item.priceFg); el.nickEnabled.checked = item.showNickname; el.nickOptions.classList.toggle('hidden', !item.showNickname); el.nickValue.value = item.nickname || badgeState.defaultNickname || ''; @@ -4778,6 +4813,72 @@ document.addEventListener('DOMContentLoaded', function() { }); }); + // Price vertical position slider + el.priceY.addEventListener('input', () => { + const item = currentItem(); + if (!item) return; + const v = parseInt(el.priceY.value, 10) || 0; + item.priceY = v; + el.priceYValue.textContent = v; + renderPreview(); + }); + + // Price size slider + el.priceSize.addEventListener('input', () => { + const item = currentItem(); + if (!item) return; + const v = parseInt(el.priceSize.value, 10) || 100; + item.priceSize = v; + el.priceSizeValue.textContent = v + '%'; + renderPreview(); + }); + + // Color: ribbon background + function normalizeHex(v) { + return (v || '').toString().toLowerCase(); + } + + function syncSwatches(target, color) { + const norm = normalizeHex(color); + document.querySelectorAll(`.badge-color-row[data-target="${target}"] .badge-color-swatch`).forEach(sw => { + sw.classList.toggle('active', normalizeHex(sw.dataset.color) === norm); + }); + } + + el.priceBg.addEventListener('input', () => { + const item = currentItem(); + if (!item) return; + item.priceBg = el.priceBg.value; + syncSwatches('bg', item.priceBg); + renderPreview(); + }); + + el.priceFg.addEventListener('input', () => { + const item = currentItem(); + if (!item) return; + item.priceFg = el.priceFg.value; + syncSwatches('fg', item.priceFg); + renderPreview(); + }); + + document.querySelectorAll('.badge-color-row .badge-color-swatch').forEach(sw => { + sw.addEventListener('click', () => { + const item = currentItem(); + if (!item) return; + const target = sw.parentElement.dataset.target; + const color = sw.dataset.color; + if (target === 'bg') { + item.priceBg = color; + el.priceBg.value = color; + } else if (target === 'fg') { + item.priceFg = color; + el.priceFg.value = color; + } + syncSwatches(target, color); + renderPreview(); + }); + }); + el.nickEnabled.addEventListener('change', () => { const item = currentItem(); if (!item) return;