diff --git a/app/api/usage.py b/app/api/usage.py
index 278beeb..7a16b5e 100644
--- a/app/api/usage.py
+++ b/app/api/usage.py
@@ -103,8 +103,15 @@ def daily_usage(days: str = Query("30")) -> list[dict]:
@router.get("/summary")
-def usage_summary(days: str = Query("30")) -> dict:
- end = datetime.now(timezone.utc)
+def usage_summary(days: str = Query("30"), offset_days: int = Query(0)) -> dict:
+ """`offset_days` shifts the whole trailing window back in time by that
+ many days (e.g. days=30&offset_days=30 is "the 30 days before the
+ current 30-day period") -- lets the dashboard fetch an equal-length
+ prior period for a simple trend comparison, without a separate
+ calendar-date endpoint."""
+ if offset_days < 0:
+ raise HTTPException(400, "offset_days must be >= 0")
+ end = datetime.now(timezone.utc) - timedelta(days=offset_days)
with get_session() as session:
start = _resolve_start(session, days, end)
rows = get_usage_series(session, start, end)
diff --git a/app/static/css/style.css b/app/static/css/style.css
index f223d67..37ee670 100644
--- a/app/static/css/style.css
+++ b/app/static/css/style.css
@@ -149,9 +149,37 @@ th {
.savings-positive { color: var(--good); font-weight: 600; }
.savings-negative { color: var(--bad); }
-.stat-row { display: flex; gap: 2rem; flex-wrap: wrap; margin-top: 0.75rem; }
+.stat-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 1.25rem 1.5rem;
+ margin-top: 1rem;
+}
+.stat-item { display: flex; align-items: flex-start; gap: 0.65rem; min-width: 0; }
+.stat-icon {
+ flex-shrink: 0;
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.stat-icon svg { width: 17px; height: 17px; }
+.stat-body { min-width: 0; }
.stat-label { color: var(--muted); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em; }
-.stat-value { font-size: 1.3rem; font-weight: 700; margin-top: 0.2rem; }
+.stat-value { font-size: 1.3rem; font-weight: 700; margin-top: 0.15rem; }
+.stat-trend {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+ font-size: 0.75rem;
+ color: var(--muted);
+ margin-top: 0.2rem;
+}
+.stat-trend.good { color: var(--good); }
+.stat-trend.bad { color: var(--bad); }
+.stat-sparkline { display: block; margin-top: 0.3rem; }
.badge {
display: inline-block;
@@ -279,8 +307,8 @@ select, input[type="date"] {
}
.flow-line {
- animation: flow-dash 1s linear infinite;
+ animation: flow-dash 1.2s linear infinite;
}
@keyframes flow-dash {
- to { stroke-dashoffset: -12; }
+ to { stroke-dashoffset: -11.5; }
}
diff --git a/app/web/templates/dashboard.html b/app/web/templates/dashboard.html
index 5195894..1c74024 100644
--- a/app/web/templates/dashboard.html
+++ b/app/web/templates/dashboard.html
@@ -18,27 +18,42 @@