diff --git a/prisma.config.ts b/prisma.config.ts
new file mode 100644
index 0000000..831a20f
--- /dev/null
+++ b/prisma.config.ts
@@ -0,0 +1,14 @@
+// This file was generated by Prisma, and assumes you have installed the following:
+// npm install --save-dev prisma dotenv
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ migrations: {
+ path: "prisma/migrations",
+ },
+ datasource: {
+ url: process.env["DATABASE_URL"],
+ },
+});
diff --git a/src/app/globals.css b/src/app/globals.css
index f5576e2..74c8c00 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -163,25 +163,33 @@
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
+
+ /* Background gradients */
+ --bg-main: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 20%, #bbf7d0 40%, #dcfce7 60%, #f0fdf4 80%, #dcfce7 100%);
+ --bg-orbs:
+ radial-gradient(ellipse at 15% 10%, rgba(99, 102, 241, 0.1) 0%, transparent 40%),
+ radial-gradient(ellipse at 85% 20%, rgba(168, 85, 247, 0.08) 0%, transparent 35%),
+ radial-gradient(ellipse at 50% 50%, rgba(59, 130, 246, 0.06) 0%, transparent 50%),
+ radial-gradient(ellipse at 20% 80%, rgba(34, 197, 94, 0.05) 0%, transparent 40%),
+ radial-gradient(ellipse at 80% 85%, rgba(239, 68, 68, 0.05) 0%, transparent 35%);
}
@layer base {
+ html {
+ scroll-behavior: smooth;
+ }
+
* {
@apply border-border outline-ring/50;
}
+
body {
@apply text-foreground;
font-family: var(--font-sans);
letter-spacing: var(--tracking-normal);
- background: linear-gradient(135deg,
- #0f0f1a 0%,
- #1a1a2e 20%,
- #16213e 40%,
- #1a1a2e 60%,
- #0f0f1a 80%,
- #1a1a2e 100%);
- background-attachment: fixed;
+ background-color: transparent;
min-height: 100vh;
+ overflow-x: hidden;
}
body::before {
@@ -191,14 +199,21 @@
left: 0;
right: 0;
bottom: 0;
- background:
+ background: var(--bg-orbs), var(--bg-main);
+ background-size: cover;
+ pointer-events: none;
+ z-index: -50;
+ }
+
+ /* Dark mode overrides */
+ .dark {
+ --bg-main: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 20%, #16213e 40%, #1a1a2e 60%, #0f0f1a 80%, #1a1a2e 100%);
+ --bg-orbs:
radial-gradient(ellipse at 15% 10%, rgba(99, 102, 241, 0.15) 0%, transparent 40%),
radial-gradient(ellipse at 85% 20%, rgba(168, 85, 247, 0.12) 0%, transparent 35%),
radial-gradient(ellipse at 50% 50%, rgba(45, 55, 72, 0.1) 0%, transparent 50%),
radial-gradient(ellipse at 20% 80%, rgba(34, 197, 94, 0.08) 0%, transparent 40%),
radial-gradient(ellipse at 80% 85%, rgba(239, 68, 68, 0.08) 0%, transparent 35%);
- pointer-events: none;
- z-index: -1;
}
/* Calendar styling - reduce overall size */
@@ -217,8 +232,13 @@
/* Animation keyframes */
@keyframes fade-in {
- from { opacity: 0; }
- to { opacity: 1; }
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
}
@keyframes fade-in-up {
@@ -226,6 +246,7 @@
opacity: 0;
transform: translateY(20px);
}
+
to {
opacity: 1;
transform: translateY(0);
@@ -237,6 +258,7 @@
opacity: 0;
transform: translateY(-20px);
}
+
to {
opacity: 1;
transform: translateY(0);
@@ -248,6 +270,7 @@
opacity: 0;
transform: scale(0.95);
}
+
to {
opacity: 1;
transform: scale(1);
@@ -259,6 +282,7 @@
opacity: 0;
transform: translateX(20px);
}
+
to {
opacity: 1;
transform: translateX(0);
@@ -270,6 +294,7 @@
opacity: 0;
transform: translateX(-20px);
}
+
to {
opacity: 1;
transform: translateX(0);
@@ -277,23 +302,49 @@
}
@keyframes pulse-subtle {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.7; }
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.7;
+ }
}
@keyframes float {
- 0%, 100% { transform: translateY(0); }
- 50% { transform: translateY(-5px); }
+
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+
+ 50% {
+ transform: translateY(-5px);
+ }
}
@keyframes shimmer {
- 0% { background-position: -200% 0; }
- 100% { background-position: 200% 0; }
+ 0% {
+ background-position: -200% 0;
+ }
+
+ 100% {
+ background-position: 200% 0;
+ }
}
@keyframes glow {
- 0%, 100% { box-shadow: 0 0 5px rgba(99, 102, 241, 0.5); }
- 50% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.8); }
+
+ 0%,
+ 100% {
+ box-shadow: 0 0 5px rgba(99, 102, 241, 0.5);
+ }
+
+ 50% {
+ box-shadow: 0 0 20px rgba(99, 102, 241, 0.8);
+ }
}
@keyframes confetti {
@@ -301,6 +352,7 @@
transform: translateY(0) rotate(0deg);
opacity: 1;
}
+
100% {
transform: translateY(100vh) rotate(720deg);
opacity: 0;
@@ -349,15 +401,34 @@
}
/* Stagger delay utilities */
-.delay-100 { animation-delay: 100ms; }
-.delay-200 { animation-delay: 200ms; }
-.delay-300 { animation-delay: 300ms; }
-.delay-400 { animation-delay: 400ms; }
-.delay-500 { animation-delay: 500ms; }
-.delay-600 { animation-delay: 600ms; }
+.delay-100 {
+ animation-delay: 100ms;
+}
+
+.delay-200 {
+ animation-delay: 200ms;
+}
+
+.delay-300 {
+ animation-delay: 300ms;
+}
+
+.delay-400 {
+ animation-delay: 400ms;
+}
+
+.delay-500 {
+ animation-delay: 500ms;
+}
+
+.delay-600 {
+ animation-delay: 600ms;
+}
/* Start hidden for animations */
-.opacity-0 { opacity: 0; }
+.opacity-0 {
+ opacity: 0;
+}
/* Smooth transitions */
.transition-smooth {
@@ -474,4 +545,4 @@
opacity: 0.03;
pointer-events: none;
border-radius: inherit;
-}
+}
\ No newline at end of file
diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx
index f385d5c..7182c55 100644
--- a/src/components/Dashboard.tsx
+++ b/src/components/Dashboard.tsx
@@ -187,13 +187,11 @@ export function Dashboard({ user }: DashboardProps) {
);
}
- const pageBackground = theme === 'dark'
- ? 'linear-gradient(135deg, #0a0a14 0%, #141e3c 50%, #0f1932 100%)'
- : 'linear-gradient(135deg, #ffffff 0%, #f0f4f8 50%, #e8ecf0 100%)';
+
return (
-
-
+
+
{preferences && (
diff --git a/src/components/HealthTimelineCard.tsx b/src/components/HealthTimelineCard.tsx
index f9f1af3..6b969fc 100644
--- a/src/components/HealthTimelineCard.tsx
+++ b/src/components/HealthTimelineCard.tsx
@@ -85,7 +85,7 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
const cardBackground =
theme === 'light'
- ? 'linear-gradient(135deg, rgba(6, 95, 70, 0.85) 0%, rgba(4, 120, 87, 0.9) 100%)'
+ ? 'linear-gradient(135deg, rgba(236, 253, 245, 0.9) 0%, rgba(209, 250, 229, 0.8) 100%)'
: 'linear-gradient(135deg, rgba(20, 184, 166, 0.2) 0%, rgba(6, 182, 212, 0.15) 100%)';
const substanceLabel = substance === 'nicotine' ? 'Nicotine' : 'Marijuana';
@@ -98,11 +98,11 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
-
-
+
+
Health Recovery
-
+
{substanceLabel}-free for {formatDuration(minutesSinceQuit)}
@@ -110,10 +110,10 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
{/* Progress to next milestone */}
{nextMilestone && (
-
+
- Next milestone
-
+ Next milestone
+
{formatTimeRemaining(minutesSinceQuit, nextMilestone.timeMinutes)}
@@ -123,7 +123,7 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
style={{ width: `${progressToNext}%` }}
/>
-
{nextMilestone.title}
+
{nextMilestone.title}
)}
@@ -137,19 +137,17 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
return (
{/* Icon */}
{isAchieved ? (
@@ -161,9 +159,9 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
{/* Content */}
-
{milestone.title}
@@ -178,8 +176,8 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
{milestone.description}
-
-
+
+
{formatDuration(milestone.timeMinutes)}
@@ -189,6 +187,6 @@ export function HealthTimelineCard({ usageData, substance }: HealthTimelineCardP
})}
-
+
);
}
diff --git a/src/components/SavingsSetupDialog.tsx b/src/components/SavingsSetupDialog.tsx
index 2c1a7cb..7e200c1 100644
--- a/src/components/SavingsSetupDialog.tsx
+++ b/src/components/SavingsSetupDialog.tsx
@@ -79,7 +79,7 @@ export function SavingsSetupDialog({
costPerUnit: cost,
unitsPerDay: units,
currency,
- substance,
+ substance: substance as 'nicotine' | 'weed',
savingsGoal: savingsGoal ? parseFloat(savingsGoal) : null,
goalName: goalName.trim() || null,
};
@@ -97,13 +97,13 @@ export function SavingsSetupDialog({
return (