style fix, rebuild lang

This commit is contained in:
dgtlmoon
2026-06-22 08:52:53 +02:00
parent 247c7a12ba
commit 2b0113820d
20 changed files with 118 additions and 57 deletions
@@ -133,8 +133,8 @@ window.watchOverviewI18n = {
outOfStock: {{ _('Out of stock')|tojson }},
noPriceData: {{ _('No price data available to graph yet.')|tojson }},
priceHistoryError: {{ _('Could not load price history.')|tojson }},
changes: {{ _('Number of changes')|tojson }},
avgPrice: {{ _('average price')|tojson }},
changes: {{ _('Changes')|tojson }},
avgPrice: {{ _('Average price')|tojson }},
priceLow: {{ _('Currently low')|tojson }},
priceTypical: {{ _('Currently typical')|tojson }},
priceHigh: {{ _('Currently high')|tojson }},
@@ -81,8 +81,8 @@
out_of_stock: {{ _('Out of stock')|tojson }},
no_data: {{ _('No price data available to graph yet.')|tojson }},
load_error: {{ _('Could not load price history.')|tojson }},
changes: {{ _('Number of changes')|tojson }},
avg_price: {{ _('average price')|tojson }},
changes: {{ _('Changes')|tojson }},
avg_price: {{ _('Average price')|tojson }},
price_low: {{ _('Currently low')|tojson }},
price_typical: {{ _('Currently typical')|tojson }},
price_high: {{ _('Currently high')|tojson }},
@@ -21,7 +21,7 @@
const DEFAULT_I18N = { in_stock: 'In stock', out_of_stock: 'Out of stock',
no_data: 'No price data available to graph yet.', load_error: 'Could not load price history.',
changes: 'Number of changes', avg_price: 'average price',
changes: 'Changes', avg_price: 'Average price',
price_low: 'Currently low', price_typical: 'Currently typical', price_high: 'Currently high',
cheaper_than: 'cheaper than %s% of tracked prices',
pricier_than: 'more expensive than %s% of tracked prices',
@@ -76,6 +76,51 @@
return Array.from(keep).sort((a, b) => a - b).map(i => arr[i]);
}
// Monotone cubic spline (FritschCarlson). A gentle, smooth curve that — unlike Catmull-Rom —
// is guaranteed NOT to overshoot beyond the actual data points: tangents are clamped so the
// line never dips below / rises above neighbouring values. So the curve stays faithful to the
// real prices (no phantom dips/spikes) while still looking soft. Returns an SVG path string.
function monotonePath(pts) {
const n = pts.length;
if (n < 2) return '';
const xs = pts.map(p => p.x), ys = pts.map(p => p.y);
if (n === 2) return `M ${xs[0].toFixed(1)} ${ys[0].toFixed(1)} L ${xs[1].toFixed(1)} ${ys[1].toFixed(1)}`;
const dx = [], slope = [];
for (let i = 0; i < n - 1; i++) {
const h = xs[i + 1] - xs[i];
dx.push(h);
slope.push(h === 0 ? 0 : (ys[i + 1] - ys[i]) / h);
}
const m = new Array(n);
m[0] = slope[0];
m[n - 1] = slope[n - 2];
for (let i = 1; i < n - 1; i++) {
// Local extremum (slope changes sign) → flat tangent so the curve doesn't overshoot.
m[i] = (slope[i - 1] * slope[i] <= 0) ? 0 : (slope[i - 1] + slope[i]) / 2;
}
// FritschCarlson: clamp tangents so each segment stays monotone (no overshoot).
for (let i = 0; i < n - 1; i++) {
if (slope[i] === 0) { m[i] = 0; m[i + 1] = 0; continue; }
const a = m[i] / slope[i], b = m[i + 1] / slope[i], s = a * a + b * b;
if (s > 9) {
const t = 3 / Math.sqrt(s);
m[i] = t * a * slope[i];
m[i + 1] = t * b * slope[i];
}
}
let d = `M ${xs[0].toFixed(1)} ${ys[0].toFixed(1)}`;
for (let i = 0; i < n - 1; i++) {
const h = dx[i];
const c1x = xs[i] + h / 3, c1y = ys[i] + m[i] * h / 3;
const c2x = xs[i + 1] - h / 3, c2y = ys[i + 1] - m[i + 1] * h / 3;
d += ` C ${c1x.toFixed(1)} ${c1y.toFixed(1)} ${c2x.toFixed(1)} ${c2y.toFixed(1)} ${xs[i + 1].toFixed(1)} ${ys[i + 1].toFixed(1)}`;
}
return d;
}
function draw($container, series, currency, i18n, summary, height) {
height = height || HEIGHT;
// Only points that actually have a price can sit on the line.
@@ -141,19 +186,20 @@
svg.appendChild(at);
}
// Single neutral straight-line path connecting the points (no smoothing — a curve
// overshoots and reads as a price move that didn't happen). Stock status is shown by the
// DOT colours instead, not the line.
let linePath = '';
pts.forEach((p, i) => { linePath += (i === 0 ? 'M ' : ' L ') + p.x.toFixed(1) + ' ' + p.y.toFixed(1); });
// Single neutral, gently-smoothed line (monotone cubic — soft but never overshoots the
// real values). Stock status is shown by the DOT colours instead, not the line.
svg.appendChild(el('path', {
class: 'rg-line', d: linePath, fill: 'none',
class: 'rg-line', d: monotonePath(pts), fill: 'none',
'stroke-width': 2, 'stroke-linecap': 'round', 'stroke-linejoin': 'round'
}));
// Dots, coloured by each point's own stock state (green = in stock, red = out of stock).
// Sized to match the legend swatch (~9px) with a thin surface-coloured ring so they stand
// out from the line instead of blending into it.
pts.forEach(p => {
svg.appendChild(el('circle', { cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 3.2, fill: stockColor(p.d.in_stock) }));
svg.appendChild(el('circle', {
class: 'rg-dot', cx: p.x.toFixed(1), cy: p.y.toFixed(1), r: 4.5, fill: stockColor(p.d.in_stock)
}));
});
// Sparse x-axis date labels: evenly spaced, always including both endpoints. The number
@@ -203,17 +249,14 @@
.append('<span class="rg-legend-dot out"></span>').append(document.createTextNode(' ' + (i18n.out_of_stock || 'Out of stock'))));
$container.append($legend);
// Footer stats: total recorded changes + average of the prices shown.
const avgPrice = prices.reduce((a, b) => a + b, 0) / prices.length;
$('<div class="rg-stats"></div>')
.text((i18n.changes || 'Number of changes') + ' ' + (series ? series.length : data.length) +
', ' + (i18n.avg_price || 'average price') + ' ' + fmtPrice(avgPrice, currency))
.appendTo($container);
// Header row above the graph: status pill (LOW / TYPICAL / HIGH, left) and the summary
// stats (right), on one line. The pill sub-text is phrased per status so it never reads
// awkwardly (e.g. "cheaper than 0%" when high).
const $header = $('<div class="rg-header"></div>');
// Status pill (LOW / TYPICAL / HIGH) above the graph, like Google Flights. The sub-text
// is phrased per status so it never reads awkwardly (e.g. "cheaper than 0%" when high).
let $pill = $('<span></span>'); // placeholder keeps the stats pushed to the right
if (showOverlays) {
const $pill = $('<div class="rg-status rg-status-' + summary.status + '"></div>');
$pill = $('<div class="rg-status rg-status-' + summary.status + '"></div>');
$pill.append($('<span class="rg-status-label"></span>').text(i18n['price_' + summary.status] || ''));
let sub;
if (summary.status === 'low') {
@@ -224,8 +267,15 @@
sub = i18n.typical_note || '';
}
if (sub) $pill.append($('<span class="rg-status-sub"></span>').text(sub));
$container.prepend($pill);
}
const avgPrice = prices.reduce((a, b) => a + b, 0) / prices.length;
const $stats = $('<div class="rg-stats"></div>')
.text((i18n.avg_price || 'Average price') + ' ' + fmtPrice(avgPrice, currency) +
', ' + (series ? series.length : data.length) + ' ' + (i18n.changes || 'Changes'));
$header.append($pill).append($stats);
$container.prepend($header);
}
function buildTable($table, series, currency, i18n) {
@@ -70,6 +70,8 @@
.rg-label { fill: currentColor; opacity: 0.7; font-size: 12px; }
// Neutral single-colour price line; stock status is shown by the dot colours, not the line.
.rg-line { stroke: currentColor; opacity: 0.55; }
// Surface-coloured ring so the bigger stock dots sit clearly on top of the line.
.rg-dot { stroke: var(--color-background, #fff); stroke-width: 1.5; }
.rg-legend {
display: flex;
@@ -85,11 +87,21 @@
.rg-legend-dot.out { background: #e74c3c; }
}
// Header row above the graph: status pill (left) and summary stats (right) on one line.
.rg-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.5rem;
}
.rg-stats {
margin-top: 0.35rem;
font-size: 0.8rem;
opacity: 0.7;
text-align: center;
text-align: right;
margin-left: auto; // pin to the right even when the pill is absent
}
// Analyser overlays: typical-range band + dashed average line.
@@ -103,7 +115,6 @@
display: inline-flex;
align-items: baseline;
gap: 0.4rem;
margin-bottom: 0.5rem;
padding: 0.2rem 0.6rem;
border-radius: 1rem;
font-size: 0.85rem;
File diff suppressed because one or more lines are too long
@@ -2376,12 +2376,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2421,12 +2421,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2370,12 +2370,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2370,12 +2370,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2437,12 +2437,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2381,12 +2381,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2372,12 +2372,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2389,12 +2389,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2380,12 +2380,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
+3 -3
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: changedetection.io 0.55.7\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-06-22 00:55+0200\n"
"POT-Creation-Date: 2026-06-22 08:47+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -2369,12 +2369,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2418,12 +2418,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2423,12 +2423,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2402,12 +2402,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2376,12 +2376,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
@@ -2376,12 +2376,12 @@ msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "Number of changes"
msgid "Changes"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html
#: changedetectionio/processors/restock_diff/templates/restock_diff/difference.html
msgid "average price"
msgid "Average price"
msgstr ""
#: changedetectionio/blueprint/watchlist/templates/watch-overview.html