feat: update typography and color scheme; integrate Google Sans fonts

- Replaced "SF Pro Text" and related fonts with "Google Sans", "Google Sans Text", and "Product Sans" in CSS and Tailwind configuration.
- Adjusted color variables for primary, secondary, and error states to enhance UI consistency.
- Modified background colors and button styles for improved aesthetics and usability.
- Introduced new utility classes for layout and component styling in the CSS.
This commit is contained in:
Ned Halksworth
2026-05-15 23:22:12 +01:00
parent a9a35cc751
commit 94c906cc59
3 changed files with 572 additions and 208 deletions
+105 -81
View File
@@ -132,10 +132,17 @@ const NAV_ITEMS: Array<{ id: AppView; label: string; icon: LucideIcon }> = [
];
const ACCENT_OPTIONS: Array<{ id: AccentTheme; label: string }> = [
{ id: "blue", label: "Baby blue" },
{ id: "pink", label: "Pastel pink" },
];
const MATERIAL_ACCENTS = {
primary: "var(--chart-primary)",
secondary: "var(--chart-secondary)",
tertiary: "var(--chart-tertiary)",
error: "var(--chart-error)",
custom: "#b85d84",
};
function App() {
const [themeAccent, setThemeAccent] = useState<AccentTheme>(() => readStoredAccent());
const [user, setUser] = useState<AuthUser | null>(null);
@@ -522,7 +529,7 @@ function App() {
<ShellBackdrop />
<div className="mx-auto grid w-full max-w-[1680px] gap-4 px-3 py-3 lg:grid-cols-[280px_1fr] lg:px-5 lg:py-5">
<div className="app-layout">
<Sidebar
accent={themeAccent}
activeView={activeView}
@@ -531,11 +538,12 @@ function App() {
setupStatus={setupStatus}
user={user}
onAccentChange={setThemeAccent}
onAdd={openNewEntry}
onChange={setActiveView}
onLogout={() => void logout()}
/>
<div className="min-w-0">
<div className="app-content">
<MobileNav activeView={activeView} onChange={setActiveView} />
<TopBar
@@ -561,7 +569,7 @@ function App() {
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.2 }}
className="mt-4"
className="app-main"
>
{activeView === "overview" && (
<OverviewView
@@ -675,7 +683,7 @@ function LoadingScreen({ setupStatus, themeAccent }: { setupStatus: SetupStatus;
<div className="mx-auto flex h-14 w-14 items-center justify-center rounded-lg border border-cyan-300/40 bg-cyan-300/10 text-cyan-200">
<Loader2 className="animate-spin" size={24} aria-hidden="true" />
</div>
<h1 className="mt-5 text-2xl font-semibold tracking-tight text-white">Red Bull command centre</h1>
<h1 className="mt-5 text-2xl font-semibold tracking-tight text-white">Red Bull tracker</h1>
<p className="mt-3 text-sm leading-6 text-slate-300">{setupStatus.message}</p>
</div>
</div>
@@ -719,9 +727,9 @@ function AuthView({
return (
<div className="app-shell min-h-screen bg-[#050711] text-slate-100" data-accent={accent}>
<ShellBackdrop />
<main className="mx-auto grid min-h-screen w-full max-w-6xl items-center gap-6 px-4 py-8 lg:grid-cols-[1.05fr_0.95fr]">
<section className="min-w-0">
<div className="mb-4 inline-flex items-center gap-2 rounded-md border border-cyan-300/30 bg-cyan-300/10 px-3 py-2 text-sm font-semibold text-cyan-100">
<main className="auth-layout">
<section className="auth-hero">
<div className="state-chip mb-4">
<Cloud size={16} aria-hidden="true" />
{setupStatus.state === "ok" ? "Appwrite sync online" : "Appwrite setup check"}
</div>
@@ -729,9 +737,9 @@ function AuthView({
Red Bull Tracker App
</h1>
<p className="mt-5 max-w-xl text-base leading-7 text-slate-300">
Glossy intake telemetry with Appwrite authentication, device sync, and finance-grade Excel exports.
Soft Material You intake tracking with Appwrite authentication, device sync, and polished Excel exports.
</p>
<div className="mt-6 grid gap-3 sm:grid-cols-3">
<div className="auth-signal-grid">
<AuthSignal icon={ShieldCheck} label="User scoped" value="Private entries" />
<AuthSignal icon={Database} label="Database" value={appwriteConfig.databaseId} />
<AuthSignal icon={CheckCircle2} label="Ping" value={setupStatus.state === "ok" ? "Connected" : "Check setup"} />
@@ -746,17 +754,17 @@ function AuthView({
</div>
</section>
<section className="glass-panel p-5 sm:p-6">
<div className="mb-5 flex rounded-md border border-white/10 bg-white/5 p-1">
<section className="auth-panel">
<div className="segmented-control mb-5">
<button
className={`flex-1 rounded px-3 py-2 text-sm font-semibold transition ${mode === "login" ? "bg-cyan-300 text-[#07101f]" : "text-slate-300 hover:bg-white/10"}`}
className={mode === "login" ? "segmented-control-active" : ""}
type="button"
onClick={() => setMode("login")}
>
Log in
</button>
<button
className={`flex-1 rounded px-3 py-2 text-sm font-semibold transition ${mode === "signup" ? "bg-pink-200 text-[#07101f]" : "text-slate-300 hover:bg-white/10"}`}
className={mode === "signup" ? "segmented-control-active" : ""}
type="button"
onClick={() => setMode("signup")}
>
@@ -856,6 +864,7 @@ function Sidebar({
setupStatus,
user,
onAccentChange,
onAdd,
onChange,
onLogout,
}: {
@@ -866,22 +875,28 @@ function Sidebar({
setupStatus: SetupStatus;
user: AuthUser;
onAccentChange: (accent: AccentTheme) => void;
onAdd: () => void;
onChange: (view: AppView) => void;
onLogout: () => void;
}) {
return (
<aside className="glass-panel sticky top-5 hidden h-[calc(100vh-2.5rem)] p-3 lg:flex lg:flex-col">
<div className="mb-7 flex items-center gap-3 px-2 pt-1">
<aside className="material-drawer">
<div className="drawer-brand">
<div className="can-emblem">
<Command size={22} aria-hidden="true" />
</div>
<div className="min-w-0">
<p className="truncate text-sm font-semibold text-white">Red Bull</p>
<p className="truncate text-xs text-cyan-100">Intake telemetry</p>
<p className="truncate text-xs text-cyan-100">Intake tracker</p>
</div>
</div>
<nav className="grid gap-1" aria-label="Main navigation">
<button className="drawer-primary-action" type="button" onClick={onAdd}>
<Plus size={19} aria-hidden="true" />
Add intake
</button>
<nav className="drawer-nav" aria-label="Main navigation">
{NAV_ITEMS.map((item) => (
<button
key={item.id}
@@ -899,8 +914,8 @@ function Sidebar({
<AccentPicker accent={accent} onChange={onAccentChange} />
</div>
<div className="mt-auto grid gap-3">
<div className="rounded-lg border border-white/10 bg-white/[0.06] p-3">
<div className="drawer-footer">
<div className="drawer-info-card">
<div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-400">
{dataLoading ? <Loader2 className="animate-spin text-cyan-200" size={15} aria-hidden="true" /> : <Cloud className="text-cyan-200" size={15} aria-hidden="true" />}
Sync
@@ -909,7 +924,7 @@ function Sidebar({
<p className={`mt-2 text-xs ${setupStatus.state === "ok" ? "text-emerald-200" : "text-amber-200"}`}>{setupStatus.message}</p>
</div>
<div className="rounded-lg border border-white/10 bg-white/[0.06] p-3">
<div className="drawer-info-card">
<p className="truncate text-sm font-semibold text-white">{user.name || user.email || "Appwrite user"}</p>
<p className="mt-1 truncate text-xs text-slate-400">{user.email}</p>
<button className="secondary-button mt-3 w-full justify-center" type="button" onClick={onLogout}>
@@ -924,14 +939,12 @@ function Sidebar({
function MobileNav({ activeView, onChange }: { activeView: AppView; onChange: (view: AppView) => void }) {
return (
<nav className="sticky top-3 z-30 mb-3 grid grid-cols-4 gap-1 rounded-lg border border-white/10 bg-[#090f22]/90 p-1 shadow-fridge backdrop-blur-xl lg:hidden" aria-label="Main navigation">
<nav className="mobile-nav-bar lg:hidden" aria-label="Main navigation">
{NAV_ITEMS.map((item) => (
<button
key={item.id}
type="button"
className={`flex min-h-11 flex-col items-center justify-center gap-1 rounded-md text-[11px] font-medium transition ${
activeView === item.id ? "bg-cyan-300 text-[#07101f] shadow-cyan" : "text-slate-300 hover:bg-white/10"
}`}
className={`mobile-nav-item ${activeView === item.id ? "mobile-nav-item-active" : ""}`}
onClick={() => onChange(item.id)}
>
<item.icon size={16} aria-hidden="true" />
@@ -967,7 +980,9 @@ function TopBar({
onImportExcel: () => void;
onRefresh: () => void;
}) {
const title = NAV_ITEMS.find((item) => item.id === activeView)?.label ?? "Overview";
const activeItem = NAV_ITEMS.find((item) => item.id === activeView) ?? NAV_ITEMS[0];
const title = activeItem.label;
const ActiveIcon = activeItem.icon;
const subtitle = new Intl.DateTimeFormat("en-GB", {
weekday: "long",
day: "numeric",
@@ -975,22 +990,32 @@ function TopBar({
}).format(new Date());
return (
<header className="glass-panel p-4 sm:p-5">
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
<header className="top-app-bar">
<div className="top-app-bar-main">
<div className="top-title-cluster">
<span className="top-app-icon">
<ActiveIcon size={24} aria-hidden="true" />
</span>
<div className="min-w-0">
<p className="flex flex-wrap items-center gap-2 text-sm font-medium text-cyan-100">
<span>{subtitle}</span>
<span className="rounded bg-white/10 px-2 py-1 text-xs text-slate-300">{user.email || "Synced user"}</span>
</p>
<h1 className="mt-1 text-4xl font-semibold tracking-tight text-white sm:text-5xl">{title}</h1>
<p className="top-kicker">{subtitle}</p>
<h1 className="top-title">{title}</h1>
</div>
</div>
<div className="flex flex-wrap gap-2">
<div className="top-meta-row">
<span className="account-chip">{user.email || "Synced user"}</span>
<AccentPicker accent={accent} onChange={onAccentChange} />
</div>
</div>
<div className="top-action-row">
<div className="top-action-primary">
<button className="primary-button" type="button" onClick={onAdd} disabled={Boolean(actionLoading)}>
<Plus size={18} aria-hidden="true" />
Add Intake
</button>
</div>
<div className="top-action-secondary">
<button className="secondary-button" type="button" onClick={onRefresh} disabled={dataLoading}>
{dataLoading ? <Loader2 className="animate-spin" size={17} aria-hidden="true" /> : <RefreshCcw size={17} aria-hidden="true" />}
Sync
@@ -1074,28 +1099,28 @@ function OverviewView({
</section>
<section className="grid gap-3 md:grid-cols-2 xl:grid-cols-4">
<MetricTile icon={CalendarDays} label="This Month" value={dashboard.monthCans} detail={`${dashboard.monthSpend} spent`} accent="#39d5ff" />
<MetricTile icon={PoundSterling} label="Total Spend" value={dashboard.totalSpend} detail={`${dashboard.avgWeeklySpend} weekly average`} accent="#ffb7d9" />
<MetricTile icon={Activity} label="Favourite" value={dashboard.favouriteFlavour} detail="by total cans" accent="#ffd84d" />
<MetricTile icon={TimerReset} label="Days Without" value={dashboard.daysWithoutRedBull} detail={`${dashboard.currentStreak} day streak`} accent="#ff3448" />
<MetricTile icon={CalendarDays} label="This Month" value={dashboard.monthCans} detail={`${dashboard.monthSpend} spent`} accent={MATERIAL_ACCENTS.primary} />
<MetricTile icon={PoundSterling} label="Total Spend" value={dashboard.totalSpend} detail={`${dashboard.avgWeeklySpend} weekly average`} accent={MATERIAL_ACCENTS.secondary} />
<MetricTile icon={Activity} label="Favourite" value={dashboard.favouriteFlavour} detail="by total cans" accent={MATERIAL_ACCENTS.tertiary} />
<MetricTile icon={TimerReset} label="Days Without" value={dashboard.daysWithoutRedBull} detail={`${dashboard.currentStreak} day streak`} accent={MATERIAL_ACCENTS.error} />
</section>
<section className="grid gap-4 xl:grid-cols-[1.25fr_0.75fr]">
<AppCard title="Spend telemetry" subtitle="Last 30 logged days">
<AppCard title="Spend overview" subtitle="Last 30 logged days">
{chartData.length ? (
<ResponsiveContainer width="100%" height={280}>
<AreaChart data={chartData} margin={{ top: 12, right: 12, bottom: 0, left: -18 }}>
<defs>
<linearGradient id="mikuSpend" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#39d5ff" stopOpacity={0.36} />
<stop offset="100%" stopColor="#39d5ff" stopOpacity={0.03} />
<linearGradient id="spendGradient" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor={MATERIAL_ACCENTS.primary} stopOpacity={0.28} />
<stop offset="100%" stopColor={MATERIAL_ACCENTS.primary} stopOpacity={0.03} />
</linearGradient>
</defs>
<CartesianGrid stroke="rgba(203,213,225,0.12)" vertical={false} />
<XAxis dataKey="label" stroke="#94a3b8" tickLine={false} axisLine={false} />
<YAxis stroke="#94a3b8" tickLine={false} axisLine={false} />
<CartesianGrid stroke="var(--chart-grid)" vertical={false} />
<XAxis dataKey="label" stroke="var(--subtle)" tickLine={false} axisLine={false} />
<YAxis stroke="var(--subtle)" tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} />
<Area type="monotone" dataKey="spend" name="Spend" stroke="#39d5ff" fill="url(#mikuSpend)" strokeWidth={3} />
<Area type="monotone" dataKey="spend" name="Spend" stroke={MATERIAL_ACCENTS.primary} fill="url(#spendGradient)" strokeWidth={3} />
</AreaChart>
</ResponsiveContainer>
) : (
@@ -1131,7 +1156,7 @@ function OverviewView({
{flavourData.length ? (
<ResponsiveContainer width="100%" height={260}>
<PieChart>
<Pie data={flavourData} dataKey="value" nameKey="name" innerRadius={70} outerRadius={104} paddingAngle={4} stroke="#080d1f" strokeWidth={4}>
<Pie data={flavourData} dataKey="value" nameKey="name" innerRadius={70} outerRadius={104} paddingAngle={4} stroke="var(--surface-container)" strokeWidth={4}>
{flavourData.map((entry) => (
<Cell key={entry.name} fill={entry.accent} />
))}
@@ -1169,12 +1194,12 @@ function TodayPanel({
<p className="mt-2 text-lg text-slate-300">cans logged</p>
</div>
<div className="grid gap-2 sm:grid-cols-3 lg:min-w-[420px]">
<MiniMetric label="Caffeine" value={dashboard.todayCaffeine} accent="#39d5ff" />
<MiniMetric label="Sugar" value={dashboard.todaySugar} accent="#ffb7d9" />
<MiniMetric label="Streak" value={dashboard.currentStreak} accent="#ffd84d" />
<MiniMetric label="Caffeine" value={dashboard.todayCaffeine} accent={MATERIAL_ACCENTS.primary} />
<MiniMetric label="Sugar" value={dashboard.todaySugar} accent={MATERIAL_ACCENTS.secondary} />
<MiniMetric label="Streak" value={dashboard.currentStreak} accent={MATERIAL_ACCENTS.tertiary} />
</div>
</div>
<div className="mt-6 flex flex-wrap items-center gap-2">
<div className="today-action-row mt-6 hidden flex-wrap items-center gap-2 lg:flex">
<button className="primary-button" type="button" onClick={onAdd}>
<Plus size={18} aria-hidden="true" />
Add intake
@@ -1276,20 +1301,20 @@ function TrendsView({
<AreaChart data={chartData} margin={{ top: 12, right: 16, bottom: 0, left: -12 }}>
<defs>
<linearGradient id="trendSpend" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#39d5ff" stopOpacity={0.28} />
<stop offset="100%" stopColor="#39d5ff" stopOpacity={0.02} />
<stop offset="0%" stopColor={MATERIAL_ACCENTS.primary} stopOpacity={0.26} />
<stop offset="100%" stopColor={MATERIAL_ACCENTS.primary} stopOpacity={0.02} />
</linearGradient>
<linearGradient id="trendCans" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor="#ff3448" stopOpacity={0.2} />
<stop offset="100%" stopColor="#ff3448" stopOpacity={0.02} />
<stop offset="0%" stopColor={MATERIAL_ACCENTS.secondary} stopOpacity={0.22} />
<stop offset="100%" stopColor={MATERIAL_ACCENTS.secondary} stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid stroke="rgba(203,213,225,0.12)" vertical={false} />
<XAxis dataKey="label" stroke="#94a3b8" tickLine={false} axisLine={false} />
<YAxis stroke="#94a3b8" tickLine={false} axisLine={false} />
<CartesianGrid stroke="var(--chart-grid)" vertical={false} />
<XAxis dataKey="label" stroke="var(--subtle)" tickLine={false} axisLine={false} />
<YAxis stroke="var(--subtle)" tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} />
<Area type="monotone" dataKey="spend" name="Spend" stroke="#39d5ff" fill="url(#trendSpend)" strokeWidth={3} />
<Area type="monotone" dataKey="cans" name="Cans" stroke="#ff3448" fill="url(#trendCans)" strokeWidth={3} />
<Area type="monotone" dataKey="spend" name="Spend" stroke={MATERIAL_ACCENTS.primary} fill="url(#trendSpend)" strokeWidth={3} />
<Area type="monotone" dataKey="cans" name="Cans" stroke={MATERIAL_ACCENTS.secondary} fill="url(#trendCans)" strokeWidth={3} />
</AreaChart>
</ResponsiveContainer>
) : (
@@ -1303,11 +1328,11 @@ function TrendsView({
{chartData.length ? (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData} margin={{ top: 12, right: 16, bottom: 0, left: -12 }}>
<CartesianGrid stroke="rgba(203,213,225,0.12)" vertical={false} />
<XAxis dataKey="label" stroke="#94a3b8" tickLine={false} axisLine={false} />
<YAxis stroke="#94a3b8" tickLine={false} axisLine={false} />
<CartesianGrid stroke="var(--chart-grid)" vertical={false} />
<XAxis dataKey="label" stroke="var(--subtle)" tickLine={false} axisLine={false} />
<YAxis stroke="var(--subtle)" tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} />
<Bar dataKey="caffeine" name="Caffeine" fill="#39d5ff" radius={[8, 8, 0, 0]} />
<Bar dataKey="caffeine" name="Caffeine" fill={MATERIAL_ACCENTS.primary} radius={[8, 8, 0, 0]} />
</BarChart>
</ResponsiveContainer>
) : (
@@ -1319,12 +1344,12 @@ function TrendsView({
{weekData.length ? (
<ResponsiveContainer width="100%" height={300}>
<RechartsLineChart data={weekData} margin={{ top: 12, right: 16, bottom: 0, left: -12 }}>
<CartesianGrid stroke="rgba(203,213,225,0.12)" vertical={false} />
<XAxis dataKey="label" stroke="#94a3b8" tickLine={false} axisLine={false} />
<YAxis stroke="#94a3b8" tickLine={false} axisLine={false} />
<CartesianGrid stroke="var(--chart-grid)" vertical={false} />
<XAxis dataKey="label" stroke="var(--subtle)" tickLine={false} axisLine={false} />
<YAxis stroke="var(--subtle)" tickLine={false} axisLine={false} />
<Tooltip content={<ChartTooltip />} />
<Line type="monotone" dataKey="spend" name="Spend" stroke="#ffd84d" strokeWidth={3} dot={{ r: 3 }} />
<Line type="monotone" dataKey="cans" name="Cans" stroke="#ffb7d9" strokeWidth={3} dot={{ r: 3 }} />
<Line type="monotone" dataKey="spend" name="Spend" stroke={MATERIAL_ACCENTS.tertiary} strokeWidth={3} dot={{ r: 3 }} />
<Line type="monotone" dataKey="cans" name="Cans" stroke={MATERIAL_ACCENTS.primary} strokeWidth={3} dot={{ r: 3 }} />
</RechartsLineChart>
</ResponsiveContainer>
) : (
@@ -1338,7 +1363,7 @@ function TrendsView({
{flavourData.length ? (
<ResponsiveContainer width="100%" height={320}>
<PieChart>
<Pie data={flavourData} dataKey="value" nameKey="name" innerRadius={76} outerRadius={118} paddingAngle={4} stroke="#080d1f" strokeWidth={4}>
<Pie data={flavourData} dataKey="value" nameKey="name" innerRadius={76} outerRadius={118} paddingAngle={4} stroke="var(--surface-container)" strokeWidth={4}>
{flavourData.map((entry) => (
<Cell key={entry.name} fill={entry.accent} />
))}
@@ -1384,9 +1409,9 @@ function DataView({
<div className="grid gap-4 xl:grid-cols-[1fr_0.85fr]">
<AppCard title="Appwrite storage" subtitle={`${entries.length} entries synced for this user`}>
<div className="grid gap-3 sm:grid-cols-3">
<MiniMetric label="All-time cans" value={dashboard.allTimeCans} accent="#39d5ff" />
<MiniMetric label="Total spend" value={dashboard.totalSpend} accent="#ffd84d" />
<MiniMetric label="Favourite" value={dashboard.favouriteFlavour} accent="#ffb7d9" />
<MiniMetric label="All-time cans" value={dashboard.allTimeCans} accent={MATERIAL_ACCENTS.primary} />
<MiniMetric label="Total spend" value={dashboard.totalSpend} accent={MATERIAL_ACCENTS.tertiary} />
<MiniMetric label="Favourite" value={dashboard.favouriteFlavour} accent={MATERIAL_ACCENTS.secondary} />
</div>
<div className="mt-5 grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
@@ -1425,7 +1450,7 @@ function DataView({
</AppCard>
<div className="grid gap-4">
<AppCard title="Excel theme" subtitle="Pastel pink and Miku blue workbook">
<AppCard title="Excel theme" subtitle="Pastel pink and soft blue workbook">
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-lg border border-cyan-200/30 bg-cyan-200/10 p-4">
<FileSpreadsheet className="text-cyan-100" size={24} aria-hidden="true" />
@@ -1783,7 +1808,7 @@ function EntryModal({
const initialFlavour = entry?.flavour ?? DEFAULT_FLAVOUR.name;
const [selectedFlavour, setSelectedFlavour] = useState(initialFlavour);
const [customFlavour, setCustomFlavour] = useState("");
const [customAccent, setCustomAccent] = useState("#39d5ff");
const [customAccent, setCustomAccent] = useState(MATERIAL_ACCENTS.custom);
const [cans, setCans] = useState(entry?.cans.toString() ?? "1");
const [sizePreset, setSizePreset] = useState(sizeToPreset(entry?.sizeMl ?? 250));
const [customSize, setCustomSize] = useState(entry?.sizeMl.toString() ?? "250");
@@ -1799,7 +1824,7 @@ function EntryModal({
const editingCustom = entry && !BUILT_IN_FLAVOURS.some((flavour) => flavour.name === entry.flavour);
setSelectedFlavour(editingCustom ? entry.flavour : entry?.flavour ?? DEFAULT_FLAVOUR.name);
setCustomFlavour(editingCustom ? entry.flavour : "");
setCustomAccent(entry?.flavourAccent ?? "#39d5ff");
setCustomAccent(entry?.flavourAccent ?? MATERIAL_ACCENTS.custom);
setCans(entry?.cans.toString() ?? "1");
setSizePreset(sizeToPreset(entry?.sizeMl ?? 250));
setCustomSize(entry?.sizeMl.toString() ?? "250");
@@ -2057,9 +2082,9 @@ function ImportPreviewModal({
</div>
<div className="mb-4 grid gap-3 sm:grid-cols-3">
<MiniMetric label="Ready" value={`${validRows.length}`} accent="#39d5ff" />
<MiniMetric label="Duplicates" value={`${duplicateRows.length}`} accent="#ffd84d" />
<MiniMetric label="Invalid" value={`${invalidRows.length}`} accent="#ff3448" />
<MiniMetric label="Ready" value={`${validRows.length}`} accent={MATERIAL_ACCENTS.primary} />
<MiniMetric label="Duplicates" value={`${duplicateRows.length}`} accent={MATERIAL_ACCENTS.tertiary} />
<MiniMetric label="Invalid" value={`${invalidRows.length}`} accent={MATERIAL_ACCENTS.error} />
</div>
<div className="max-h-[48vh] overflow-auto rounded-lg border border-white/10">
@@ -2306,8 +2331,7 @@ function actionLabel(value: string) {
}
function readStoredAccent(): AccentTheme {
const value = localStorage.getItem(ACCENT_STORAGE_KEY);
return value === "pink" ? "pink" : "blue";
return "pink";
}
export default App;
+453 -114
View File
@@ -2,10 +2,22 @@
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Google Sans";
src: local("Google Sans"), local("GoogleSans-Regular");
font-display: swap;
}
@font-face {
font-family: "Google Sans Text";
src: local("Google Sans Text"), local("GoogleSansText-Regular");
font-display: swap;
}
:root {
color-scheme: light;
font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Avenir Next", "Helvetica Neue", sans-serif;
background: #f5fbff;
font-family: "Google Sans", "Google Sans Text", "Product Sans", Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
background: #fff8fb;
}
* {
@@ -14,18 +26,18 @@
html {
min-width: 320px;
background: #f5fbff;
background: #fff8fb;
}
body {
min-width: 320px;
min-height: 100vh;
margin: 0;
background: #f5fbff;
color: #193042;
font-family: "SF Pro Text", -apple-system, BlinkMacSystemFont, "Avenir Next", "Helvetica Neue", sans-serif;
background: #fff8fb;
color: #21191d;
font-family: "Google Sans", "Google Sans Text", "Product Sans", Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: geometricPrecision;
text-rendering: optimizeLegibility;
}
button,
@@ -39,12 +51,12 @@ button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: 2px solid var(--accent-strong, #74c7ec);
outline: 2px solid var(--primary, #9c4168);
outline-offset: 3px;
}
::selection {
background: color-mix(in srgb, var(--accent, #bdeeff) 55%, transparent);
background: color-mix(in srgb, var(--primary-container, #ffd8e7) 68%, transparent);
}
::-webkit-scrollbar {
@@ -53,83 +65,293 @@ textarea:focus-visible {
}
::-webkit-scrollbar-track {
background: rgba(230, 244, 255, 0.92);
background: var(--surface-container-low, #fff0f5);
}
::-webkit-scrollbar-thumb {
background: rgba(116, 155, 184, 0.45);
background: color-mix(in srgb, var(--outline, #85737a) 42%, transparent);
border: 3px solid var(--surface-container-low, #fff0f5);
border-radius: 999px;
}
@layer components {
.glass-panel {
@apply rounded-lg border shadow-fridge backdrop-blur-2xl;
background: color-mix(in srgb, var(--panel) 86%, white);
border-color: var(--border);
.auth-layout {
@apply mx-auto grid min-h-screen w-full max-w-6xl gap-6 px-4 py-8 lg:grid-cols-[1.05fr_0.95fr];
align-items: center;
}
.can-panel {
@apply rounded-lg border shadow-cyan backdrop-blur-2xl;
background:
linear-gradient(135deg, color-mix(in srgb, var(--accent) 42%, white), rgba(255, 255, 255, 0.96) 52%, color-mix(in srgb, var(--accent-soft) 76%, white));
border-color: var(--border);
.auth-hero {
@apply min-w-0;
}
.can-emblem {
@apply flex h-11 w-11 items-center justify-center rounded-lg border text-[#193042] shadow-cyan;
background: linear-gradient(135deg, var(--accent), #ffffff 58%, var(--accent-warm));
border-color: color-mix(in srgb, var(--accent-strong) 35%, white);
.auth-signal-grid {
@apply mt-6 grid gap-3 sm:grid-cols-3;
}
.command-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border bg-white px-4 py-2 font-display text-sm font-semibold shadow-sm transition active:scale-[0.99];
border-color: var(--border);
color: var(--text);
.auth-panel {
@apply border p-5 shadow-fridge sm:p-6;
background: color-mix(in srgb, var(--surface-container) 88%, white);
border-color: var(--outline-variant);
border-radius: 28px;
}
.primary-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold text-[#193042] shadow-cyan transition disabled:cursor-not-allowed;
background: var(--accent);
border-color: color-mix(in srgb, var(--accent-strong) 42%, white);
.state-chip {
@apply inline-flex min-h-10 items-center gap-2 px-3 text-sm font-semibold;
background: var(--primary-container);
border-radius: 999px;
color: var(--on-primary-container);
}
.secondary-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border bg-white px-4 py-2 text-sm font-semibold shadow-sm transition disabled:cursor-not-allowed;
border-color: var(--border);
color: var(--text);
.segmented-control {
@apply grid grid-cols-2 gap-1 border p-1;
background: var(--surface-container-high);
border-color: var(--outline-variant);
border-radius: 999px;
}
.excel-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold text-[#193042] shadow-cyan transition hover:brightness-105 disabled:cursor-not-allowed;
background: linear-gradient(135deg, #ffe1ef, var(--accent));
border-color: color-mix(in srgb, var(--accent-strong) 35%, white);
}
.danger-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border border-red-300 bg-red-500/90 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-red-400 disabled:cursor-not-allowed disabled:border-slate-300 disabled:bg-slate-200 disabled:text-slate-500;
}
.nav-item {
@apply flex min-h-11 items-center gap-3 rounded-md px-3 text-sm font-medium transition;
.segmented-control button {
@apply min-h-10 px-3 text-sm font-semibold transition;
border-radius: 999px;
color: var(--muted);
}
.segmented-control-active {
background: var(--primary-container);
color: var(--on-primary-container) !important;
}
.app-layout {
@apply mx-auto grid w-full gap-4 px-3 pb-28 pt-3;
max-width: 1720px;
}
.app-content {
@apply min-w-0;
}
.app-main {
@apply mt-4;
}
.material-drawer {
@apply sticky top-6 hidden h-[calc(100vh-3rem)] flex-col border p-4 lg:flex;
background: var(--surface-container);
border-color: var(--outline-variant);
border-radius: 28px;
box-shadow: var(--elevation-1);
}
.drawer-brand {
@apply mb-5 flex items-center gap-3 px-1;
}
.drawer-primary-action {
@apply mb-5 inline-flex min-h-14 items-center justify-center gap-3 px-5 text-sm font-semibold shadow-can transition active:scale-[0.99];
background: var(--primary-container);
border-radius: 18px;
color: var(--on-primary-container);
}
.drawer-nav {
@apply grid gap-2;
}
.drawer-footer {
@apply mt-auto grid gap-3;
}
.drawer-info-card {
@apply border p-4;
background: var(--surface-container-high);
border-color: var(--outline-variant);
border-radius: 22px;
}
.top-app-bar {
@apply border p-4 sm:p-5;
background: color-mix(in srgb, var(--surface-container-low) 84%, white);
border-color: var(--outline-variant);
border-radius: 28px;
box-shadow: var(--elevation-1);
}
.top-app-bar-main {
@apply flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between;
}
.top-title-cluster {
@apply flex min-w-0 items-start gap-3;
}
.top-app-icon {
@apply mt-1 flex h-12 w-12 shrink-0 items-center justify-center;
background: var(--primary-container);
border-radius: 16px;
color: var(--on-primary-container);
}
.top-kicker {
@apply text-sm font-medium;
color: var(--primary);
}
.top-title {
@apply mt-1 break-words text-4xl font-semibold sm:text-5xl;
color: var(--text);
}
.top-meta-row {
@apply flex flex-wrap items-center gap-2;
}
.account-chip {
@apply inline-flex min-h-10 max-w-full items-center rounded-md px-3 text-xs font-semibold;
background: var(--surface-container-high);
color: var(--muted);
}
.top-action-row {
@apply mt-5 flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between;
}
.top-action-primary,
.top-action-secondary {
@apply flex flex-wrap gap-2;
}
.mobile-nav-bar {
@apply fixed inset-x-3 bottom-3 z-40 grid grid-cols-4 gap-1 border p-1 shadow-fridge;
background: color-mix(in srgb, var(--surface-container-high) 92%, white);
border-color: var(--outline-variant);
border-radius: 28px;
}
.mobile-nav-item {
@apply flex min-h-16 flex-col items-center justify-center gap-1 text-[11px] font-semibold transition;
border-radius: 22px;
color: var(--muted);
}
.mobile-nav-item-active {
background: var(--primary-container);
color: var(--on-primary-container) !important;
}
@media (min-width: 1024px) {
.app-layout {
grid-template-columns: 300px minmax(0, 1fr);
gap: 24px;
padding: 24px;
}
.app-main {
margin-top: 24px;
}
}
.glass-panel {
@apply rounded-lg border shadow-fridge;
background: var(--surface-container);
border-color: var(--outline-variant);
border-radius: 24px;
}
.can-panel {
@apply rounded-lg border shadow-can;
background: linear-gradient(135deg, var(--primary-container), var(--surface-container-high) 58%, var(--tertiary-container));
border-color: color-mix(in srgb, var(--primary) 20%, var(--outline-variant));
border-radius: 28px;
}
.can-emblem {
@apply flex h-11 w-11 items-center justify-center rounded-lg border shadow-can;
background: var(--primary-container);
border-color: color-mix(in srgb, var(--primary) 24%, var(--outline-variant));
color: var(--on-primary-container);
}
.command-button,
.secondary-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold shadow-sm transition active:scale-[0.99] disabled:cursor-not-allowed;
background: var(--secondary-container);
border-color: transparent;
color: var(--on-secondary-container);
border-radius: 999px;
}
.primary-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold shadow-can transition active:scale-[0.99] disabled:cursor-not-allowed;
background: var(--primary);
border-color: transparent;
color: var(--on-primary);
border-radius: 999px;
}
.excel-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold shadow-sm transition active:scale-[0.99] disabled:cursor-not-allowed;
background: var(--tertiary-container);
border-color: transparent;
color: var(--on-tertiary-container);
border-radius: 999px;
}
.primary-button:hover,
.secondary-button:hover,
.command-button:hover,
.excel-button:hover,
.icon-button:hover,
.quick-add:hover,
.list-button:hover {
filter: brightness(0.985);
box-shadow: var(--elevation-2);
}
.primary-button:disabled,
.secondary-button:disabled,
.command-button:disabled,
.excel-button:disabled,
.danger-button:disabled {
box-shadow: none;
opacity: 0.58;
}
.danger-button {
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-md border px-4 py-2 text-sm font-semibold shadow-sm transition active:scale-[0.99] disabled:cursor-not-allowed;
background: var(--error);
border-color: transparent;
color: var(--on-error);
border-radius: 999px;
}
.nav-item {
@apply flex min-h-12 items-center gap-3 border border-transparent px-4 text-sm font-medium transition;
border-radius: 999px;
color: var(--muted);
}
.nav-item:hover {
background: var(--surface-container-high);
color: var(--text);
}
.nav-item-active {
@apply shadow-cyan;
background: var(--accent);
color: var(--text) !important;
background: var(--primary-container);
color: var(--on-primary-container) !important;
box-shadow: none;
}
.icon-button {
@apply inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md border bg-white shadow-sm transition;
border-color: var(--border);
@apply inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md border shadow-sm transition;
background: var(--surface-container-high);
border-color: var(--outline-variant);
color: var(--text);
}
.quick-add {
@apply inline-flex min-h-12 items-center gap-2 rounded-md border bg-white px-3 font-display text-sm font-semibold shadow-sm transition hover:border-[var(--accent)];
@apply inline-flex min-h-12 items-center gap-2 rounded-md border px-3 text-sm font-semibold shadow-sm transition;
background: var(--surface-container-high);
border-color: var(--outline-variant);
color: var(--text);
border-color: var(--border);
}
.field-label {
@@ -138,45 +360,65 @@ textarea:focus-visible {
}
.field-control {
@apply w-full rounded-md border bg-white px-3 py-3 text-base font-normal shadow-sm transition;
border-color: var(--border);
@apply w-full rounded-md border px-3 py-3 text-base font-normal shadow-sm transition;
background: var(--surface-container-lowest);
border-color: var(--outline-variant);
color: var(--text);
border-radius: 16px;
}
.field-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 14%, transparent);
}
.modal-panel {
@apply max-h-[92vh] w-full max-w-3xl overflow-y-auto rounded-lg border bg-white/95 p-5 shadow-fridge backdrop-blur-2xl sm:p-6;
border-color: var(--border);
@apply max-h-[92vh] w-full max-w-3xl overflow-y-auto rounded-lg border p-5 shadow-fridge sm:p-6;
background: var(--surface-container-lowest);
border-color: var(--outline-variant);
color: var(--text);
border-radius: 28px;
}
.entry-row {
@apply grid gap-3 rounded-lg border bg-white/80 p-4 transition sm:grid-cols-[1fr_auto] sm:items-center;
border-color: var(--border);
@apply grid gap-3 rounded-lg border p-4 transition sm:grid-cols-[1fr_auto] sm:items-center;
background: var(--surface-container-high);
border-color: var(--outline-variant);
border-radius: 18px;
}
.entry-row:hover {
box-shadow: var(--elevation-1);
}
.list-button {
@apply flex min-h-11 items-center justify-between rounded-lg border bg-white px-3 text-sm font-semibold transition;
border-color: var(--border);
color: var(--accent-strong);
@apply flex min-h-11 items-center justify-between rounded-lg border px-3 text-sm font-semibold transition;
background: var(--secondary-container);
border-color: transparent;
color: var(--on-secondary-container);
}
.status-card {
@apply flex items-center gap-2 rounded-md border px-3 py-2 text-sm shadow-sm backdrop-blur-xl;
@apply flex items-center gap-2 rounded-md border px-3 py-2 text-sm shadow-sm;
}
.accent-picker {
@apply inline-flex min-h-11 items-center gap-1 rounded-md border bg-white/80 p-1 shadow-sm;
border-color: var(--border);
@apply inline-flex min-h-11 items-center gap-1 rounded-md border p-1 shadow-sm;
background: var(--surface-container-high);
border-color: var(--outline-variant);
border-radius: 999px;
}
.accent-picker button {
@apply inline-flex min-h-9 items-center gap-2 rounded px-3 text-sm font-semibold transition;
color: var(--muted);
border-radius: 999px;
}
.accent-picker button:hover,
.accent-picker-active {
background: var(--accent-soft);
color: var(--text) !important;
background: var(--primary-container);
color: var(--on-primary-container) !important;
}
.accent-swatch {
@@ -184,58 +426,117 @@ textarea:focus-visible {
}
.accent-swatch-blue {
background: #bdeeff;
background: #d8e7ff;
}
.accent-swatch-pink {
background: #ffd6e8;
background: #ffd8e7;
}
}
.app-shell {
--accent: #bdeeff;
--accent-soft: #e7f8ff;
--accent-strong: #4aa8d6;
--accent-warm: #ffe2ef;
--bg: #f5fbff;
--panel: #f8fcff;
--panel-strong: #ffffff;
--border: rgba(104, 164, 198, 0.24);
--text: #193042;
--muted: #607587;
--subtle: #7e93a3;
--primary: #9c4168;
--on-primary: #ffffff;
--primary-container: #ffd8e7;
--on-primary-container: #3e001d;
--secondary: #526354;
--on-secondary: #ffffff;
--secondary-container: #d7efe2;
--on-secondary-container: #102116;
--tertiary: #765930;
--on-tertiary: #ffffff;
--tertiary-container: #ffddb2;
--on-tertiary-container: #2b1700;
--error: #ba1a1a;
--on-error: #ffffff;
--error-container: #ffdad6;
--on-error-container: #410002;
--bg: #fff8fb;
--surface: #fff8fb;
--surface-container-lowest: #ffffff;
--surface-container-low: #fff0f5;
--surface-container: #faedf3;
--surface-container-high: #f4e7ee;
--panel: var(--surface-container);
--panel-strong: var(--surface-container-lowest);
--outline: #85737a;
--outline-variant: #d8c2ca;
--text: #21191d;
--muted: #655b60;
--subtle: #83757c;
--accent: var(--primary-container);
--accent-soft: var(--surface-container-low);
--accent-strong: var(--primary);
--accent-warm: #d7efe2;
--chart-primary: #b85d84;
--chart-secondary: #5f7f6f;
--chart-tertiary: #906d1d;
--chart-error: #ba1a1a;
--chart-grid: rgba(132, 115, 122, 0.24);
--elevation-1: 0 1px 2px rgba(69, 54, 62, 0.14), 0 2px 6px rgba(69, 54, 62, 0.08);
--elevation-2: 0 2px 6px rgba(69, 54, 62, 0.14), 0 8px 18px rgba(69, 54, 62, 0.08);
min-height: 100vh;
background: var(--bg) !important;
color: var(--text) !important;
font-family: "Google Sans", "Google Sans Text", "Product Sans", Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
}
.app-shell[data-accent="blue"] {
--primary: #49617d;
--on-primary: #ffffff;
--primary-container: #d8e7ff;
--on-primary-container: #061d35;
--secondary: #5f5d72;
--secondary-container: #e5dff9;
--on-secondary-container: #1c1a2c;
--tertiary: #765930;
--tertiary-container: #ffddb2;
--bg: #f8fbff;
--surface: #f8fbff;
--surface-container-lowest: #ffffff;
--surface-container-low: #eef4fb;
--surface-container: #e8eef6;
--surface-container-high: #e1e9f2;
--outline: #72777f;
--outline-variant: #c2c7cf;
--text: #191c20;
--muted: #5d6269;
--subtle: #777c83;
--accent-warm: #ffd8e7;
--chart-primary: #49617d;
--chart-secondary: #9c4168;
--chart-tertiary: #906d1d;
--chart-error: #ba1a1a;
--chart-grid: rgba(114, 119, 127, 0.24);
}
.app-shell[data-accent="pink"] {
--accent: #ffd6e8;
--accent-soft: #fff0f7;
--accent-strong: #d46c9d;
--accent-warm: #dff6ff;
--bg: #fff8fc;
--panel: #fffbfd;
--border: rgba(210, 108, 157, 0.22);
--primary: #9c4168;
--on-primary: #ffffff;
--primary-container: #ffd8e7;
--on-primary-container: #3e001d;
}
.backdrop-wash {
background:
radial-gradient(circle at 14% 12%, color-mix(in srgb, var(--accent) 48%, transparent), transparent 32%),
radial-gradient(circle at 82% 6%, color-mix(in srgb, var(--accent-warm) 72%, transparent), transparent 34%),
linear-gradient(180deg, var(--bg) 0%, #ffffff 46%, color-mix(in srgb, var(--accent-soft) 66%, white) 100%);
background: linear-gradient(180deg, var(--bg) 0%, var(--surface-container-lowest) 46%, var(--surface-container-low) 100%);
}
.backdrop-grid {
background-image:
linear-gradient(color-mix(in srgb, var(--accent-strong) 12%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--accent-strong) 12%, transparent) 1px, transparent 1px);
background-size: 42px 42px;
opacity: 0.5;
linear-gradient(var(--chart-grid) 1px, transparent 1px),
linear-gradient(90deg, var(--chart-grid) 1px, transparent 1px);
background-size: 48px 48px;
opacity: 0.22;
}
.backdrop-rail {
background: linear-gradient(90deg, var(--accent), #ffffff, var(--accent-warm), var(--accent));
background: linear-gradient(90deg, var(--primary-container), var(--accent-warm), var(--tertiary-container), var(--primary-container));
}
.app-shell *,
.app-shell .tracking-tight,
.app-shell [class*="tracking-"] {
letter-spacing: 0 !important;
}
.app-shell .text-white,
@@ -254,46 +555,77 @@ textarea:focus-visible {
.app-shell .text-cyan-50,
.app-shell .text-cyan-100,
.app-shell .text-cyan-200 {
color: var(--accent-strong) !important;
.app-shell .text-cyan-200,
.app-shell .text-pink-100,
.app-shell .text-pink-200 {
color: var(--primary) !important;
}
.app-shell .text-emerald-200 {
color: #16845c !important;
color: #2d6f57 !important;
}
.app-shell .text-amber-100,
.app-shell .text-amber-200 {
color: #8a5a00 !important;
color: #765930 !important;
}
.app-shell .text-red-100,
.app-shell .text-red-200 {
color: #b4233c !important;
color: var(--error) !important;
}
.app-shell .text-\[\#07101f\] {
color: var(--text) !important;
color: var(--on-primary-container) !important;
}
.app-shell .primary-button,
.app-shell .primary-button * {
color: var(--on-primary) !important;
}
.app-shell .secondary-button,
.app-shell .secondary-button *,
.app-shell .command-button,
.app-shell .command-button * {
color: var(--on-secondary-container) !important;
}
.app-shell .excel-button,
.app-shell .excel-button * {
color: var(--on-tertiary-container) !important;
}
.app-shell .bg-\[\#050711\],
.app-shell .bg-\[\#090f22\]\/90,
.app-shell .bg-\[\#0d142c\],
.app-shell .bg-\[\#080d1f\]\/95,
.app-shell .bg-\[\#070d1f\]\/90 {
background: var(--panel-strong) !important;
.app-shell .bg-\[\#070d1f\]\/90,
.app-shell .bg-\[\#07101f\] {
background: var(--surface-container-high) !important;
}
.app-shell .bg-cyan-300,
.app-shell .bg-cyan-200,
.app-shell .bg-cyan-300\/10,
.app-shell .bg-cyan-200\/10 {
background-color: var(--accent) !important;
.app-shell .bg-cyan-200\/10,
.app-shell .bg-cyan-300\/15 {
background-color: var(--primary-container) !important;
}
.app-shell .bg-pink-200,
.app-shell .bg-pink-200\/10 {
background-color: var(--accent-soft) !important;
background-color: var(--secondary-container) !important;
}
.app-shell .bg-amber-300\/10,
.app-shell .bg-amber-300\/15 {
background-color: var(--tertiary-container) !important;
}
.app-shell .bg-red-500\/10,
.app-shell .bg-red-500\/15 {
background-color: var(--error-container) !important;
}
.app-shell .bg-white\/5,
@@ -305,7 +637,7 @@ textarea:focus-visible {
.app-shell .hover\:bg-white\/10:hover,
.app-shell .hover\:bg-white\/\[0\.10\]:hover,
.app-shell .hover\:bg-white\/\[0\.12\]:hover {
background-color: color-mix(in srgb, var(--accent-soft) 52%, white) !important;
background-color: var(--surface-container-high) !important;
}
.app-shell .border-white\/10,
@@ -313,26 +645,33 @@ textarea:focus-visible {
.app-shell .border-cyan-200\/25,
.app-shell .border-cyan-200\/30,
.app-shell .border-cyan-300\/30,
.app-shell .border-cyan-300\/40,
.app-shell .border-pink-200\/30,
.app-shell .border-pink-200\/40 {
border-color: var(--border) !important;
.app-shell .border-pink-200\/40,
.app-shell .border-amber-300\/40,
.app-shell .border-red-400\/40 {
border-color: var(--outline-variant) !important;
}
.app-shell .shadow-fridge,
.app-shell .shadow-cyan,
.app-shell .shadow-sm {
box-shadow: 0 18px 55px rgba(83, 139, 174, 0.14), 0 1px 2px rgba(83, 139, 174, 0.10) !important;
box-shadow: var(--elevation-1) !important;
}
.app-shell .danger-button,
.app-shell .danger-button * {
color: #ffffff !important;
color: var(--on-error) !important;
}
.app-shell .field-control::placeholder {
color: color-mix(in srgb, var(--muted) 58%, white);
}
.app-shell input[type="checkbox"] {
accent-color: var(--primary);
}
.app-shell .modal-panel {
color: var(--text);
}
+13 -12
View File
@@ -6,20 +6,21 @@ export default {
extend: {
fontFamily: {
display: [
"SF Pro Display",
"SF Pro Text",
"Google Sans",
"Google Sans Text",
"Product Sans",
"Roboto",
"-apple-system",
"BlinkMacSystemFont",
"Avenir Next",
"Helvetica Neue",
"sans-serif",
],
body: [
"SF Pro Text",
"Google Sans",
"Google Sans Text",
"Product Sans",
"Roboto",
"-apple-system",
"BlinkMacSystemFont",
"Avenir Next",
"Helvetica Neue",
"sans-serif",
],
},
@@ -38,11 +39,11 @@ export default {
},
},
boxShadow: {
apple: "0 18px 55px rgba(0, 0, 0, 0.22), 0 1px 2px rgba(0, 0, 0, 0.18)",
fridge: "0 18px 70px rgba(0, 0, 0, 0.34), 0 1px 2px rgba(255, 255, 255, 0.06)",
can: "0 10px 24px rgba(57, 213, 255, 0.12)",
redline: "0 12px 28px rgba(255, 52, 72, 0.26)",
cyan: "0 14px 32px rgba(57, 213, 255, 0.18)",
apple: "0 1px 2px rgba(69, 54, 62, 0.14), 0 2px 6px rgba(69, 54, 62, 0.08)",
fridge: "0 2px 6px rgba(69, 54, 62, 0.12), 0 8px 18px rgba(69, 54, 62, 0.08)",
can: "0 1px 2px rgba(156, 65, 104, 0.18), 0 3px 8px rgba(156, 65, 104, 0.10)",
redline: "0 2px 8px rgba(186, 26, 26, 0.20)",
cyan: "0 1px 2px rgba(156, 65, 104, 0.16), 0 4px 12px rgba(156, 65, 104, 0.10)",
},
backgroundImage: {
"carbon-grid":