Limit alerts
@@ -111,7 +117,7 @@ export function OverviewView({
{chartData.length ? (
-
+
@@ -159,7 +165,7 @@ export function OverviewView({
{flavourData.length ? (
-
+
diff --git a/src/components/views/SettingsView.tsx b/src/components/views/SettingsView.tsx
index de67e3a..c246cb4 100644
--- a/src/components/views/SettingsView.tsx
+++ b/src/components/views/SettingsView.tsx
@@ -60,8 +60,8 @@ export function SettingsView({
}: SettingsViewProps) {
return (
-
-
+
+
-
+
+
+
+
{userInitial(user)}
+
+
{user?.name || "Appwrite user"}
+
{user?.email}
+
+
+
+
+ {dataLoading ? : }
+ {setupStatus.message}
+
+
+
+
+
+
+
@@ -91,7 +113,7 @@ export function SettingsView({
-
+
+
-
-
-
-
-
-
{userInitial(user)}
-
-
{user?.name || "Appwrite user"}
-
{user?.email}
-
-
-
-
- {dataLoading ? : }
- {setupStatus.message}
-
-
-
-
- Log out
-
-
+
@@ -162,7 +170,7 @@ function DataPair({ label, value }: { label: string; value: string }) {
return (
{label}
- {value}
+ {value}
);
}
diff --git a/src/components/views/TrendsView.tsx b/src/components/views/TrendsView.tsx
index 6d5e48a..9e5a63f 100644
--- a/src/components/views/TrendsView.tsx
+++ b/src/components/views/TrendsView.tsx
@@ -14,6 +14,7 @@ import {
XAxis,
YAxis,
} from "recharts";
+import { currency, oneDecimal, wholeNumber } from "../../lib/metrics";
import { AppCard } from "../AppCard";
import { ChartTooltip } from "../ChartTooltip";
import { EmptyState } from "../EmptyState";
@@ -24,6 +25,15 @@ import { SpendForecastCard } from "../SpendForecastCard";
import type { Insight } from "../../lib/dashboard";
import type { Filters, Flavour, RedBullEntry, UserLimits } from "../../types";
+const CHART_HEIGHT = 300;
+const AXIS_PROPS = {
+ stroke: "var(--subtle)",
+ tickLine: false,
+ axisLine: false,
+ interval: "preserveStartEnd",
+ minTickGap: 24,
+} as const;
+
type TrendsViewProps = {
chartData: Array<{ label: string; spend: number; cans: number; caffeine: number; sugar: number }>;
weekData: Array<{ label: string; spend: number; cans: number }>;
@@ -49,32 +59,47 @@ export function TrendsView({
onFilterChange,
onSaveLimits,
}: TrendsViewProps) {
+ const totalSpend = chartData.reduce((total, point) => total + point.spend, 0);
+ const totalCans = chartData.reduce((total, point) => total + point.cans, 0);
+ const totalCaffeine = chartData.reduce((total, point) => total + point.caffeine, 0);
+ const weekSpend = weekData.reduce((total, point) => total + point.spend, 0);
+ const weekCans = weekData.reduce((total, point) => total + point.cans, 0);
+
+ const cansSpendLabel = `Spend and cans over ${chartData.length} logged days, total ${currency.format(totalSpend)} and ${oneDecimal.format(totalCans)} cans`;
+ const caffeineLabel = `Estimated caffeine per day over ${chartData.length} logged days, total ${wholeNumber.format(totalCaffeine)} milligrams`;
+ const weekLabel = `Weekly spend and cans across ${weekData.length} weeks, total ${currency.format(weekSpend)} and ${oneDecimal.format(weekCans)} cans`;
+ const flavourLabel = flavourData.length
+ ? `Cans by flavour across ${flavourData.length} flavours. Most logged: ${flavourData[0].name}`
+ : "";
+
return (
{chartData.length ? (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- } />
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } />
+
+
+
+
+
) : (
)}
@@ -84,15 +109,17 @@ export function TrendsView({
{chartData.length ? (
-
-
-
-
-
- } />
-
-
-
+
+
+
+
+
+
+ } />
+
+
+
+
) : (
)}
@@ -100,16 +127,18 @@ export function TrendsView({
{weekData.length ? (
-
-
-
-
-
- } />
-
-
-
-
+
+
+
+
+
+
+ } />
+
+
+
+
+
) : (
)}
@@ -119,16 +148,18 @@ export function TrendsView({
{flavourData.length ? (
-
-
-
- {flavourData.map((entry) => (
- |
- ))}
-
- } />
-
-
+
+
+
+
+ {flavourData.map((entry) => (
+ |
+ ))}
+
+ } />
+
+
+
) : (
)}
diff --git a/src/index.css b/src/index.css
index 6b25662..af12618 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1397,7 +1397,7 @@ textarea:focus-visible {
@media (min-width: 640px) {
.hero-stat-row {
- grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 10px;
}
@@ -1476,11 +1476,6 @@ textarea:focus-visible {
.today-stat-value {
font-size: clamp(3rem, 6vw, 5rem);
}
-
- .logbook-layout {
- grid-template-columns: minmax(280px, 340px) minmax(0, 1fr);
- align-items: start;
- }
}
@media (min-width: 1024px) {
@@ -1714,3 +1709,269 @@ textarea:focus-visible {
padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px));
}
}
+
+/* --- View redesign additions (A5 / B2.2 / B2.3 / B3.4) --- */
+
+/* Floating add button: mobile only, sits above the bottom nav, safe-area aware. */
+.fab-add {
+ position: fixed;
+ right: max(16px, env(safe-area-inset-right, 0px));
+ bottom: calc(96px + env(safe-area-inset-bottom, 0px));
+ z-index: 45;
+ display: grid;
+ width: 56px;
+ height: 56px;
+ place-items: center;
+ color: var(--on-primary, #ffffff);
+ background: var(--primary, #2563c7);
+ border: 0;
+ border-radius: 999px;
+ box-shadow: var(--elevation-2, 0 12px 34px rgba(60, 64, 67, 0.16));
+ transition: background 0.16s ease, transform 0.16s ease;
+}
+
+.fab-add:hover {
+ background: color-mix(in srgb, var(--primary, #2563c7) 90%, #000000);
+}
+
+.fab-add:active {
+ transform: scale(0.94);
+}
+
+@media (min-width: 1024px) {
+ .fab-add {
+ display: none;
+ }
+}
+
+/* Promoted quick-add row: thumb-friendly targets with a primary icon chip. */
+.quick-add-button {
+ min-height: 60px;
+}
+
+.quick-add-icon {
+ color: var(--on-primary, #ffffff);
+ background: var(--primary, #2563c7);
+}
+
+/* Limit hero rows: state-tinted borders on top of the progress fill colours. */
+.limit-row--warn {
+ border-color: color-mix(in srgb, var(--warning, #8d5700) 42%, transparent);
+}
+
+.limit-row--over {
+ border-color: color-mix(in srgb, var(--error, #ba1a1a) 42%, transparent);
+}
+
+.limit-progress-fill {
+ transition: width 0.3s ease;
+}
+
+/* Compact filter bar: collapses behind a toggle on mobile, single row on desktop. */
+.filter-bar {
+ display: grid;
+ gap: 12px;
+ padding: 12px 14px;
+ background: var(--surface-container-lowest, #ffffff);
+ border: 1px solid var(--outline-variant, #d8e1ee);
+ border-radius: 22px;
+ box-shadow: var(--elevation-1, 0 1px 2px rgba(60, 64, 67, 0.08));
+}
+
+.filter-bar-toggle {
+ display: inline-flex;
+ min-height: 40px;
+ align-items: center;
+ justify-self: start;
+ gap: 8px;
+ padding: 0 14px;
+ color: var(--text, #202124);
+ background: var(--surface-container-low, #edf2fa);
+ border: 1px solid var(--outline-variant, #d8e1ee);
+ border-radius: 999px;
+ font-size: 13px;
+ font-weight: 500;
+}
+
+.filter-bar-controls {
+ display: grid;
+ gap: 10px;
+}
+
+.filter-bar-controls[data-open="false"] {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .filter-bar-toggle {
+ display: none;
+ }
+
+ .filter-bar-controls,
+ .filter-bar-controls[data-open="false"] {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-end;
+ }
+
+ .filter-bar-field {
+ flex: 1 1 150px;
+ min-width: 140px;
+ }
+
+ .filter-bar-field--search {
+ flex: 2 1 220px;
+ }
+
+ .filter-bar-clear {
+ flex: 0 0 auto;
+ }
+}
+
+/* Logbook day groups. */
+.day-group {
+ display: grid;
+ gap: 8px;
+}
+
+.day-group-header {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 4px 12px;
+ padding: 0 2px;
+}
+
+.day-group-title {
+ color: var(--text, #202124);
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.day-group-subtotals {
+ color: var(--muted, #5f6670);
+ font-size: 12px;
+}
+
+/* Entry row layout: content left, actions right on wider screens. */
+.entry-row-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+}
+
+.entry-log-again {
+ min-height: 36px;
+ padding: 0 14px;
+ font-size: 12px;
+}
+
+@media (min-width: 640px) {
+ .entry-row {
+ grid-template-columns: minmax(0, 1fr) auto;
+ align-items: center;
+ }
+
+ .entry-row-actions {
+ justify-content: flex-end;
+ }
+}
+
+/* Stat hint popover (replaces title-attribute tooltips). */
+.stat-hint {
+ position: relative;
+ display: inline-flex;
+ margin-left: 4px;
+ vertical-align: middle;
+}
+
+.stat-hint-trigger {
+ display: inline-flex;
+ width: 18px;
+ height: 18px;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ color: var(--subtle, #80868b);
+ background: transparent;
+ border: 0;
+ border-radius: 999px;
+}
+
+.stat-hint-trigger:hover {
+ color: var(--primary, #2563c7);
+ background: color-mix(in srgb, var(--primary-container, #dbe9ff) 80%, transparent);
+}
+
+.stat-hint-popover {
+ position: absolute;
+ bottom: calc(100% + 6px);
+ left: 0;
+ z-index: 60;
+ width: max-content;
+ max-width: 220px;
+ padding: 8px 10px;
+ color: var(--text, #202124);
+ background: var(--surface-container-lowest, #ffffff);
+ border: 1px solid var(--outline-variant, #d8e1ee);
+ border-radius: 12px;
+ box-shadow: var(--elevation-2, 0 12px 28px rgba(60, 64, 67, 0.14));
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 1.4;
+ text-align: left;
+}
+
+/* Entry modal field groups. */
+.entry-fieldset {
+ display: grid;
+ gap: 12px;
+ min-width: 0;
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.entry-fieldset-legend {
+ padding: 0;
+ color: var(--primary, #2563c7);
+ font-size: 12px;
+ font-weight: 500;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+}
+
+/* Settings danger zone. */
+.danger-zone {
+ border-color: color-mix(in srgb, var(--error, #ba1a1a) 38%, transparent);
+ background: color-mix(in srgb, var(--error-container, #ffdad6) 22%, var(--surface-container-lowest, #ffffff));
+}
+
+.danger-zone-title {
+ color: var(--error, #ba1a1a);
+}
+
+/* Password visibility toggle. */
+.password-toggle {
+ position: absolute;
+ top: 50%;
+ right: 6px;
+ display: inline-flex;
+ width: 34px;
+ height: 34px;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ color: var(--muted, #5f6670);
+ background: transparent;
+ border: 0;
+ border-radius: 999px;
+ transform: translateY(-50%);
+}
+
+.password-toggle:hover {
+ color: var(--text, #202124);
+ background: color-mix(in srgb, var(--text, #202124) 6%, transparent);
+}