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;