- Bulk-load recent price points per item and render a sparkline in the items list (new LoadRecentPriceHistory query avoids N+1). - Add retro.css visual layer and refreshed login/items/layout styling. - Swap the logo from webp to avif. - Pin htmx/Chart.js/Tailwind/templ versions in the Makefile with vendor / tools / update-deps targets; README documents the dependency-bump flow and the hardened systemd deploy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
2.4 KiB
JavaScript
76 lines
2.4 KiB
JavaScript
// Renders the price-history line chart on the per-item results page.
|
|
// Chart data is read from the canvas's data-chart attribute: templ
|
|
// interpolates attribute values but treats <script> element contents as raw
|
|
// text, so the JSON cannot live in an inline <script> block. Loaded after
|
|
// chart.umd.min.js with the #price-chart canvas already in the DOM above it.
|
|
(function () {
|
|
var canvas = document.getElementById('price-chart');
|
|
if (!canvas || !canvas.dataset.chart || typeof Chart === 'undefined') {
|
|
return;
|
|
}
|
|
var data = JSON.parse(canvas.dataset.chart);
|
|
var ctx = canvas.getContext('2d');
|
|
|
|
// Vertical gradient fill — bright accent at the top, transparent at the
|
|
// baseline — makes the chart read at a glance instead of as a thin line.
|
|
var fill = ctx.createLinearGradient(0, 0, 0, canvas.clientHeight || 200);
|
|
fill.addColorStop(0, 'rgba(0, 228, 164, 0.45)');
|
|
fill.addColorStop(1, 'rgba(0, 228, 164, 0.00)');
|
|
|
|
// Chart.js plugin that paints a soft glow under the line stroke before
|
|
// the dataset draws. Cheap enough to keep on by default; respects
|
|
// prefers-reduced-motion only insofar as nothing animates here.
|
|
var glowPlugin = {
|
|
id: 'priceLineGlow',
|
|
beforeDatasetDraw: function (chart) {
|
|
var c = chart.ctx;
|
|
c.save();
|
|
c.shadowColor = 'rgba(0, 228, 164, 0.55)';
|
|
c.shadowBlur = 12;
|
|
},
|
|
afterDatasetDraw: function (chart) { chart.ctx.restore(); }
|
|
};
|
|
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels,
|
|
datasets: [{
|
|
label: 'Best price',
|
|
data: data.points,
|
|
borderColor: '#00e4a4',
|
|
borderWidth: 2.5,
|
|
backgroundColor: fill,
|
|
pointBackgroundColor: '#ffffff',
|
|
pointBorderColor: '#00e4a4',
|
|
pointBorderWidth: 1.5,
|
|
pointRadius: 3,
|
|
pointHoverRadius: 5,
|
|
tension: 0.3,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: { mode: 'index', intersect: false },
|
|
scales: {
|
|
x: { ticks: { color: '#a8c0f0' }, grid: { color: 'rgba(255,255,255,0.06)' } },
|
|
y: { ticks: { color: '#a8c0f0' }, grid: { color: 'rgba(255,255,255,0.06)' } }
|
|
},
|
|
plugins: {
|
|
legend: { labels: { color: '#ffffff' } },
|
|
tooltip: {
|
|
backgroundColor: 'rgba(20, 32, 80, 0.95)',
|
|
borderColor: 'rgba(0, 164, 228, 0.6)',
|
|
borderWidth: 1,
|
|
titleColor: '#ffffff',
|
|
bodyColor: '#a8c0f0',
|
|
padding: 10
|
|
}
|
|
}
|
|
},
|
|
plugins: [glowPlugin]
|
|
});
|
|
})();
|