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

@@ -7,35 +7,40 @@ type LoginData struct {
}
templ loginBody(d LoginData) {
<div class="min-h-screen grid md:grid-cols-2">
<div class="min-h-screen grid md:grid-cols-2 v-auth-grid">
<div class="flex items-center justify-center p-8">
<div class="w-full max-w-sm">
<div class="flex items-center gap-2 mb-6">
<div class="flex items-center gap-2 mb-8 v-auth-wordmark">
<span class="text-3xl">🐝</span>
<span class="text-2xl font-semibold tracking-wide">Veola</span>
</div>
<h1 class="text-3xl font-semibold mb-2">Open the door.</h1>
<p class="v-muted mb-6">Sign in to continue.</p>
if d.Error != "" {
<div class="v-flash-error">{ d.Error }</div>
}
<form method="post" action="/login" class="space-y-4">
@CSRFInput(d.CSRFToken)
<div>
<label class="v-label">Username</label>
<input class="v-input" name="username" autocomplete="username" autofocus value={ d.Username }/>
</div>
<div>
<label class="v-label">Password</label>
<input class="v-input" type="password" name="password" autocomplete="current-password"/>
</div>
<button class="v-btn w-full justify-center" type="submit">Sign In</button>
</form>
<div class="v-card v-auth-card p-7">
<h1 class="text-3xl font-semibold mb-2">Open the door.</h1>
<p class="v-muted mb-6">Sign in to continue.</p>
if d.Error != "" {
<div class="v-flash-error">{ d.Error }</div>
}
<form method="post" action="/login" class="space-y-4">
@CSRFInput(d.CSRFToken)
<div>
<label class="v-label">Username</label>
<input class="v-input" name="username" autocomplete="username" autofocus value={ d.Username }/>
</div>
<div>
<label class="v-label">Password</label>
<input class="v-input" type="password" name="password" autocomplete="current-password"/>
</div>
<button class="v-btn w-full justify-center" type="submit">Sign In</button>
</form>
</div>
<p class="v-auth-tagline">Track · Watch · Notice</p>
</div>
</div>
<div class="hidden md:flex items-end justify-center p-8 bg-[#152560]">
<div class="v-veola-portrait max-w-md">
<img src="/static/img/veola.webp" alt="Veola"/>
<div class="hidden md:flex flex-col items-center justify-center p-8 v-auth-portrait-col">
<div class="v-auth-portrait-halo">
<div class="v-veola-portrait max-w-md">
<img src="/static/img/veola.avif" alt="Veola"/>
</div>
</div>
</div>
</div>
@@ -52,39 +57,44 @@ type SetupData struct {
}
templ setupBody(d SetupData) {
<div class="min-h-screen grid md:grid-cols-2">
<div class="min-h-screen grid md:grid-cols-2 v-auth-grid">
<div class="flex items-center justify-center p-8">
<div class="w-full max-w-sm">
<div class="flex items-center gap-2 mb-6">
<div class="flex items-center gap-2 mb-8 v-auth-wordmark">
<span class="text-3xl">🐝</span>
<span class="text-2xl font-semibold tracking-wide">Veola</span>
</div>
<h1 class="text-3xl font-semibold mb-2">First time here.</h1>
<p class="v-muted mb-6">Create the admin account. Password must be at least 12 characters.</p>
if d.Error != "" {
<div class="v-flash-error">{ d.Error }</div>
}
<form method="post" action="/setup" class="space-y-4">
@CSRFInput(d.CSRFToken)
<div>
<label class="v-label">Username</label>
<input class="v-input" name="username" autofocus value={ d.Username }/>
</div>
<div>
<label class="v-label">Password</label>
<input class="v-input" type="password" name="password"/>
</div>
<div>
<label class="v-label">Confirm Password</label>
<input class="v-input" type="password" name="password_confirm"/>
</div>
<button class="v-btn w-full justify-center" type="submit">Create Admin</button>
</form>
<div class="v-card v-auth-card p-7">
<h1 class="text-3xl font-semibold mb-2">First time here.</h1>
<p class="v-muted mb-6">Create the admin account. Password must be at least 12 characters.</p>
if d.Error != "" {
<div class="v-flash-error">{ d.Error }</div>
}
<form method="post" action="/setup" class="space-y-4">
@CSRFInput(d.CSRFToken)
<div>
<label class="v-label">Username</label>
<input class="v-input" name="username" autofocus value={ d.Username }/>
</div>
<div>
<label class="v-label">Password</label>
<input class="v-input" type="password" name="password"/>
</div>
<div>
<label class="v-label">Confirm Password</label>
<input class="v-input" type="password" name="password_confirm"/>
</div>
<button class="v-btn w-full justify-center" type="submit">Create Admin</button>
</form>
</div>
<p class="v-auth-tagline">Track · Watch · Notice</p>
</div>
</div>
<div class="hidden md:flex items-end justify-center p-8 bg-[#152560]">
<div class="v-veola-portrait max-w-md">
<img src="/static/img/veola.webp" alt="Veola"/>
<div class="hidden md:flex flex-col items-center justify-center p-8 v-auth-portrait-col">
<div class="v-auth-portrait-halo">
<div class="v-veola-portrait max-w-md">
<img src="/static/img/veola.avif" alt="Veola"/>
</div>
</div>
</div>
</div>