From bb3bf6b2382ad6b5d9bbb90cd0d298da3e0c7b23 Mon Sep 17 00:00:00 2001 From: Jeet Debnath Date: Thu, 26 Mar 2026 12:05:46 +0530 Subject: [PATCH] login page update --- .claude/settings.local.json | 3 +- src/App.css | 369 +++++++++++++++++++++++++------ src/components/TreeAnimation.tsx | 153 +++++++++++++ src/pages/LoginPage.tsx | 59 +++-- start-all.sh | 38 ++-- 5 files changed, 521 insertions(+), 101 deletions(-) create mode 100644 src/components/TreeAnimation.tsx diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 472f475..f6f4186 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(/Users/jeet/Library/Python/3.9/bin/pytest -v 2>&1)", "Bash(conda run:*)", "Bash(git rm:*)", - "Bash(git remote:*)" + "Bash(git remote:*)", + "Bash(find /Users/jeet/Desktop/Jio/grateful-journal/src -type f -name *.ts -o -name *.tsx)" ] } } diff --git a/src/App.css b/src/App.css index 480b520..df55149 100644 --- a/src/App.css +++ b/src/App.css @@ -43,24 +43,7 @@ /* ============================ LOGIN PAGE ============================ */ -.login-page { - height: 100dvh; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(160deg, #eef6ee 0%, #dcfce7 100%); - padding: 1.5rem; - overflow: hidden; -} - -.login-page__loading { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - color: #9ca3af; -} - +/* ── Loading state ──────────────────────────────────────── */ .login-page__spinner { width: 28px; height: 28px; @@ -70,42 +53,141 @@ animation: spin 0.7s linear infinite; } -.login-card { - background: #fff; +/* ── Login page — two-panel layout ─────────────────────── */ +.lp { + height: 100dvh; + display: grid; + grid-template-columns: 55% 45%; + overflow: hidden; + background: linear-gradient(160deg, #eef6ee 0%, #dcfce7 100%); +} + +/* Loading state wrapper */ +.lp--loading { + grid-template-columns: 1fr; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + background: var(--color-bg-soft); + color: #9ca3af; +} + +/* ── Left: animated tree hero ───────────────────────────── */ +.lp__hero { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem 1.5rem; + gap: 1.25rem; + position: relative; + overflow: hidden; +} + +.lp__hero-words { + text-align: center; + z-index: 1; +} + +.lp__quote { + font-family: "Sniglet", system-ui; + font-size: 1.625rem; + font-weight: 700; + color: #16a34a; + margin: 0 0 0.4rem; + opacity: 0; + animation: lp-rise 0.9s ease-out 3.0s forwards; +} + +.lp__subquote { + font-size: 0.9375rem; + color: #22c55e; + margin: 0; + opacity: 0; + animation: lp-rise 0.9s ease-out 3.35s forwards; +} + +/* ── Right: login panel ─────────────────────────────────── */ +.lp__panel { + display: flex; + align-items: center; + justify-content: center; padding: 2rem; - border-radius: 20px; - border-top: 4px solid #22c55e; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); +} + +.lp__form { width: 100%; - max-width: 360px; + max-width: 320px; + display: flex; + flex-direction: column; + gap: 1.75rem; + background: #fff; + border-radius: 24px; + border-top: 4px solid #22c55e; + padding: 2rem 1.75rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08); +} + +.lp__brand { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.625rem; + animation: lp-rise 0.7s ease-out 0.1s both; +} + +.lp__icon { + font-size: 3.25rem; + line-height: 1; + animation: lp-pop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0.1s both; +} + +.lp__title { + margin: 0; + font-size: 2.25rem; + font-weight: 700; + color: #22c55e; + font-family: "Sniglet", system-ui; + letter-spacing: -0.02em; text-align: center; } -.login-card__brand { - margin-bottom: 1.75rem; -} - -.login-card__title { - margin: 0 0 0.375rem; - font-size: 1.625rem; - font-weight: 700; - color: #22c55e; - letter-spacing: -0.02em; - font-family: "Sniglet", system-ui; -} - -.login-card__tagline { +.lp__tagline { margin: 0; color: #6b7280; font-size: 0.9375rem; + text-align: center; + line-height: 1.65; + animation: lp-rise 0.7s ease-out 0.25s both; } -.login-card__actions { +.lp__actions { display: flex; flex-direction: column; gap: 1rem; + animation: lp-rise 0.7s ease-out 0.4s both; } +.lp__privacy { + margin: 0; + font-size: 0.8rem; + color: #9ca3af; + text-align: center; +} + +.lp__error { + margin: 0; + padding: 0.625rem 0.75rem; + color: #b91c1c; + font-size: 0.875rem; + background: #fef2f2; + border-radius: 8px; + text-align: left; +} + +/* kept for other callers */ .login-card__error { margin: 0; padding: 0.625rem 0.75rem; @@ -116,6 +198,189 @@ text-align: left; } +/* ── Tree animation ─────────────────────────────────────── */ +.tree-wrap { + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.tree-svg { + width: 100%; + max-width: 290px; + height: auto; + overflow: visible; +} + +.t-trunk { + stroke-dasharray: 200; + stroke-dashoffset: 200; + animation: t-draw 0.95s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; +} + +.t-root { + stroke-dasharray: 120; + stroke-dashoffset: 120; + animation: t-draw 0.55s ease-out forwards; +} + +.t-branch { + stroke-dasharray: 300; + stroke-dashoffset: 300; + animation: t-draw 0.65s ease-out forwards; +} + +@keyframes t-draw { + to { stroke-dashoffset: 0; } +} + +.t-leaf { + transform-box: fill-box; + transform-origin: center; + transform: scale(0); + opacity: 0; + animation: t-pop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; +} + +@keyframes t-pop { + 0% { transform: scale(0); opacity: 0; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} + +.t-canopy { + transform-origin: 140px 412px; + animation: t-sway 5.5s ease-in-out 3.4s infinite; +} + +@keyframes t-sway { + 0%, 100% { transform: rotate(0deg); } + 28% { transform: rotate(1.8deg); } + 72% { transform: rotate(-1.5deg); } +} + +.t-particle { + opacity: 0; + animation: t-float linear infinite; +} + +@keyframes t-float { + 0% { transform: translateY(0) translateX(0); opacity: 0; } + 8% { opacity: 0.7; } + 88% { opacity: 0.35; } + 100% { transform: translateY(-380px) translateX(28px); opacity: 0; } +} + +/* ── Login animations ───────────────────────────────────── */ +@keyframes lp-rise { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes lp-pop { + from { opacity: 0; transform: scale(0.55); } + to { opacity: 1; transform: scale(1); } +} + +/* ── Login responsive — phones (≤ 640px) ───────────────── */ +@media (max-width: 640px) { + /* Stack vertically: tree hero on top, login below */ + .lp { + grid-template-columns: 1fr; + grid-template-rows: 56vh 1fr; + } + + .lp__hero { + padding: 1.5rem 1rem; + gap: 1rem; + justify-content: center; + } + + /* Slightly smaller tree so it fits within the hero panel */ + .tree-svg { + max-width: 220px; + } + + .lp__quote { + font-size: 1.2rem; + margin-bottom: 0.2rem; + } + + .lp__subquote { + font-size: 0.8125rem; + } + + .lp__panel { + padding: 1.25rem 1.25rem calc(1.5rem + env(safe-area-inset-bottom)); + border-left: none; + overflow-y: auto; + align-items: center; + } + + .lp__form { + max-width: 420px; + gap: 1.25rem; + padding: 1.75rem 1.5rem; + } + + .lp__title { + font-size: 1.875rem; + } + + .lp__icon { + font-size: 2.5rem; + } + + .lp__tagline { + font-size: 0.875rem; + line-height: 1.55; + } +} + +/* ── Login responsive — small phones (≤ 390px, e.g. iPhone SE) */ +@media (max-width: 390px) { + .lp { + grid-template-rows: 50vh 1fr; + } + + .tree-svg { + max-width: 190px; + } + + .lp__quote { + font-size: 1.05rem; + } + + .lp__subquote { + font-size: 0.75rem; + } + + .lp__panel { + padding: 1rem 1rem calc(1.25rem + env(safe-area-inset-bottom)); + } + + .lp__form { + padding: 1.5rem 1.25rem; + } + + .lp__title { + font-size: 1.625rem; + } + + .lp__icon { + font-size: 2rem; + } + + .lp__form { + gap: 1rem; + } + + .lp__tagline { + font-size: 0.8125rem; + } +} + .google-sign-in-btn { display: inline-flex; align-items: center; @@ -1667,9 +1932,7 @@ color: #4ade80; } -[data-theme="dark"] .login-card__title { - color: #4ade80; -} +/* login page is always light — no dark overrides */ /* -- Calendar -- */ [data-theme="dark"] .calendar-day { @@ -2001,27 +2264,7 @@ background: #333; } -/* -- Login page -- */ -[data-theme="dark"] .login-page { - background: linear-gradient(160deg, #0f0f0f 0%, #0a2e14 50%, #0f0f0f 100%); -} - -[data-theme="dark"] .login-card { - background: #1a1a1a; - border-top-color: #4ade80; - box-shadow: - 0 8px 32px rgba(0, 0, 0, 0.5), - 0 0 0 1px rgba(74, 222, 128, 0.08); -} - -[data-theme="dark"] .login-card__tagline { - color: #7a8a7a; -} - -[data-theme="dark"] .login-card__error { - background: rgba(239, 68, 68, 0.1); - color: #f87171; -} +/* -- Login page — light mode only, no dark theme overrides -- */ /* -- Google sign-in btn -- */ [data-theme="dark"] .google-sign-in-btn { diff --git a/src/components/TreeAnimation.tsx b/src/components/TreeAnimation.tsx new file mode 100644 index 0000000..de7f81b --- /dev/null +++ b/src/components/TreeAnimation.tsx @@ -0,0 +1,153 @@ +const LEAVES = [ + // Left low cluster (b1 tip ~40,308) + { cx: 34, cy: 302, r: 18, fill: '#22c55e', delay: '1.65s' }, + { cx: 14, cy: 295, r: 15, fill: '#16a34a', delay: '1.70s' }, + { cx: 26, cy: 280, r: 16, fill: '#4ade80', delay: '1.68s' }, + { cx: 48, cy: 290, r: 13, fill: '#15803d', delay: '1.72s' }, + { cx: 8, cy: 312, r: 12, fill: '#22c55e', delay: '1.75s' }, + // Right low cluster (b2 tip ~240,302) + { cx: 246, cy: 296, r: 18, fill: '#22c55e', delay: '1.75s' }, + { cx: 266, cy: 290, r: 15, fill: '#16a34a', delay: '1.80s' }, + { cx: 254, cy: 275, r: 16, fill: '#4ade80', delay: '1.78s' }, + { cx: 234, cy: 286, r: 13, fill: '#15803d', delay: '1.82s' }, + { cx: 270, cy: 308, r: 12, fill: '#22c55e', delay: '1.85s' }, + // sb3/sb4 mid-tips + { cx: 50, cy: 270, r: 13, fill: '#4ade80', delay: '1.80s' }, + { cx: 228, cy: 267, r: 13, fill: '#4ade80', delay: '1.85s' }, + // sb1/sb2 outer tips + { cx: 8, cy: 255, r: 14, fill: '#4ade80', delay: '1.90s' }, + { cx: 270, cy: 251, r: 14, fill: '#4ade80', delay: '1.90s' }, + // Left mid cluster (b3 tip ~44,258) + { cx: 38, cy: 252, r: 16, fill: '#22c55e', delay: '2.05s' }, + { cx: 18, cy: 246, r: 13, fill: '#4ade80', delay: '2.10s' }, + { cx: 30, cy: 232, r: 14, fill: '#16a34a', delay: '2.08s' }, + { cx: 52, cy: 240, r: 11, fill: '#86efac', delay: '2.12s' }, + { cx: 12, cy: 264, r: 10, fill: '#22c55e', delay: '2.15s' }, + // Right mid cluster (b4 tip ~236,255) + { cx: 242, cy: 248, r: 16, fill: '#22c55e', delay: '2.10s' }, + { cx: 262, cy: 242, r: 13, fill: '#4ade80', delay: '2.15s' }, + { cx: 250, cy: 228, r: 14, fill: '#16a34a', delay: '2.12s' }, + { cx: 230, cy: 238, r: 11, fill: '#86efac', delay: '2.18s' }, + { cx: 266, cy: 260, r: 10, fill: '#22c55e', delay: '2.20s' }, + // sb5/sb6 outer tips (~16,214 and ~262,210) + { cx: 12, cy: 208, r: 13, fill: '#86efac', delay: '2.30s' }, + { cx: 266, cy: 206, r: 13, fill: '#86efac', delay: '2.30s' }, + // Left upper cluster (b5 tip ~86,218) + { cx: 80, cy: 212, r: 17, fill: '#4ade80', delay: '2.45s' }, + { cx: 62, cy: 202, r: 14, fill: '#22c55e', delay: '2.50s' }, + { cx: 90, cy: 196, r: 12, fill: '#86efac', delay: '2.48s' }, + { cx: 68, cy: 188, r: 13, fill: '#4ade80', delay: '2.52s' }, + // Right upper cluster (b6 tip ~194,214) + { cx: 200, cy: 208, r: 17, fill: '#4ade80', delay: '2.48s' }, + { cx: 218, cy: 198, r: 14, fill: '#22c55e', delay: '2.52s' }, + { cx: 192, cy: 193, r: 12, fill: '#86efac', delay: '2.50s' }, + { cx: 210, cy: 185, r: 13, fill: '#4ade80', delay: '2.55s' }, + // Top center canopy (b7 tip ~128,196) + { cx: 120, cy: 188, r: 16, fill: '#4ade80', delay: '2.60s' }, + { cx: 140, cy: 176, r: 21, fill: '#22c55e', delay: '2.65s' }, + { cx: 160, cy: 188, r: 16, fill: '#4ade80', delay: '2.62s' }, + { cx: 126, cy: 166, r: 13, fill: '#16a34a', delay: '2.68s' }, + { cx: 154, cy: 164, r: 14, fill: '#86efac', delay: '2.72s' }, + { cx: 140, cy: 154, r: 18, fill: '#22c55e', delay: '2.75s' }, + { cx: 134, cy: 142, r: 12, fill: '#4ade80', delay: '2.78s' }, + { cx: 148, cy: 140, r: 11, fill: '#86efac', delay: '2.80s' }, +] + +const PARTICLES = [ + { cx: 45, cy: 420, r: 5, fill: '#4ade80', delay: '3.5s', dur: '7s' }, + { cx: 235, cy: 415, r: 3, fill: '#86efac', delay: '5.0s', dur: '9s' }, + { cx: 88, cy: 425, r: 4, fill: '#22c55e', delay: '4.0s', dur: '8s' }, + { cx: 192, cy: 418, r: 5, fill: '#4ade80', delay: '6.0s', dur: '10s' }, + { cx: 140, cy: 422, r: 3, fill: '#86efac', delay: '3.8s', dur: '6s' }, + { cx: 115, cy: 416, r: 4, fill: '#22c55e', delay: '7.0s', dur: '8s' }, + { cx: 165, cy: 424, r: 3, fill: '#4ade80', delay: '4.5s', dur: '7s' }, +] + +export function TreeAnimation() { + return ( +
+ +
+ ) +} diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index daea991..5019f78 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -2,7 +2,7 @@ import { useAuth } from '../contexts/AuthContext' import { useNavigate } from 'react-router-dom' import { useEffect, useState } from 'react' import { GoogleSignInButton } from '../components/GoogleSignInButton' -import { LoginCard } from '../components/LoginCard' +import { TreeAnimation } from '../components/TreeAnimation' export default function LoginPage() { const { user, loading, signInWithGoogle } = useAuth() @@ -12,9 +12,7 @@ export default function LoginPage() { useEffect(() => { if (loading) return - if (user) { - navigate('/', { replace: true }) - } + if (user) navigate('/', { replace: true }) }, [user, loading, navigate]) async function handleGoogleSignIn() { @@ -31,31 +29,46 @@ export default function LoginPage() { if (loading) { return ( -
-
- -

Loading…

-
+
+ +

Loading…

) } return ( -
- - - {error && ( -

- {error} +

+ {/* ── Left: animated tree hero ─────────────────── */} +
+ +
+

Grow your gratitude.

+

One small moment at a time.

+
+
+ + {/* ── Right: login panel ───────────────────────── */} +
+
+
+ 🌱 +

Grateful Journal

+
+ +

+ A private space for gratitude and reflection.
+ No feeds. No noise. Just you and your thoughts.

- )} - + +
+ +

🔒 End-to-end encrypted. We never read your entries.

+ {error && ( +

{error}

+ )} +
+
+
) } diff --git a/start-all.sh b/start-all.sh index 16daf87..0c7f5e7 100755 --- a/start-all.sh +++ b/start-all.sh @@ -5,47 +5,57 @@ set -e -echo "🚀 Starting Grateful Journal..." +# Cleanup on Ctrl+C or exit +cleanup() { + echo "" + echo "Stopping all services..." + kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + wait $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + echo "All services stopped." + exit 0 +} +trap cleanup INT TERM + +echo "Starting Grateful Journal..." echo "" # Check if MongoDB is running -echo "📦 Checking MongoDB..." +echo "Checking MongoDB..." if lsof -Pi :27017 -sTCP:LISTEN -t >/dev/null 2>&1 ; then - echo "✓ MongoDB already running on port 27017" + echo "MongoDB already running on port 27017" else echo "Starting MongoDB..." brew services start mongodb-community sleep 2 - echo "✓ MongoDB started on port 27017" + echo "MongoDB started on port 27017" fi echo "" # Start Backend (FastAPI with conda environment) -echo "🔄 Starting FastAPI backend..." -# Activate conda and start backend +echo "Starting FastAPI backend..." conda run -n yoyo python backend/main.py & BACKEND_PID=$! -echo "✓ Backend running on http://localhost:8001 (PID: $BACKEND_PID)" +echo "Backend running on http://localhost:8001 (PID: $BACKEND_PID)" sleep 2 echo "" # Start Frontend (Vite) -echo "🔄 Starting Vite frontend..." +echo "Starting Vite frontend..." npm run dev -- --port 8000 & FRONTEND_PID=$! -echo "✓ Frontend running on http://localhost:8000 (PID: $FRONTEND_PID)" +echo "Frontend running on http://localhost:8000 (PID: $FRONTEND_PID)" echo "" -echo "✅ All services started!" +echo "All services started!" echo "" -echo "📱 Frontend: http://localhost:8000" -echo "🔌 Backend: http://localhost:8001" -echo "📄 API Docs: http://localhost:8001/docs" +echo "Frontend: http://localhost:8000" +echo "Backend: http://localhost:8001" +echo "API Docs: http://localhost:8001/docs" echo "" -echo "To stop all services, press Ctrl+C" +echo "Press Ctrl+C to stop all services" echo "" # Wait for both processes