summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-03-22 10:05:16 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-03-22 10:05:16 +0000
commit11b905fd437d651b2e39745aa82a5dd36f70331e (patch)
treea9019cfa8fc81b274622b5b414fbb8d5f602fa78 /web
parent77f4aa8d1f818169490a35bf2ec3eb43e60166b4 (diff)
style: modernize UI with dark glass theme and 40% bg opacity
Diffstat (limited to 'web')
-rw-r--r--web/static/css/input.css17
-rw-r--r--web/templates/index.html24
-rw-r--r--web/templates/login.html11
-rw-r--r--web/templates/partials/sync-log.html16
-rw-r--r--web/templates/passkeys_list.html14
-rw-r--r--web/templates/settings.html372
-rw-r--r--web/templates/shopping-mode.html9
7 files changed, 160 insertions, 303 deletions
diff --git a/web/static/css/input.css b/web/static/css/input.css
index 321aa4f..7dd2055 100644
--- a/web/static/css/input.css
+++ b/web/static/css/input.css
@@ -7,29 +7,32 @@
/* Custom base styles - Dark translucent theme */
@layer base {
body {
- @apply antialiased text-white bg-gray-900 min-h-screen;
+ @apply antialiased text-slate-200 bg-slate-950 min-h-screen relative;
font-family: 'Inter', system-ui, sans-serif;
- text-shadow: 0 0 8px black, 0 0 8px black;
+ }
+
+ .bg-overlay {
+ @apply fixed inset-0 z-[-1] opacity-40 bg-cover bg-center bg-no-repeat;
}
/* Headings */
h1, h2, h3, h4, h5, h6 {
- @apply text-white font-light tracking-wide;
+ @apply text-white font-medium tracking-tight;
}
- a { @apply text-white/90 hover:text-white; }
+ a { @apply text-slate-300 hover:text-white transition-colors; }
}
/* Custom components */
@layer components {
/* Dark Glass Card */
.card {
- @apply bg-black/70 backdrop-blur-sm rounded-lg p-4 transition-all duration-200 overflow-hidden;
- box-shadow: 0 0 12px black;
+ @apply bg-slate-900/80 backdrop-blur-md rounded-xl p-5 transition-all duration-200 border border-white/5;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.card-hover {
- @apply hover:bg-black/80;
+ @apply hover:bg-slate-800/90 hover:border-white/10;
}
/* Panel with overflow clipping */
diff --git a/web/templates/index.html b/web/templates/index.html
index 1884a77..5cfa77f 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -8,25 +8,25 @@
<link rel="stylesheet" href="/static/css/output.css">
<style>
:root {
- --panel-bg: rgba(0, 0, 0, 0.4);
- --card-bg: rgba(0, 0, 0, 0.5);
- --card-bg-hover: rgba(0, 0, 0, 0.6);
- --input-bg: rgba(0, 0, 0, 0.4);
- --modal-bg: rgba(0, 0, 0, 0.8);
- --modal-overlay: rgba(0, 0, 0, 0.7);
+ --panel-bg: rgba(15, 23, 42, 0.8);
+ --card-bg: rgba(30, 41, 59, 0.7);
+ --card-bg-hover: rgba(51, 65, 85, 0.8);
+ --input-bg: rgba(15, 23, 42, 0.9);
+ --modal-bg: rgba(15, 23, 42, 0.95);
+ --modal-overlay: rgba(2, 6, 23, 0.8);
}
- .text-shadow { text-shadow: 0 0 8px black, 0 0 8px black; }
- .text-shadow-sm { text-shadow: 0 0 4px black; }
+ .text-shadow-sm { text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
/* CSS variable-based backgrounds */
- .bg-panel { background-color: var(--panel-bg); }
- .bg-card { background-color: var(--card-bg); }
- .bg-card-hover:hover { background-color: var(--card-bg-hover); }
+ .bg-panel { background-color: var(--panel-bg); @apply backdrop-blur-md border border-white/5; }
+ .bg-card { background-color: var(--card-bg); @apply backdrop-blur-sm border border-white/5; }
+ .bg-card-hover:hover { background-color: var(--card-bg-hover); @apply border-white/10; }
.bg-input { background-color: var(--input-bg); }
.bg-modal { background-color: var(--modal-bg); }
.bg-modal-overlay { background-color: var(--modal-overlay); }
</style>
</head>
-<body class="min-h-screen bg-gray-900 text-white" style="background-image: url('{{.BackgroundURL}}'); background-size: cover; background-position: center; background-attachment: fixed;" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
+<body class="min-h-screen bg-slate-950 text-slate-200" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
+ <div class="bg-overlay" style="background-image: url('{{.BackgroundURL}}');"></div>
<div class="content-max-width py-3 sm:py-6">
<!-- Minimal Header -->
<header class="flex mb-4 sm:mb-6 justify-between items-center no-print text-shadow-sm">
diff --git a/web/templates/login.html b/web/templates/login.html
index 7d40a6b..19bce00 100644
--- a/web/templates/login.html
+++ b/web/templates/login.html
@@ -7,13 +7,14 @@
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/css/output.css">
<style>
- .text-shadow { text-shadow: 0 0 8px black, 0 0 8px black; }
+ .text-shadow-sm { text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
</style>
</head>
-<body class="min-h-screen flex items-center justify-center bg-gray-900" style="background-image: url('https://picsum.photos/1920/1080?random=login'); background-size: cover; background-position: center;">
- <div class="w-full max-w-md p-8">
- <div class="bg-black/60 backdrop-blur-sm rounded-xl p-8 text-shadow" style="box-shadow: 0 0 12px black;">
- <h1 class="text-2xl font-light text-white text-center mb-8 tracking-wide">Personal Dashboard</h1>
+<body class="min-h-screen flex items-center justify-center bg-slate-950 text-slate-200">
+ <div class="bg-overlay" style="background-image: url('https://picsum.photos/1920/1080?random=login');"></div>
+ <div class="w-full max-w-md p-6 sm:p-8 relative z-10">
+ <div class="card shadow-2xl">
+ <h1 class="text-3xl font-light text-white text-center mb-10 tracking-tight text-shadow-sm">Personal Dashboard</h1>
{{if .Error}}
<div class="mb-6 p-4 bg-red-900/50 border border-red-500/30 rounded-lg text-red-300 text-sm">
diff --git a/web/templates/partials/sync-log.html b/web/templates/partials/sync-log.html
index e7f8191..89b7dcf 100644
--- a/web/templates/partials/sync-log.html
+++ b/web/templates/partials/sync-log.html
@@ -1,14 +1,16 @@
{{define "sync-log"}}
<div id="sync-log">
{{if .}}
- <div style="margin-top: 12px;">
- <div style="font-size: 0.75rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px;">Sync Activity</div>
- {{range .}}
- <div style="display: flex; gap: 8px; align-items: baseline; font-size: 0.8rem; padding: 3px 0; border-bottom: 1px solid var(--border-color, #2a2a2a);">
- <span style="color: var(--text-secondary); white-space: nowrap; font-family: monospace;">{{.CreatedAt.Format "15:04:05"}}</span>
- <span style="color: var(--text-primary, #e0e0e0);">{{.Message}}</span>
+ <div class="mt-8">
+ <div class="text-[10px] font-bold text-slate-500 uppercase tracking-widest mb-3 px-1">Sync Activity</div>
+ <div class="space-y-1">
+ {{range .}}
+ <div class="flex gap-4 items-baseline text-xs py-2 px-3 bg-slate-900/40 rounded border-b border-white/5 last:border-0 transition-colors hover:bg-slate-900/60">
+ <span class="text-slate-500 font-mono whitespace-nowrap">{{.CreatedAt.Format "15:04:05"}}</span>
+ <span class="text-slate-300 leading-relaxed">{{.Message}}</span>
+ </div>
+ {{end}}
</div>
- {{end}}
</div>
{{end}}
</div>
diff --git a/web/templates/passkeys_list.html b/web/templates/passkeys_list.html
index 4e05461..22ed724 100644
--- a/web/templates/passkeys_list.html
+++ b/web/templates/passkeys_list.html
@@ -1,13 +1,13 @@
{{define "passkeys_list.html"}}
-<div class="items-list">
+<div class="grid gap-3">
{{if .Passkeys}}
{{range .Passkeys}}
- <div class="item-row" id="passkey-{{.ID}}">
- <div class="item-name">
- <strong>{{if .Name}}{{.Name}}{{else}}Passkey{{end}}</strong>
- <div class="item-desc">Added {{.CreatedAt.Format "Jan 2, 2006"}}</div>
+ <div class="card flex items-center gap-4" id="passkey-{{.ID}}">
+ <div class="flex-1 min-w-0">
+ <strong class="text-white block truncate">{{if .Name}}{{.Name}}{{else}}Passkey{{end}}</strong>
+ <div class="text-xs text-slate-500">Added {{.CreatedAt.Format "Jan 2, 2006"}}</div>
</div>
- <button class="btn btn-danger btn-sm"
+ <button class="text-xs text-red-400 hover:text-red-300 transition-colors"
hx-delete="/passkeys/{{.ID}}"
hx-target="#passkey-{{.ID}}"
hx-swap="outerHTML"
@@ -18,7 +18,7 @@
</div>
{{end}}
{{else}}
- <div class="empty-state">No passkeys registered. Register one to enable passwordless login.</div>
+ <div class="card text-center text-slate-500 py-6 text-sm italic">No passkeys registered. Register one to enable passwordless login.</div>
{{end}}
</div>
{{end}}
diff --git a/web/templates/settings.html b/web/templates/settings.html
index 4df4cb0..ca1d268 100644
--- a/web/templates/settings.html
+++ b/web/templates/settings.html
@@ -5,228 +5,84 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{.CSRFToken}}">
<title>Settings - Task Dashboard</title>
- <script src="https://unpkg.com/htmx.org@1.9.10"></script>
+ <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
+ <link rel="stylesheet" href="/static/css/output.css">
+ <script src="/static/js/htmx.min.js"></script>
<style>
- :root {
- --bg-primary: #1a1a2e;
- --bg-secondary: #16213e;
- --bg-tertiary: #0f3460;
- --text-primary: #eee;
- --text-secondary: #888;
- --accent: #e94560;
- --success: #28a745;
- --danger: #dc3545;
- --border: #333;
- }
- body {
- font-family: system-ui, sans-serif;
- background: var(--bg-primary);
- color: var(--text-primary);
- margin: 0;
- padding: 20px;
- min-height: 100vh;
- }
- .container { max-width: 900px; margin: 0 auto; }
- h1 { color: var(--accent); margin-bottom: 8px; }
- h2 { color: var(--text-primary); font-size: 1.1em; margin: 24px 0 16px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
- .subtitle { color: var(--text-secondary); margin-bottom: 24px; }
- .back-link { color: var(--accent); text-decoration: none; display: inline-block; margin-bottom: 16px; }
- .back-link:hover { text-decoration: underline; }
-
- .card {
- background: var(--bg-secondary);
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 16px;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 16px;
- }
- .card-title {
- font-size: 1.1em;
- font-weight: 600;
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .source-badge {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 10px;
- font-size: 0.7em;
- font-weight: normal;
- }
- .source-trello { background: #0079bf; }
- .source-todoist { background: #e44332; }
- .source-gcal { background: #9b59b6; }
- .source-gtasks { background: #f39c12; }
-
- .btn {
- background: var(--bg-tertiary);
- color: var(--text-primary);
- border: 1px solid var(--border);
- padding: 8px 16px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 0.9em;
- }
- .btn:hover { background: #1a4a80; }
- .btn:disabled { opacity: 0.5; cursor: not-allowed; }
- .btn-danger { border-color: var(--danger); }
- .btn-danger:hover { background: var(--danger); }
- .btn-sm { padding: 4px 8px; font-size: 0.8em; }
-
- .items-list { display: grid; gap: 8px; }
- .item-row {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px 12px;
- background: var(--bg-tertiary);
- border-radius: 4px;
- }
- .item-row:hover { background: #1a4a80; }
-
- .toggle-switch {
- position: relative;
- width: 44px;
- height: 24px;
- flex-shrink: 0;
- }
- .toggle-switch input { opacity: 0; width: 0; height: 0; }
- .toggle-slider {
- position: absolute;
- cursor: pointer;
- top: 0; left: 0; right: 0; bottom: 0;
- background-color: #555;
- transition: .3s;
- border-radius: 24px;
- }
- .toggle-slider:before {
- position: absolute;
- content: "";
- height: 18px;
- width: 18px;
- left: 3px;
- bottom: 3px;
- background-color: white;
- transition: .3s;
- border-radius: 50%;
- }
- input:checked + .toggle-slider { background-color: var(--success); }
- input:checked + .toggle-slider:before { transform: translateX(20px); }
-
- .item-name { flex: 1; }
- .item-desc { color: var(--text-secondary); font-size: 0.85em; }
- .item-id {
- font-family: monospace;
- font-size: 0.75em;
- color: var(--text-secondary);
- max-width: 200px;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .empty-state {
- color: var(--text-secondary);
- padding: 20px;
- text-align: center;
- }
-
- .add-form {
- display: flex;
- gap: 8px;
- margin-top: 12px;
- padding-top: 12px;
- border-top: 1px solid var(--border);
- }
- .add-form input {
- flex: 1;
- background: var(--bg-tertiary);
- border: 1px solid var(--border);
- color: var(--text-primary);
- padding: 8px 12px;
- border-radius: 4px;
- }
- .add-form input:focus { outline: none; border-color: var(--accent); }
-
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator { display: inline; }
</style>
</head>
-<body hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
-
- <div class="container">
- <a href="/" class="back-link">&larr; Back to Dashboard</a>
- <h1>Settings</h1>
- <p class="subtitle">Configure feature toggles and data sources.</p>
+<body class="bg-slate-950 text-slate-200 min-h-screen p-4 sm:p-8" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
+ <div class="max-w-4xl mx-auto">
+ <a href="/" class="inline-block mb-6 text-slate-400 hover:text-white transition-colors">&larr; Back to Dashboard</a>
+ <h1 class="text-4xl font-light text-white mb-2 tracking-tight">Settings</h1>
+ <p class="text-slate-400 mb-10">Configure feature toggles and data sources.</p>
<!-- Feature Toggles Section -->
- <div class="card">
- <div class="card-header">
- <div class="card-title">Feature Toggles</div>
- </div>
- <div class="items-list" id="toggles-list">
+ <section class="mb-12">
+ <h2 class="text-xl font-medium text-white mb-6 pb-2 border-b border-white/10">Feature Toggles</h2>
+ <div class="grid gap-4" id="toggles-list">
{{if .Toggles}}
{{range .Toggles}}
- <div class="item-row" id="toggle-{{.Name}}">
- <label class="toggle-switch">
- <input type="checkbox"
- {{if .Enabled}}checked{{end}}
- hx-post="/settings/features/toggle"
- hx-vals='{"name": "{{.Name}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
- hx-swap="none"
- hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
- <span class="toggle-slider"></span>
- </label>
- <div class="item-name">
- <strong>{{.Name}}</strong>
- {{if .Description}}<div class="item-desc">{{.Description}}</div>{{end}}
+ <div class="card flex items-center gap-4" id="toggle-{{.Name}}">
+ <div class="flex-1">
+ <strong class="text-white">{{.Name}}</strong>
+ {{if .Description}}<div class="text-sm text-slate-400">{{.Description}}</div>{{end}}
+ </div>
+ <div class="flex items-center gap-6">
+ <label class="relative inline-flex items-center cursor-pointer">
+ <input type="checkbox" value="" class="sr-only peer"
+ {{if .Enabled}}checked{{end}}
+ hx-post="/settings/features/toggle"
+ hx-vals='{"name": "{{.Name}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
+ hx-swap="none"
+ hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
+ <div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
+ </label>
+ <button class="text-xs text-red-400 hover:text-red-300 transition-colors"
+ hx-delete="/settings/features/{{.Name}}"
+ hx-target="#toggle-{{.Name}}"
+ hx-swap="outerHTML"
+ hx-confirm="Delete feature toggle '{{.Name}}'?">
+ Delete
+ </button>
</div>
- <button class="btn btn-danger btn-sm"
- hx-delete="/settings/features/{{.Name}}"
- hx-target="#toggle-{{.Name}}"
- hx-swap="outerHTML"
- hx-confirm="Delete feature toggle '{{.Name}}'?">
- Delete
- </button>
</div>
{{end}}
{{else}}
- <div class="empty-state">No feature toggles configured.</div>
+ <div class="card text-center text-slate-500 py-10">No feature toggles configured.</div>
{{end}}
</div>
- <form class="add-form" hx-post="/settings/features" hx-target="#toggles-list" hx-swap="beforeend">
- <input type="text" name="name" placeholder="Feature name (snake_case)" required pattern="[a-z_]+">
- <input type="text" name="description" placeholder="Description (optional)">
- <button type="submit" class="btn">Add Toggle</button>
+ <form class="mt-4 flex flex-wrap gap-3 p-4 bg-slate-900/40 rounded-xl border border-white/5" hx-post="/settings/features" hx-target="#toggles-list" hx-swap="beforeend">
+ <input type="text" name="name" placeholder="Feature name (snake_case)" required pattern="[a-z_]+"
+ class="flex-1 bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <input type="text" name="description" placeholder="Description (optional)"
+ class="flex-[2] bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <button type="submit" class="bg-blue-600 hover:bg-blue-500 text-white px-6 py-2 rounded-lg text-sm font-medium transition-colors">Add Toggle</button>
</form>
- </div>
+ </section>
<!-- Passkeys Section -->
{{if .WebAuthnEnabled}}
- <div class="card" id="passkeys-card">
- <div class="card-header">
- <div class="card-title">Passkeys</div>
+ <section class="mb-12" id="passkeys-card-section">
+ <h2 class="text-xl font-medium text-white mb-6 pb-2 border-b border-white/10">Passkeys</h2>
+ <div id="passkeys-list" hx-get="/settings/passkeys" hx-trigger="load" hx-swap="innerHTML" class="grid gap-4">
+ <div class="card text-center text-slate-500 py-10">Loading passkeys...</div>
</div>
- <div id="passkeys-list" hx-get="/settings/passkeys" hx-trigger="load" hx-swap="innerHTML">
- <div class="empty-state">Loading passkeys...</div>
+ <div class="mt-4 flex flex-wrap gap-3 p-4 bg-slate-900/40 rounded-xl border border-white/5" id="passkey-register-form">
+ <input type="text" id="passkey-name" placeholder="Passkey name (e.g., MacBook Touch ID)" maxlength="100"
+ class="flex-1 bg-slate-950 border border-white/10 rounded-lg px-4 py-2 text-sm focus:ring-1 focus:ring-blue-500 outline-none">
+ <button type="button" class="bg-slate-700 hover:bg-slate-600 text-white px-6 py-2 rounded-lg text-sm font-medium transition-colors" id="register-passkey-btn">Register New Passkey</button>
</div>
- <div class="add-form" id="passkey-register-form">
- <input type="text" id="passkey-name" placeholder="Passkey name (e.g., MacBook Touch ID)" maxlength="100">
- <button type="button" class="btn" id="register-passkey-btn">Register New Passkey</button>
- </div>
- <p id="passkey-status" style="color: var(--text-secondary); font-size: 0.85em; margin-top: 8px; display: none;"></p>
- </div>
- {{end}}
+ <p id="passkey-status" class="mt-3 text-sm text-slate-400 hidden"></p>
+ </section>
+
<script>
(function() {
- if (!document.getElementById('passkeys-card') || !window.PublicKeyCredential) {
- var card = document.getElementById('passkeys-card');
- if (card) card.style.display = 'none';
+ if (!window.PublicKeyCredential) {
+ var section = document.getElementById('passkeys-card-section');
+ if (section) section.style.display = 'none';
return;
}
@@ -238,7 +94,7 @@
btn.disabled = true;
statusEl.style.display = 'block';
- statusEl.style.color = 'var(--text-secondary)';
+ statusEl.className = 'mt-3 text-sm text-slate-400';
statusEl.textContent = 'Starting registration...';
try {
@@ -279,13 +135,12 @@
});
if (!finishResp.ok) throw new Error('Registration failed');
- statusEl.style.color = 'var(--success)';
+ statusEl.className = 'mt-3 text-sm text-green-400';
statusEl.textContent = 'Passkey registered successfully!';
nameInput.value = '';
- // Reload passkeys list
htmx.trigger('#passkeys-list', 'load');
} catch (e) {
- statusEl.style.color = 'var(--danger)';
+ statusEl.className = 'mt-3 text-sm text-red-400';
if (e.name === 'NotAllowedError') {
statusEl.textContent = 'Registration was cancelled.';
} else {
@@ -310,66 +165,67 @@
}
})();
</script>
+ {{end}}
- <!-- Source Configuration Section -->
- <h2>Data Sources</h2>
- <div style="display: flex; gap: 8px; align-items: center;">
- <form hx-post="/settings/sync" hx-swap="outerHTML" hx-target="#sources-container" hx-indicator=".sync-indicator">
- <button type="submit" class="btn">
- <span class="sync-indicator htmx-indicator">Syncing...</span>
- Sync Available Sources
- </button>
- </form>
- <button class="btn btn-danger"
- hx-post="/settings/clear-cache"
- hx-target="#sync-log"
- hx-swap="outerHTML"
- hx-confirm="Clear all cached data? Next page load will fetch fresh data from all sources.">
- Clear Cache
- </button>
- </div>
-
- {{template "sync-log" .SyncLog}}
-
- <div id="sources-container" style="margin-top: 16px;">
- {{range .Sources}}
- <div class="card">
- <div class="card-header">
- <div class="card-title">
- <span class="source-badge source-{{.}}">{{.}}</span>
- {{if eq . "trello"}}Trello Boards{{end}}
- {{if eq . "todoist"}}Todoist Projects{{end}}
- {{if eq . "gcal"}}Google Calendars{{end}}
- {{if eq . "gtasks"}}Google Task Lists{{end}}
- </div>
+ <!-- Data Sources Section -->
+ <section class="mb-12">
+ <div class="flex flex-wrap items-center justify-between gap-4 mb-6 pb-2 border-b border-white/10">
+ <h2 class="text-xl font-medium text-white">Data Sources</h2>
+ <div class="flex gap-3">
+ <form hx-post="/settings/sync" hx-swap="outerHTML" hx-target="#sources-container" hx-indicator=".sync-indicator">
+ <button type="submit" class="text-sm bg-slate-800 hover:bg-slate-700 text-white px-4 py-2 rounded-lg transition-colors">
+ <span class="sync-indicator htmx-indicator mr-2">Syncing...</span>
+ Sync Available Sources
+ </button>
+ </form>
+ <button class="text-sm border border-red-500/30 text-red-400 hover:bg-red-500/10 px-4 py-2 rounded-lg transition-colors"
+ hx-post="/settings/clear-cache"
+ hx-target="#sync-log"
+ hx-swap="outerHTML"
+ hx-confirm="Clear all cached data? Next page load will fetch fresh data from all sources.">
+ Clear Cache
+ </button>
</div>
- <div class="items-list">
- {{$configs := index $.Configs .}}
- {{if $configs}}
- {{range $configs}}
- <div class="item-row">
- <label class="toggle-switch">
- <input type="checkbox"
- {{if .Enabled}}checked{{end}}
- hx-post="/settings/toggle"
- hx-vals='{"source": "{{.Source}}", "item_type": "{{.ItemType}}", "item_id": "{{.ItemID}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
- hx-swap="none"
- hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
- <span class="toggle-slider"></span>
- </label>
- <span class="item-name">{{.ItemName}}</span>
- <span class="item-id" title="{{.ItemID}}">{{.ItemID}}</span>
- </div>
+ </div>
+
+ {{template "sync-log" .SyncLog}}
+
+ <div id="sources-container" class="grid gap-8 mt-8">
+ {{range .Sources}}
+ <div class="space-y-4">
+ <h3 class="text-sm font-semibold uppercase tracking-wider text-slate-500 flex items-center gap-2">
+ {{if eq . "trello"}}Trello Boards{{else if eq . "todoist"}}Todoist Projects{{else if eq . "gcal"}}Google Calendars{{else if eq . "gtasks"}}Google Task Lists{{else}}{{.}}{{end}}
+ </h3>
+ <div class="grid gap-3 sm:grid-cols-2">
+ {{$configs := index $.Configs .}}
+ {{if $configs}}
+ {{range $configs}}
+ <div class="card flex items-center gap-4">
+ <label class="relative inline-flex items-center cursor-pointer">
+ <input type="checkbox" value="" class="sr-only peer"
+ {{if .Enabled}}checked{{end}}
+ hx-post="/settings/toggle"
+ hx-vals='{"source": "{{.Source}}", "item_type": "{{.ItemType}}", "item_id": "{{.ItemID}}", "enabled": "{{if .Enabled}}false{{else}}true{{end}}"}'
+ hx-swap="none"
+ hx-on::after-request="this.checked = !this.checked; if(event.detail.successful) this.checked = !this.checked;">
+ <div class="w-11 h-6 bg-slate-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
+ </label>
+ <div class="flex-1 min-w-0">
+ <div class="text-white truncate" title="{{.ItemName}}">{{.ItemName}}</div>
+ <div class="text-[10px] text-slate-500 font-mono truncate" title="{{.ItemID}}">{{.ItemID}}</div>
+ </div>
+ </div>
+ {{end}}
+ {{else}}
+ <div class="card col-span-full text-center text-slate-500 py-6 text-sm italic">
+ No items found. Click "Sync Available Sources" to fetch.
+ </div>
{{end}}
- {{else}}
- <div class="empty-state">
- No items configured. Click "Sync Available Sources" to fetch.
- </div>
- {{end}}
+ </div>
</div>
+ {{end}}
</div>
- {{end}}
- </div>
+ </section>
</div>
</body>
</html>
diff --git a/web/templates/shopping-mode.html b/web/templates/shopping-mode.html
index b2f129a..3a1a3bc 100644
--- a/web/templates/shopping-mode.html
+++ b/web/templates/shopping-mode.html
@@ -8,12 +8,6 @@
<link rel="stylesheet" href="/static/css/output.css">
<script src="/static/js/htmx.min.js"></script>
<style>
- body {
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
- min-height: 100vh;
- margin: 0;
- padding: 0;
- }
.store-switcher {
scrollbar-width: none;
-ms-overflow-style: none;
@@ -25,6 +19,7 @@
background: rgba(34, 197, 94, 0.2);
border-radius: 8px;
}
+ .text-shadow-sm { text-shadow: 0 1px 2px rgba(0,0,0,0.5); }
@media print {
* {
text-shadow: none !important;
@@ -74,7 +69,7 @@
}
</style>
</head>
-<body class="text-white" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
+<body class="bg-slate-950 text-slate-200" hx-headers='{"X-CSRF-Token": "{{.CSRFToken}}"}'>
<!-- Header -->
<header class="sticky top-0 z-50 bg-black/40 backdrop-blur-lg border-b border-white/10 no-print">
<div class="flex items-center justify-between px-4 py-3">