Items-list sparklines, retro CSS, pinned tooling, deploy docs

- 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>
This commit is contained in:
prosolis
2026-05-15 19:10:56 -07:00
parent 0ec97afafb
commit ea3577a45e
17 changed files with 1174 additions and 343 deletions

View File

@@ -9,7 +9,29 @@
return;
}
var data = JSON.parse(canvas.dataset.chart);
new Chart(canvas.getContext('2d'), {
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,
@@ -17,19 +39,37 @@
label: 'Best price',
data: data.points,
borderColor: '#00e4a4',
backgroundColor: 'rgba(0,228,164,0.15)',
pointBackgroundColor: '#e84040',
borderWidth: 2.5,
backgroundColor: fill,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#00e4a4',
pointBorderWidth: 1.5,
pointRadius: 3,
tension: 0.25,
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.07)' } },
y: { ticks: { color: '#a8c0f0' }, grid: { color: 'rgba(255,255,255,0.07)' } }
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' } } }
}
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]
});
})();